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 = {