diff --git a/composeApp/src/androidMain/kotlin/org/example/project/MyApp.kt b/composeApp/src/androidMain/kotlin/org/example/project/MyApp.kt
index de685ea..5d969e4 100644
--- a/composeApp/src/androidMain/kotlin/org/example/project/MyApp.kt
+++ b/composeApp/src/androidMain/kotlin/org/example/project/MyApp.kt
@@ -1,7 +1,7 @@
package org.example.project
import android.app.Application
-import org.example.project.di.initKoin
+import initKoin
class MyApp : Application() {
override fun onCreate() {
diff --git a/composeApp/src/commonMain/composeResources/values-ar/string.xml b/composeApp/src/commonMain/composeResources/values-ar/string.xml
index 4659eed..0e01b6b 100644
--- a/composeApp/src/commonMain/composeResources/values-ar/string.xml
+++ b/composeApp/src/commonMain/composeResources/values-ar/string.xml
@@ -24,4 +24,20 @@
التالى
تخطى
+
+
+ أين موقعك؟
+ الموقع يساعد في تحسين الدقة، لكن لا تقلق، يمكنك تحديثه لاحقًا.
+ المحافظة، المنطقة
+ اختر المحافظة
+ اختر المنطقة
+ التالي
+ أيقونة الموقع
+ أيقونة القائمة المنسدلة
+ أدخل موقعك بالتفصيل
+ العوده
+ السهم لأسفل
+
+
+
\ No newline at end of file
diff --git a/composeApp/src/commonMain/composeResources/values/string.xml b/composeApp/src/commonMain/composeResources/values/string.xml
index afd1afe..2d14a09 100644
--- a/composeApp/src/commonMain/composeResources/values/string.xml
+++ b/composeApp/src/commonMain/composeResources/values/string.xml
@@ -33,4 +33,21 @@
What services do you offer?
Choose your specialties to get relevant job requests. You can change this later.
+
+
+ Where are you located?
+ Location helps improve accuracy, but don\'t worry, you can update it later.
+ Governorate, District
+ Choose Governorate
+ Choose District
+ Next
+ Location icon
+ Dropdown
+ Enter your location in detail
+ back arrow
+ back arrow
+
+
+
+
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/dto/DistrictDto.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/dto/DistrictDto.kt
new file mode 100644
index 0000000..93c9914
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/dto/DistrictDto.kt
@@ -0,0 +1,11 @@
+package org.example.project.data.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DistrictDto(
+ @SerialName("id") val id: String,
+ @SerialName("name") val name: String,
+ @SerialName("governorateId") val governorateId: String
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/dto/GovernoratesDto.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/dto/GovernoratesDto.kt
new file mode 100644
index 0000000..1fad12c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/dto/GovernoratesDto.kt
@@ -0,0 +1,10 @@
+package org.example.project.data.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GovernoratesDto(
+ @SerialName("id") val id: String,
+ @SerialName("name") val name: String
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/LocationRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/LocationRepositoryImpl.kt
new file mode 100644
index 0000000..5065857
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/LocationRepositoryImpl.kt
@@ -0,0 +1,32 @@
+package org.example.project.data.repository
+
+import io.ktor.client.HttpClient
+import io.ktor.client.request.get
+import io.ktor.client.call.body
+import org.example.project.data.dto.DistrictDto
+import org.example.project.data.dto.GovernoratesDto
+import org.example.project.data.repository.mapper.toDistrict
+import org.example.project.data.repository.mapper.toGovernorates
+import org.example.project.data.utils.NetworkConstants.DISTRICT_ENDPOINT
+import org.example.project.data.utils.NetworkConstants.GOVERNORATES_ENDPOINT
+import org.example.project.data.utils.NetworkConstants.LOCATION_PATH
+import org.example.project.domain.entity.District
+import org.example.project.domain.entity.Governorates
+import org.example.project.domain.repository.LocationRepository
+
+class LocationRepositoryImpl(
+ private val httpClient: HttpClient
+) : LocationRepository {
+
+ override suspend fun getAllGovernorates(): List {
+ val response = httpClient.get("/$LOCATION_PATH/$GOVERNORATES_ENDPOINT")
+ val body: List = response.body()
+ return body.toGovernorates()
+ }
+
+ override suspend fun getDistrictsByGovernorateId(governorateId: String): List {
+ val response = httpClient.get("/$LOCATION_PATH/$DISTRICT_ENDPOINT/$governorateId")
+ val body: List = response.body()
+ return body.toDistrict()
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/District.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/District.kt
new file mode 100644
index 0000000..0c57291
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/District.kt
@@ -0,0 +1,14 @@
+package org.example.project.data.repository.mapper
+
+import org.example.project.data.dto.DistrictDto
+import org.example.project.domain.entity.District
+
+fun List.toDistrict(): List {
+ return map { dto ->
+ District(
+ id = dto.id,
+ name = dto.name,
+ governorateId = dto.governorateId
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/Gavernorate.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/Gavernorate.kt
new file mode 100644
index 0000000..f7b5233
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/mapper/Gavernorate.kt
@@ -0,0 +1,13 @@
+package org.example.project.data.repository.mapper
+
+import org.example.project.data.dto.GovernoratesDto
+import org.example.project.domain.entity.Governorates
+
+fun List.toGovernorates(): List {
+ return map { dto ->
+ Governorates(
+ id = dto.id,
+ name = dto.name
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/utils/NetworkConstants.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/utils/NetworkConstants.kt
index 9c7d8c5..8e09a6f 100644
--- a/composeApp/src/commonMain/kotlin/org/example/project/data/utils/NetworkConstants.kt
+++ b/composeApp/src/commonMain/kotlin/org/example/project/data/utils/NetworkConstants.kt
@@ -2,4 +2,8 @@ package org.example.project.data.utils
object NetworkConstants {
const val ONBOARDING_END_POINT = "/onboarding"
+
+ const val LOCATION_PATH = "location"
+ const val GOVERNORATES_ENDPOINT = "governorates"
+ const val DISTRICT_ENDPOINT = "district"
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/CraftoModule.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/CraftoModule.kt
index 032127c..91df6ba 100644
--- a/composeApp/src/commonMain/kotlin/org/example/project/di/CraftoModule.kt
+++ b/composeApp/src/commonMain/kotlin/org/example/project/di/CraftoModule.kt
@@ -1,8 +1,11 @@
-package org.example.project.di
-
-import org.koin.core.annotation.ComponentScan
+import org.example.project.data.repository.LocationRepositoryImpl
+import org.example.project.domain.repository.LocationRepository
import org.koin.core.annotation.Module
+import org.koin.dsl.module
@Module
-@ComponentScan("org.example.project")
-class CraftoModule
\ No newline at end of file
+class CraftoModule {
+ val module = module {
+ single { LocationRepositoryImpl(get()) }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/ViewModelModule.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/ViewModelModule.kt
new file mode 100644
index 0000000..25a13a5
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/di/ViewModelModule.kt
@@ -0,0 +1,11 @@
+package org.example.project.di
+
+import org.example.project.presentation.screens.setupScreens.location.LocationViewModel
+import org.koin.core.module.dsl.viewModel
+import org.koin.dsl.module
+
+class ViewModelModule {
+ val module = module {
+ viewModel { LocationViewModel(get()) }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt
index a4aba07..c6b2ef1 100644
--- a/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt
+++ b/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt
@@ -1,5 +1,5 @@
-package org.example.project.di
-
+import org.example.project.di.NetworkModule
+import org.example.project.di.ViewModelModule
import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration
import org.koin.ksp.generated.module
@@ -7,6 +7,10 @@ import org.koin.ksp.generated.module
fun initKoin(config: KoinAppDeclaration? = null) {
startKoin {
config?.invoke(this)
- modules(CraftoModule().module, NetworkModule().module)
+ modules(
+ CraftoModule().module,
+ ViewModelModule().module,
+ NetworkModule().module
+ )
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/District.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/District.kt
new file mode 100644
index 0000000..6d843cb
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/District.kt
@@ -0,0 +1,7 @@
+package org.example.project.domain.entity
+
+data class District(
+ val id: String,
+ val name: String,
+ val governorateId: String
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/Governorates.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/Governorates.kt
new file mode 100644
index 0000000..eabb1af
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/Governorates.kt
@@ -0,0 +1,6 @@
+package org.example.project.domain.entity
+
+data class Governorates(
+ val id: String,
+ val name: String
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/LocationRepository.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/LocationRepository.kt
new file mode 100644
index 0000000..f352d76
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/LocationRepository.kt
@@ -0,0 +1,9 @@
+package org.example.project.domain.repository
+
+import org.example.project.domain.entity.District
+import org.example.project.domain.entity.Governorates
+
+interface LocationRepository {
+ suspend fun getAllGovernorates(): List
+ suspend fun getDistrictsByGovernorateId(governorateId: String): List
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DetailLocationInput.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DetailLocationInput.kt
new file mode 100644
index 0000000..c27022b
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DetailLocationInput.kt
@@ -0,0 +1,23 @@
+package org.example.project.presentation.components
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import org.example.project.presentation.designsystem.components.TextField
+import org.jetbrains.compose.resources.stringResource
+import crafto.composeapp.generated.resources.Res
+import crafto.composeapp.generated.resources.enter_detailed_location
+
+@Composable
+fun DetailLocationInput(
+ text: String,
+ onTextChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ TextField(
+ hint = stringResource(Res.string.enter_detailed_location),
+ text = text,
+ onTextChange = onTextChange,
+ modifier = modifier.fillMaxWidth()
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownBottomSheet.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownBottomSheet.kt
new file mode 100644
index 0000000..a9df274
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownBottomSheet.kt
@@ -0,0 +1,57 @@
+package org.example.project.presentation.components
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.example.project.presentation.designsystem.components.BottomSheet
+import org.example.project.presentation.designsystem.textstyle.AppTheme
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+import crafto.composeapp.generated.resources.Res
+import crafto.composeapp.generated.resources.alt_arrow_down
+import crafto.composeapp.generated.resources.down_arrow
+
+@Composable
+fun DropdownBottomSheet(
+ show: Boolean,
+ items: List,
+ itemLabel: (T) -> String,
+ onDismiss: () -> Unit,
+ onSelect: (T) -> Unit
+) {
+ if (show && items.isNotEmpty()) {
+ BottomSheet(onDismissRequest = onDismiss) {
+ LazyColumn {
+ items(items) { item ->
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onSelect(item) }
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = itemLabel(item),
+ style = AppTheme.textStyle.body.medium,
+ color = AppTheme.craftoColors.shade.primary,
+ modifier = Modifier.weight(1f)
+ )
+ Icon(
+ painter = painterResource(Res.drawable.alt_arrow_down),
+ contentDescription = stringResource(Res.string.down_arrow),
+ tint = AppTheme.craftoColors.shade.secondary
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownSelector.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownSelector.kt
new file mode 100644
index 0000000..79d0eda
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/components/DropdownSelector.kt
@@ -0,0 +1,74 @@
+package org.example.project.presentation.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import org.example.project.presentation.designsystem.components.TextField
+import org.example.project.presentation.designsystem.textstyle.AppTheme
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+import crafto.composeapp.generated.resources.*
+
+@Composable
+fun DropdownSelector(
+ text: String,
+ hint: String,
+ icon: DrawableResource,
+ hasSelection: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(48.dp)
+ .background(
+ color = AppTheme.craftoColors.background.card,
+ shape = RoundedCornerShape(AppTheme.craftoRadius.lg)
+ )
+ .clickable(
+ onClick = onClick,
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ )
+ .semantics { role = Role.Button },
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ TextField(
+ hint = hint,
+ text = text,
+ onTextChange = {},
+ readOnlyMode = true,
+ enabledState = false,
+ startIcon = {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = null,
+ tint = if (hasSelection) AppTheme.craftoColors.shade.primary
+ else AppTheme.craftoColors.shade.tertiary
+ )
+ },
+ endIcon = {
+ Icon(
+ painter = painterResource(Res.drawable.alt_arrow_down),
+ contentDescription = stringResource(Res.string.dropdown_icon),
+ tint = if (hasSelection) AppTheme.craftoColors.shade.primary
+ else AppTheme.craftoColors.shade.tertiary
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/AccountSetupTopBar.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/AccountSetupTopBar.kt
new file mode 100644
index 0000000..28fd946
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/AccountSetupTopBar.kt
@@ -0,0 +1,84 @@
+package org.example.project.presentation.screens.setupScreens.location
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import crafto.composeapp.generated.resources.Res
+import crafto.composeapp.generated.resources.arrow_left
+import org.example.project.presentation.designsystem.components.ProgressIndicator
+import org.example.project.presentation.designsystem.textstyle.AppTheme
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.ui.tooling.preview.Preview
+
+@Composable
+fun AccountSetupTopBar(
+ modifier: Modifier = Modifier,
+ currentPage: Int
+) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ )
+ {
+ BackButton()
+ ProgressIndicator(
+ currentPage = currentPage,
+ totalPage = 4,
+ modifier = Modifier.fillMaxWidth(0.75f),
+ )
+ }
+}
+
+@Composable
+private fun BackButton(
+ modifier: Modifier = Modifier,
+) {
+ Box(
+ modifier = modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .background(AppTheme.craftoColors.background.card),
+ contentAlignment = Alignment.Center
+ )
+ {
+ Icon(
+ painter = painterResource(Res.drawable.arrow_left),
+ contentDescription = "back button",
+ tint = AppTheme.craftoColors.shade.primary
+ )
+ }
+}
+
+
+@Preview
+@Composable
+fun AccountSetupTopBarLightPreview() {
+ AppTheme {
+ AccountSetupTopBar(
+ currentPage = 2
+ )
+
+ }
+}
+
+@Preview
+@Composable
+fun AccountSetupTopBarDarkPreview() {
+ AppTheme(isDarkTheme = true) {
+ AccountSetupTopBar(
+ currentPage = 2
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationEffect.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationEffect.kt
new file mode 100644
index 0000000..2d5e88b
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationEffect.kt
@@ -0,0 +1,6 @@
+package org.example.project.presentation.screens.setupScreens.location
+
+sealed class LocationEffect {
+ object NavigateToNextScreen : LocationEffect()
+ data class ShowError(val message: String) : LocationEffect()
+}
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationSetupScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationSetupScreen.kt
new file mode 100644
index 0000000..bef097b
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationSetupScreen.kt
@@ -0,0 +1,166 @@
+package org.example.project.presentation.screens.setupScreens.location
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import crafto.composeapp.generated.resources.Res
+import crafto.composeapp.generated.resources.*
+import org.example.project.presentation.components.DetailLocationInput
+import org.example.project.presentation.components.DropdownBottomSheet
+import org.example.project.presentation.components.DropdownSelector
+import org.example.project.presentation.designsystem.components.ButtonState
+import org.example.project.presentation.designsystem.components.PrimaryButton
+import org.example.project.presentation.designsystem.textstyle.AppTheme
+import org.jetbrains.compose.resources.stringResource
+import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.annotation.KoinExperimentalAPI
+
+@OptIn(KoinExperimentalAPI::class)
+@Composable
+fun LocationSetupScreen(
+ viewModel: LocationViewModel = koinViewModel()
+) {
+ val state by viewModel.state.collectAsState()
+
+ HandleEffects(viewModel)
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .windowInsetsPadding(WindowInsets.safeDrawing)
+ .background(AppTheme.craftoColors.background.screen)
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ AccountSetupTopBar(modifier = Modifier.fillMaxWidth(), currentPage = 2)
+ LocationHeader(modifier = Modifier.weight(1f))
+
+ DropdownSelector(
+ text = state.locationDisplayText,
+ hint = stringResource(Res.string.location_hint),
+ icon = Res.drawable.location,
+ hasSelection = state.selectedGovernorate.isNotBlank() && state.selectedDistrict.isNotBlank(),
+ onClick = {
+ viewModel.openGovernorateSheet()
+ }
+ )
+
+ DetailLocationInput(
+ text = state.detailLocation,
+ onTextChange = viewModel::updateDetailLocation
+ )
+
+ ErrorMessage(error = state.error)
+
+ NextButton(
+ onClick = viewModel::onNextClick,
+ enabled = state.selectedGovernorate.isNotBlank() && state.selectedDistrict.isNotBlank()
+ )
+ }
+
+ DropdownBottomSheet(
+ show = state.showGovernorateSheet,
+ items = state.governorates,
+ itemLabel = { it.name },
+ onDismiss = viewModel::closeGovernorateSheet,
+ onSelect = viewModel::selectGovernorate
+ )
+
+ DropdownBottomSheet(
+ show = state.showDistrictSheet,
+ items = state.districts,
+ itemLabel = { it.name },
+ onDismiss = viewModel::closeDistrictSheet,
+ onSelect = { viewModel.selectDistrict(it.name) }
+ )
+}
+
+@Composable
+private fun HandleEffects(viewModel: LocationViewModel) {
+ LaunchedEffect(Unit) {
+ viewModel.effect.collect { effect ->
+ when (effect) {
+ is LocationEffect.NavigateToNextScreen -> {
+ // TODO
+ }
+ is LocationEffect.ShowError -> {
+ // TODO
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun LocationHeader(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.Bottom,
+ ) {
+ Text(
+ text = stringResource(Res.string.location_title),
+ style = AppTheme.textStyle.display,
+ color = AppTheme.craftoColors.shade.primary,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ Text(
+ text = stringResource(Res.string.location_description),
+ style = AppTheme.textStyle.body.largeRegular,
+ color = AppTheme.craftoColors.shade.secondary
+ )
+ }
+}
+
+
+
+@Composable
+private fun ErrorMessage(error: String?) {
+ if (error != null) {
+ Text(
+ text = error,
+ style = AppTheme.textStyle.body.medium,
+ color = AppTheme.craftoColors.shade.quinary,
+ modifier = Modifier.padding(top = 8.dp)
+ )
+ }
+}
+
+@Composable
+private fun NextButton(
+ onClick: () -> Unit,
+ enabled: Boolean
+) {
+ PrimaryButton(
+ text = stringResource(Res.string.next_button),
+ enabled = enabled,
+ onClick = onClick,
+ buttonState = if (enabled) ButtonState.Enable else ButtonState.DISABLED,
+ modifier = Modifier.fillMaxWidth()
+ )
+}
+
+
+@Preview
+@Composable
+private fun LocationSetupScreenPreview() {
+ AppTheme(isDarkTheme = false) {
+ LocationSetupScreen()
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationUiState.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationUiState.kt
new file mode 100644
index 0000000..f403a7d
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationUiState.kt
@@ -0,0 +1,18 @@
+package org.example.project.presentation.screens.setupScreens.location
+
+import org.example.project.domain.entity.District
+import org.example.project.domain.entity.Governorates
+
+data class LocationUiState(
+ val governorates: List = emptyList(),
+ val districts: List = emptyList(),
+ val selectedGovernorate: String = "",
+ val selectedGovernorateId: String = "",
+ val selectedDistrict: String = "",
+ val detailLocation: String = "",
+ val isLoading: Boolean = false,
+ val error: String? = null,
+ val showGovernorateSheet: Boolean = false,
+ val showDistrictSheet: Boolean = false,
+ val locationDisplayText: String = "Governorate, District"
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationViewModel.kt
new file mode 100644
index 0000000..1fea3cb
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setupScreens/location/LocationViewModel.kt
@@ -0,0 +1,106 @@
+package org.example.project.presentation.screens.setupScreens.location
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.IO
+import org.example.project.domain.entity.Governorates
+import org.example.project.domain.repository.LocationRepository
+import org.example.project.presentation.shared.base.BaseViewModel
+
+class LocationViewModel(
+ private val repository: LocationRepository
+) : BaseViewModel(initialState = LocationUiState()) {
+
+ init {
+ fetchGovernorates()
+ updateLocationDisplay()
+ }
+
+
+ private fun fetchGovernorates() {
+ tryToCall(
+ call = { repository.getAllGovernorates() },
+ onSuccess = { governorates ->
+ updateState { it.copy(governorates = governorates, isLoading = false, error = null) }
+ },
+ onError = { error ->
+ updateState { it.copy(isLoading = false, error = error.message) }
+ sendNewEffect(LocationEffect.ShowError(error.message))
+ },
+ dispatcher = Dispatchers.IO
+ )
+ }
+
+ fun fetchDistricts(governorateId: String) {
+ tryToCall(
+ call = { repository.getDistrictsByGovernorateId(governorateId) },
+ onSuccess = { districts ->
+ updateState {
+ it.copy(
+ districts = districts,
+ isLoading = false,
+ showDistrictSheet = districts.isNotEmpty(),
+ error = null
+ )
+ }
+ },
+ onError = { error ->
+ updateState { it.copy(isLoading = false, error = error.message) }
+ sendNewEffect(LocationEffect.ShowError(error.message))
+ },
+ dispatcher = Dispatchers.IO
+ )
+ }
+ fun updateDetailLocation(text: String) {
+ updateState { it.copy(detailLocation = text) }
+ }
+ fun selectGovernorate(governorate: Governorates) {
+ updateState {
+ it.copy(
+ selectedGovernorate = governorate.name,
+ selectedGovernorateId = governorate.id,
+ selectedDistrict = "",
+ showGovernorateSheet = false
+ )
+ }
+ fetchDistricts(governorate.id)
+ updateLocationDisplay()
+ }
+
+ fun selectDistrict(district: String) {
+ updateState {
+ it.copy(
+ selectedDistrict = district,
+ showDistrictSheet = false
+ )
+ }
+ updateLocationDisplay()
+ }
+
+ private fun updateLocationDisplay() {
+ updateState { currentState ->
+ val parts = listOfNotNull(
+ currentState.selectedGovernorate.takeIf { it.isNotBlank() },
+ currentState.selectedDistrict.takeIf { it.isNotBlank() }
+ )
+ val displayText = parts.joinToString(", ")
+
+ currentState.copy(locationDisplayText = displayText)
+ }
+ }
+
+ fun openGovernorateSheet() {
+ updateState { it.copy(showGovernorateSheet = true) }
+ }
+
+ fun closeGovernorateSheet() {
+ updateState { it.copy(showGovernorateSheet = false) }
+ }
+
+ fun closeDistrictSheet() {
+ updateState { it.copy(showDistrictSheet = false) }
+ }
+
+ fun onNextClick() {
+ sendNewEffect(LocationEffect.NavigateToNextScreen)
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/org/example/project/MainViewController.kt b/composeApp/src/iosMain/kotlin/org/example/project/MainViewController.kt
index f581b42..a04ca7d 100644
--- a/composeApp/src/iosMain/kotlin/org/example/project/MainViewController.kt
+++ b/composeApp/src/iosMain/kotlin/org/example/project/MainViewController.kt
@@ -1,7 +1,7 @@
package org.example.project
import androidx.compose.ui.window.ComposeUIViewController
-import org.example.project.di.initKoin
+import initKoin
fun MainViewController() = ComposeUIViewController(
configure = {