From 73d4b4e768791350b48118cff674995c5d99f915 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sat, 15 Nov 2025 19:05:55 +0200 Subject: [PATCH 1/9] feat: implement navigation system with user type selection and bottom navigation --- composeApp/build.gradle.kts | 9 +- .../composeResources/values-ar/string.xml | 2 +- .../composeResources/values/string.xml | 2 +- .../kotlin/org/example/project/App.kt | 9 +- .../repository/OnboardingRepositoryImp.kt | 6 +- .../data/repository/UserPreferencesImpl.kt | 29 ++++ .../org/example/project/di/DataModule.kt | 5 + .../org/example/project/di/DomainModule.kt | 9 + .../example/project/di/PresentationModule.kt | 6 +- .../kotlin/org/example/project/di/initKoin.kt | 1 - .../project/domain/entity/UserSession.kt | 7 + .../example/project/domain/entity/UserType.kt | 6 + .../domain/repository/UserPreferences.kt | 11 ++ .../session/ClearUserSessionUseCase.kt | 12 ++ .../usecase/session/GetUserSessionUseCase.kt | 16 ++ .../session/MarkOnboardingCompleteUseCase.kt | 11 ++ .../usecase/session/SaveUserTypeUseCase.kt | 12 ++ .../navigation/BottomNavigation.kt | 106 ++++++++++++ .../presentation/navigation/CraftoNavGraph.kt | 161 ++++++++++++++++++ .../presentation/navigation/Destination.kt | 61 +++++++ .../navigation/routes/AuthRoutes.kt | 78 +++++++++ .../navigation/routes/BottomNavRoutes.kt | 41 +++++ .../navigation/routes/SetupRoutes.kt | 49 ++++++ .../screens/auth/UserTypeSelectionEffect.kt | 7 + .../UserTypeSelectionInteractionListener.kt | 8 + .../screens/auth/UserTypeSelectionScreen.kt | 129 ++++++++++++++ .../screens/auth/UserTypeSelectionUiState.kt | 11 ++ .../auth/UserTypeSelectionViewModel.kt | 57 +++++++ .../presentation/screens/home/HomeScreen.kt | 29 ++++ .../screens/messages/MessagesScreen.kt | 29 ++++ .../presentation/screens/more/MoreScreen.kt | 29 ++++ .../screens/myjobs/MyJobsScreen.kt | 29 ++++ .../screens/myrequests/MyRequestsScreen.kt | 29 ++++ .../screens/onboarding/OnboardingScreen.kt | 25 ++- .../screens/onboarding/OnboardingViewModel.kt | 34 +++- .../screens/register/RegisterScreen.kt | 18 +- .../setup/composable/SetupScreenScaffold.kt | 13 +- .../composable/page/UserTypeSelectionPage.kt | 7 +- .../CraftsmanSetupInteractionListener.kt | 2 - .../craftsmansetup/CraftsmanSetupScreen.kt | 15 -- .../craftsmansetup/CraftsmanSetupUiState.kt | 22 +-- .../craftsmansetup/CraftsmanSetupViewModel.kt | 19 +-- .../customersetup/CustomerSetupScreen.kt | 12 ++ .../screens/setup/customersetup/init | 1 - gradle/libs.versions.toml | 20 ++- 45 files changed, 1089 insertions(+), 105 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserSession.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserType.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/ClearUserSessionUseCase.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/GetUserSessionUseCase.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/MarkOnboardingCompleteUseCase.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/SaveUserTypeUseCase.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/BottomNavRoutes.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/SetupRoutes.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionEffect.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionInteractionListener.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionUiState.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/CustomerSetupScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/init diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e69e0f7..7ee770b 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -78,6 +78,7 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.runtimeCompose) + //adaptive implementation(libs.adaptive) implementation(libs.adaptive.layout) @@ -90,24 +91,24 @@ kotlin { implementation(libs.koin.compose.viewmodel.navigation) api(libs.koin.annotations) + //ktor implementation(libs.ktor.client.core) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.bundles.coil) - implementation(libs.kotlinx.datetime) - implementation(libs.androidx.datastore.preferences) - implementation(libs.calf.file.picker) + //navigation + implementation(libs.navigation.compose) + implementation(libs.kotlinx.serialization.json) } commonTest.dependencies { implementation(libs.kotlin.test) - implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.mock) implementation(libs.koin.test) diff --git a/composeApp/src/commonMain/composeResources/values-ar/string.xml b/composeApp/src/commonMain/composeResources/values-ar/string.xml index 0dc7e89..fe30da7 100644 --- a/composeApp/src/commonMain/composeResources/values-ar/string.xml +++ b/composeApp/src/commonMain/composeResources/values-ar/string.xml @@ -6,7 +6,7 @@ الفريق يحاول حلها حاليا, جرب مرة اخرى. حسناً - مرحبًا بك في صنعة 👋 + مرحبًا بك في كرافتو 👋 أدخل رقم هاتفك للمتابعة +٢٠ ٠٠٠ - ٠٠٠ - ٠٠٠٠ الشروط والأحكام diff --git a/composeApp/src/commonMain/composeResources/values/string.xml b/composeApp/src/commonMain/composeResources/values/string.xml index a8033d4..5756f83 100644 --- a/composeApp/src/commonMain/composeResources/values/string.xml +++ b/composeApp/src/commonMain/composeResources/values/string.xml @@ -7,7 +7,7 @@ Ok - Welcome to San3a 👋 + Welcome to Crafto 👋 Enter your phone number to\n continue +20 000 - 000 - 0000 Terms and Conditions diff --git a/composeApp/src/commonMain/kotlin/org/example/project/App.kt b/composeApp/src/commonMain/kotlin/org/example/project/App.kt index e767ad8..3f8f0ed 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/App.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/App.kt @@ -1,17 +1,16 @@ package org.example.project import androidx.compose.runtime.Composable +import androidx.navigation.compose.rememberNavController import org.example.project.presentation.designsystem.textstyle.AppTheme -import org.example.project.presentation.screens.setup.craftsmansetup.CraftsmanSetupScreen -import org.example.project.presentation.screens.splash.SplashScreen +import org.example.project.presentation.navigation.CraftoNavGraph import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @Preview fun App() { AppTheme { - //OnboardingScreen() - SplashScreen { } - CraftsmanSetupScreen() + val navController = rememberNavController() + CraftoNavGraph(navController = navController) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/OnboardingRepositoryImp.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/OnboardingRepositoryImp.kt index b4ec49b..d0cbbb2 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/OnboardingRepositoryImp.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/OnboardingRepositoryImp.kt @@ -8,13 +8,11 @@ import org.example.project.data.remote.network.ApiConstants.Endpoints.ONBOARDING import org.example.project.data.remote.network.wrapApiCall import org.example.project.domain.entity.OnboardingItem import org.example.project.domain.repository.OnboardingRepository -import org.koin.core.annotation.Provided -import org.koin.core.annotation.Single -@Single(binds = [OnboardingRepository::class]) + class OnboardingRepositoryImp( - @Provided private val httpClient: HttpClient + private val httpClient: HttpClient ) : OnboardingRepository { override suspend fun getOnboardingData(): List { diff --git a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/UserPreferencesImpl.kt b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/UserPreferencesImpl.kt index aec8671..5649de8 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/data/repository/UserPreferencesImpl.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/data/repository/UserPreferencesImpl.kt @@ -1,6 +1,7 @@ package org.example.project.data.repository import org.example.project.data.local.datasource.StorageLocalDataSource +import org.example.project.domain.entity.UserType import org.example.project.domain.repository.UserPreferences class UserPreferencesImpl( @@ -9,6 +10,8 @@ class UserPreferencesImpl( companion object { private const val KEY_USER_ID = "user_id" + private const val KEY_USER_TYPE = "user_type" + private const val KEY_IS_FIRST_TIME = "is_first_time" } override suspend fun getUserId(): String? { @@ -22,4 +25,30 @@ class UserPreferencesImpl( override suspend fun clearUserId() { storage.remove(KEY_USER_ID) } + + override suspend fun getUserType(): UserType? { + return storage.getString(KEY_USER_TYPE)?.let { + try { + UserType.valueOf(it) + } catch (e: IllegalArgumentException) { + null + } + } + } + + override suspend fun setUserType(userType: UserType) { + storage.saveString(KEY_USER_TYPE, userType.name) + } + + override suspend fun clearUserType() { + storage.remove(KEY_USER_TYPE) + } + + override suspend fun isFirstTime(): Boolean { + return storage.getBoolean(KEY_IS_FIRST_TIME) ?: true + } + + override suspend fun setFirstTime(isFirstTime: Boolean) { + storage.saveBoolean(KEY_IS_FIRST_TIME, isFirstTime) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/DataModule.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/DataModule.kt index 4ab09b4..a948c51 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/di/DataModule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/di/DataModule.kt @@ -11,10 +11,12 @@ import org.example.project.data.remote.datasource.CraftsmanRemoteDataSourceImpl import org.example.project.data.repository.CategoryRepositoryImpl import org.example.project.data.repository.CraftsmanRepositoryImpl import org.example.project.data.repository.LocationRepositoryImpl +import org.example.project.data.repository.OnboardingRepositoryImp import org.example.project.data.service.ValidationServiceImpl import org.example.project.domain.repository.CategoryRepository import org.example.project.domain.repository.CraftsmanRepository import org.example.project.domain.repository.LocationRepository +import org.example.project.domain.repository.OnboardingRepository import org.example.project.domain.service.ValidationService import org.koin.dsl.module @@ -27,6 +29,9 @@ val dataModule = module { userPreferences = get() ) } + single { + OnboardingRepositoryImp(get()) + } single { categorySeed } single { CategoryMemoryDataSource(get()) } single { CategoryRepositoryImpl(get()) } diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/DomainModule.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/DomainModule.kt index 70c9a8c..74abecb 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/di/DomainModule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/di/DomainModule.kt @@ -8,6 +8,10 @@ import org.example.project.domain.usecase.craftsman.GetCraftsmanStatusUseCase import org.example.project.domain.usecase.craftsman.UploadIdCardsUseCase import org.example.project.domain.usecase.craftsman.UploadProfilePictureUseCase import org.example.project.domain.usecase.craftsman.UploadWorkPortfolioUseCase +import org.example.project.domain.usecase.session.ClearUserSessionUseCase +import org.example.project.domain.usecase.session.GetUserSessionUseCase +import org.example.project.domain.usecase.session.MarkOnboardingCompleteUseCase +import org.example.project.domain.usecase.session.SaveUserTypeUseCase import org.koin.dsl.module val domainModule = module { @@ -19,4 +23,9 @@ val domainModule = module { factory { DeleteCraftsmanAccountUseCase(get()) } factory { GetCategoriesUseCase(get())} factory { UploadProfilePictureUseCase(get(),get()) } + + factory { GetUserSessionUseCase(get()) } + factory { SaveUserTypeUseCase(get()) } + factory { MarkOnboardingCompleteUseCase(get()) } + factory { ClearUserSessionUseCase(get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/di/PresentationModule.kt b/composeApp/src/commonMain/kotlin/org/example/project/di/PresentationModule.kt index 9a1adec..e5d40cb 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/di/PresentationModule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/di/PresentationModule.kt @@ -1,5 +1,7 @@ package org.example.project.di +import org.example.project.presentation.screens.auth.UserTypeSelectionViewModel +import org.example.project.presentation.screens.onboarding.OnboardingViewModel import org.example.project.presentation.screens.setup.craftsmansetup.CraftsmanSetupViewModel import org.example.project.presentation.screens.setup.location.LocationViewModel import org.koin.core.module.dsl.viewModel @@ -15,5 +17,7 @@ val presentationModule = module { uploadProfilePictureUseCase = get(), ) } - viewModel {LocationViewModel(get())} + viewModel { LocationViewModel(get()) } + viewModel { OnboardingViewModel(get(), get()) } + viewModel { UserTypeSelectionViewModel(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 e38cf21..f2bec9c 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/di/initKoin.kt @@ -8,7 +8,6 @@ fun initKoin(config: KoinAppDeclaration? = null) { startKoin { config?.invoke(this) modules( - //CraftoModule().module, networkModule, dataModule, domainModule, diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserSession.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserSession.kt new file mode 100644 index 0000000..a697ae4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserSession.kt @@ -0,0 +1,7 @@ +package org.example.project.domain.entity + +data class UserSession( + val userId: String?, + val userType: UserType?, + val isFirstTime: Boolean +) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserType.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserType.kt new file mode 100644 index 0000000..8897a23 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/entity/UserType.kt @@ -0,0 +1,6 @@ +package org.example.project.domain.entity + +enum class UserType { + CUSTOMER, + CRAFTSMAN +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/UserPreferences.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/UserPreferences.kt index 7564fd0..17a04b6 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/UserPreferences.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/repository/UserPreferences.kt @@ -1,7 +1,18 @@ package org.example.project.domain.repository +import org.example.project.domain.entity.UserType + interface UserPreferences { suspend fun getUserId(): String? suspend fun setUserId(userId: String) suspend fun clearUserId() + + //TODO: Separate UserType to its own Repository + suspend fun getUserType(): UserType? + suspend fun setUserType(userType: UserType) + suspend fun clearUserType() + + // Add these + suspend fun isFirstTime(): Boolean + suspend fun setFirstTime(isFirstTime: Boolean) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/ClearUserSessionUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/ClearUserSessionUseCase.kt new file mode 100644 index 0000000..69cd850 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/ClearUserSessionUseCase.kt @@ -0,0 +1,12 @@ +package org.example.project.domain.usecase.session + +import org.example.project.domain.repository.UserPreferences + +class ClearUserSessionUseCase( + private val userPreferences: UserPreferences +) { + suspend operator fun invoke() { + userPreferences.clearUserId() + userPreferences.clearUserType() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/GetUserSessionUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/GetUserSessionUseCase.kt new file mode 100644 index 0000000..e367f19 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/GetUserSessionUseCase.kt @@ -0,0 +1,16 @@ +package org.example.project.domain.usecase.session + +import org.example.project.domain.entity.UserSession +import org.example.project.domain.repository.UserPreferences + +class GetUserSessionUseCase( + private val userPreferences: UserPreferences +) { + suspend operator fun invoke(): UserSession { + return UserSession( + userId = userPreferences.getUserId(), + userType = userPreferences.getUserType(), + isFirstTime = userPreferences.isFirstTime() + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/MarkOnboardingCompleteUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/MarkOnboardingCompleteUseCase.kt new file mode 100644 index 0000000..44f490b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/MarkOnboardingCompleteUseCase.kt @@ -0,0 +1,11 @@ +package org.example.project.domain.usecase.session + +import org.example.project.domain.repository.UserPreferences + +class MarkOnboardingCompleteUseCase( + private val userPreferences: UserPreferences +) { + suspend operator fun invoke() { + userPreferences.setFirstTime(false) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/SaveUserTypeUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/SaveUserTypeUseCase.kt new file mode 100644 index 0000000..e0f0565 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/domain/usecase/session/SaveUserTypeUseCase.kt @@ -0,0 +1,12 @@ +package org.example.project.domain.usecase.session + +import org.example.project.domain.entity.UserType +import org.example.project.domain.repository.UserPreferences + +class SaveUserTypeUseCase( + private val userPreferences: UserPreferences +) { + suspend operator fun invoke(userType: UserType) { + userPreferences.setUserType(userType) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt new file mode 100644 index 0000000..3e0f469 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -0,0 +1,106 @@ +package org.example.project.presentation.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import crafto.composeapp.generated.resources.Res +import crafto.composeapp.generated.resources.* +import org.example.project.domain.entity.UserType +import org.example.project.presentation.designsystem.components.CraftoBottomNavBar +import org.example.project.presentation.designsystem.components.CraftoNavItem +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource + + +enum class BottomNavigationItem( + val icon: DrawableResource, + val selectedIcon: DrawableResource, + val label: String, + val route: NavigationBarDestinations, +) { + Home( + icon = Res.drawable.home_angle, + selectedIcon = Res.drawable.home_angle_1, + label = "Home", + route = NavigationBarDestinations.HomeScreen + ), + MyRequests( // Customer + icon = Res.drawable.clipboard_text, + selectedIcon = Res.drawable.clipboard_text_1, + label = "My Requests", + route = NavigationBarDestinations.MyRequestsScreen + ), + MyJobs( // Craftsman + icon = Res.drawable.clipboard_text, + selectedIcon = Res.drawable.clipboard_text_1, + label = "My Jobs", + route = NavigationBarDestinations.MyJobsScreen + ), + Messages( + icon = Res.drawable.dialog, + selectedIcon = Res.drawable.dialog_1, + label = "Messages", + route = NavigationBarDestinations.MessagesScreen + ), + More( + icon = Res.drawable.user_circle, + selectedIcon = Res.drawable.user_circle_1, + label = "More", + route = NavigationBarDestinations.MoreScreen + ) +} + +fun getBottomNavItems(userType: UserType): List { + return when (userType) { + UserType.CUSTOMER -> listOf( + BottomNavigationItem.Home, + BottomNavigationItem.MyRequests, + BottomNavigationItem.Messages, + BottomNavigationItem.More + ) + UserType.CRAFTSMAN -> listOf( + BottomNavigationItem.Home, + BottomNavigationItem.MyJobs, + BottomNavigationItem.Messages, + BottomNavigationItem.More + ) + } +} + +@Composable +fun CraftoNavBar( + currentRoute: NavigationBarDestinations, + onNavDestinationClicked: (NavigationBarDestinations) -> Unit, + userType: UserType +) { + val items = getBottomNavItems(userType) + val selectedIndex = items.indexOfFirst { it.route == currentRoute } + + CraftoBottomNavBar( + items = items.map { item -> + CraftoNavItem( + label = item.label, + icon = painterResource(item.icon), + selectedIcon = painterResource(item.selectedIcon) + ) + }, + selectedIndex = selectedIndex.coerceAtLeast(0), + onItemSelected = { index -> + val selectedItem = items[index] + if (selectedItem.route != currentRoute) { + onNavDestinationClicked(selectedItem.route) + } + } + ) +} + +@Composable +fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { + val backStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = backStackEntry?.destination?.route + + return bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> + currentRoute?.startsWith(route ?: "") == true + }?.value +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt new file mode 100644 index 0000000..c393920 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt @@ -0,0 +1,161 @@ +package org.example.project.presentation.navigation + +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.launch +import org.example.project.domain.entity.UserType +import org.example.project.domain.usecase.session.GetUserSessionUseCase +import org.example.project.presentation.navigation.routes.* +import org.koin.compose.koinInject + +@Composable +fun CraftoNavGraph( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), + getUserSessionUseCase: GetUserSessionUseCase = koinInject() +) { + var userType by remember { mutableStateOf(null) } + var startDestination by remember { mutableStateOf(null) } + var isLoading by remember { mutableStateOf(true) } + + val coroutineScope = rememberCoroutineScope() + + // Determine start destination on first load + LaunchedEffect(Unit) { + coroutineScope.launch { + try { + val session = getUserSessionUseCase() + userType = session.userType + + startDestination = when { + session.isFirstTime -> { + SplashDestination // Will navigate to Onboarding + } + session.userId != null && session.userType != null -> { + NavigationBarDestinations.HomeScreen + } + session.userId != null && session.userType == null -> { + UserTypeSelectionDestination + } + else -> { + SplashDestination // Will navigate to OTP + } + } + } catch (e: Exception) { + println("Error loading session: ${e.message}") + startDestination = SplashDestination + } finally { + isLoading = false + } + } + } + + // Show loading while checking session + if (isLoading || startDestination == null) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + return + } + + Scaffold( + modifier = modifier, + bottomBar = { + getCurrentNavBarScreen(navController)?.let { selectedRoute -> + userType?.let { type -> + ShowNavigationBar( + selectedRoute = selectedRoute, + navController = navController, + userType = type + ) + } + } + } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = startDestination!!, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None } + ) { + authNavigationGraph(navController, onUserTypeUpdated = { type -> userType = type }) + setupNavigationGraph(navController, onUserTypeUpdated = { type -> userType = type }) + bottomNavigationBarGraph(navController) + //detailsNavigationGraph(navController) + } + } +} + +@Composable +private fun ShowNavigationBar( + selectedRoute: NavigationBarDestinations, + navController: NavHostController, + userType: UserType +) { + CraftoNavBar( + currentRoute = selectedRoute, + onNavDestinationClicked = { route -> + navController.navigate(route) { + popUpTo(NavigationBarDestinations.HomeScreen) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + }, + userType = userType + ) +} + +// ===== Navigation Graph Functions ===== + +fun NavGraphBuilder.authNavigationGraph( + navController: NavHostController, + onUserTypeUpdated: (UserType) -> Unit +) { + splashRoute(navController) + onboardingRoute(navController) + otpRegistrationRoute(navController) + userTypeSelectionRoute(navController, onUserTypeUpdated) +} + +fun NavGraphBuilder.setupNavigationGraph( + navController: NavHostController, + onUserTypeUpdated: (UserType) -> Unit +) { + craftsmanSetupRoute(navController, onUserTypeUpdated) + customerSetupRoute(navController, onUserTypeUpdated) +} + +fun NavGraphBuilder.bottomNavigationBarGraph(navController: NavHostController) { + homeRoute(navController) + myRequestsRoute(navController) + myJobsRoute(navController) + messagesRoute(navController) + moreRoute(navController) +} + +//fun NavGraphBuilder.detailsNavigationGraph(navController: NavHostController) { +// requestDetailsRoute(navController) +// jobDetailsRoute(navController) +// messageThreadRoute(navController) +// settingsDetailRoute(navController) +//} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt new file mode 100644 index 0000000..bc7d07e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt @@ -0,0 +1,61 @@ +package org.example.project.presentation.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface NavigationBarDestinations { + @Serializable + data object HomeScreen : NavigationBarDestinations + + @Serializable + data object MyRequestsScreen : NavigationBarDestinations // Customer + + @Serializable + data object MyJobsScreen : NavigationBarDestinations // Craftsman + + @Serializable + data object MessagesScreen : NavigationBarDestinations + + @Serializable + data object MoreScreen : NavigationBarDestinations +} + +val bottomNavBarDestinationsMap = mapOf( + NavigationBarDestinations.HomeScreen::class.qualifiedName to NavigationBarDestinations.HomeScreen, + NavigationBarDestinations.MyRequestsScreen::class.qualifiedName to NavigationBarDestinations.MyRequestsScreen, + NavigationBarDestinations.MyJobsScreen::class.qualifiedName to NavigationBarDestinations.MyJobsScreen, + NavigationBarDestinations.MessagesScreen::class.qualifiedName to NavigationBarDestinations.MessagesScreen, + NavigationBarDestinations.MoreScreen::class.qualifiedName to NavigationBarDestinations.MoreScreen, + //SettingsDetailDestination::class.qualifiedName to NavigationBarDestinations.MoreScreen // Keep bottom nav selected +) + +// ===== Auth Flow Destinations ===== +@Serializable +data object SplashDestination + +@Serializable +data object OnboardingDestination + +@Serializable +data object OtpRegistrationDestination + +@Serializable +data object UserTypeSelectionDestination + +// ===== Setup Destinations ===== +@Serializable +data object CraftsmanSetupDestination + +@Serializable +data object CustomerSetupDestination + +//@Serializable +//data class SettingsDetailDestination(val settingType: String) + +// ============= MAIN APP (With Bottom Nav) ============= +@Serializable +data object MainRoute // Parent for bottom nav + +// ============= INITIAL ROUTE ============= +@Serializable +data object SplashRoute \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt new file mode 100644 index 0000000..e6e96de --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt @@ -0,0 +1,78 @@ +package org.example.project.presentation.navigation.routes + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import org.example.project.domain.entity.UserType +import org.example.project.presentation.navigation.CraftsmanSetupDestination +import org.example.project.presentation.navigation.CustomerSetupDestination +import org.example.project.presentation.navigation.OnboardingDestination +import org.example.project.presentation.navigation.OtpRegistrationDestination +import org.example.project.presentation.navigation.SplashDestination +import org.example.project.presentation.navigation.UserTypeSelectionDestination +import org.example.project.presentation.screens.auth.UserTypeSelectionScreen +import org.example.project.presentation.screens.onboarding.OnboardingScreen +import org.example.project.presentation.screens.register.RegisterScreen +import org.example.project.presentation.screens.splash.SplashScreen + +fun NavGraphBuilder.splashRoute(navController: NavHostController) { + composable { + SplashScreen( + onTimeout = { + navController.navigate(OnboardingDestination) { + popUpTo(SplashDestination) { inclusive = true } + } + } + ) + } +} + +fun NavGraphBuilder.onboardingRoute(navController: NavHostController) { + composable { + OnboardingScreen( + onNavigateToOtp = { + navController.navigate(OtpRegistrationDestination) { + popUpTo(OnboardingDestination) { inclusive = true } + } + } + ) + } +} + +fun NavGraphBuilder.otpRegistrationRoute(navController: NavHostController) { + composable { + RegisterScreen( + onRegistrationComplete = { + navController.navigate(UserTypeSelectionDestination) { + popUpTo(OtpRegistrationDestination) { inclusive = true } + } + }, + ) + } +} + +fun NavGraphBuilder.userTypeSelectionRoute( + navController: NavHostController, + onUserTypeUpdated: (UserType) -> Unit +) { + composable { + UserTypeSelectionScreen( + onNavigateToSetup = { userType -> + onUserTypeUpdated(userType) + + when (userType) { + UserType.CRAFTSMAN -> { + navController.navigate(CraftsmanSetupDestination) { + popUpTo(UserTypeSelectionDestination) { inclusive = true } + } + } + UserType.CUSTOMER -> { + navController.navigate(CustomerSetupDestination) { + popUpTo(UserTypeSelectionDestination) { inclusive = true } + } + } + } + }, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/BottomNavRoutes.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/BottomNavRoutes.kt new file mode 100644 index 0000000..b00c228 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/BottomNavRoutes.kt @@ -0,0 +1,41 @@ +package org.example.project.presentation.navigation.routes + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import org.example.project.presentation.navigation.NavigationBarDestinations +import org.example.project.presentation.screens.home.HomeScreen +import org.example.project.presentation.screens.messages.MessagesScreen +import org.example.project.presentation.screens.more.MoreScreen +import org.example.project.presentation.screens.myjobs.MyJobsScreen +import org.example.project.presentation.screens.myrequests.MyRequestsScreen + +fun NavGraphBuilder.homeRoute(navController: NavHostController) { + composable { + HomeScreen() + } +} + +fun NavGraphBuilder.myRequestsRoute(navController: NavHostController) { + composable { + MyRequestsScreen() + } +} + +fun NavGraphBuilder.myJobsRoute(navController: NavHostController) { + composable { + MyJobsScreen() + } +} + +fun NavGraphBuilder.messagesRoute(navController: NavHostController) { + composable { + MessagesScreen() + } +} + +fun NavGraphBuilder.moreRoute(navController: NavHostController) { + composable { + MoreScreen() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/SetupRoutes.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/SetupRoutes.kt new file mode 100644 index 0000000..4b8ab53 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/SetupRoutes.kt @@ -0,0 +1,49 @@ +package org.example.project.presentation.navigation.routes + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import org.example.project.domain.entity.UserType +import org.example.project.presentation.navigation.CraftsmanSetupDestination +import org.example.project.presentation.navigation.CustomerSetupDestination +import org.example.project.presentation.navigation.NavigationBarDestinations +import org.example.project.presentation.screens.setup.craftsmansetup.CraftsmanSetupScreen +import org.example.project.presentation.screens.setup.customersetup.CustomerSetupScreen + +fun NavGraphBuilder.craftsmanSetupRoute( + navController: NavHostController, + onUserTypeUpdated: (UserType) -> Unit +) { + composable { + CraftsmanSetupScreen( + onComplete = { + onUserTypeUpdated(UserType.CRAFTSMAN) + navController.navigate(NavigationBarDestinations.HomeScreen) { + popUpTo(0) { inclusive = true } + } + }, + onClose = { + navController.popBackStack() + } + ) + } +} + +fun NavGraphBuilder.customerSetupRoute( + navController: NavHostController, + onUserTypeUpdated: (UserType) -> Unit +) { + composable { + CustomerSetupScreen( + onComplete = { + onUserTypeUpdated(UserType.CUSTOMER) + navController.navigate(NavigationBarDestinations.HomeScreen) { + popUpTo(0) { inclusive = true } + } + }, + onClose = { + navController.popBackStack() + } + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionEffect.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionEffect.kt new file mode 100644 index 0000000..5f3938d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionEffect.kt @@ -0,0 +1,7 @@ +package org.example.project.presentation.screens.auth + +import org.example.project.domain.entity.UserType + +sealed interface UserTypeSelectionEffect { + data class NavigateToSetup(val userType: UserType) : UserTypeSelectionEffect +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionInteractionListener.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionInteractionListener.kt new file mode 100644 index 0000000..c702bb0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionInteractionListener.kt @@ -0,0 +1,8 @@ +package org.example.project.presentation.screens.auth + +import org.example.project.domain.entity.UserType + +interface UserTypeSelectionInteractionListener { + fun onUserTypeSelected(userType: UserType) + fun onContinueClick() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionScreen.kt new file mode 100644 index 0000000..c8093d6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionScreen.kt @@ -0,0 +1,129 @@ +package org.example.project.presentation.screens.auth + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Snackbar +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import crafto.composeapp.generated.resources.Res +import crafto.composeapp.generated.resources.continue_button +import crafto.composeapp.generated.resources.registration_step_1_description +import crafto.composeapp.generated.resources.user_type +import org.example.project.domain.entity.UserType +import org.example.project.presentation.designsystem.components.ButtonState +import org.example.project.presentation.designsystem.components.TextButton +import org.example.project.presentation.designsystem.textstyle.AppTheme +import org.example.project.presentation.screens.setup.composable.SetupScreenScaffold +import org.example.project.presentation.screens.setup.composable.page.UserTypeSelectionPage +import org.example.project.presentation.shared.base.ErrorUiState +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun UserTypeSelectionScreen( + viewModel: UserTypeSelectionViewModel = koinViewModel(), + onNavigateToSetup: (UserType) -> Unit, +) { + val state by viewModel.state.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.effect.collect { effect -> + when (effect) { + is UserTypeSelectionEffect.NavigateToSetup -> { + onNavigateToSetup(effect.userType) + } + } + } + } + + Box(modifier = Modifier.fillMaxSize()) { + UserTypeSelectionContent( + state = state, + viewModel = viewModel, + ) + + AnimatedVisibility( + visible = state.error != null, + enter = slideInVertically { it } + fadeIn(), + exit = slideOutVertically { it } + fadeOut(), + modifier = Modifier.align(Alignment.BottomCenter) + ) { + state.error?.let { error -> + ErrorSnackbar( + error = error, + onDismiss = viewModel::clearError, + modifier = Modifier.padding(16.dp) + ) + } + } + } +} + +@Composable +private fun UserTypeSelectionContent( + state: UserTypeSelectionUiState, + viewModel: UserTypeSelectionInteractionListener, +) { + SetupScreenScaffold( + currentPageNumber = 1, + totalPages = 1, + title = stringResource(Res.string.user_type), + description = stringResource(Res.string.registration_step_1_description), + nextButtonText = stringResource(Res.string.continue_button), + nextButtonEnabled = state.selectedType != null && !state.isLoading, + nextButtonState = when { + state.isLoading -> ButtonState.LOADING + state.selectedType != null -> ButtonState.Enable + else -> ButtonState.DISABLED + }, + showBackButton = false, + onNextButtonClick = viewModel::onContinueClick + ) { + UserTypeSelectionPage( + selectedType = state.selectedType, + onTypeSelected = viewModel::onUserTypeSelected, + modifier = Modifier.fillMaxSize() + ) + } +} + +@Composable +private fun ErrorSnackbar( + error: ErrorUiState, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + Snackbar( + modifier = modifier, + shape = RoundedCornerShape(8.dp), + containerColor = AppTheme.craftoColors.additional.primaryRed.copy(alpha = 0.95f), + contentColor = AppTheme.craftoColors.button.onPrimary, + action = { + TextButton( + onClick = onDismiss, + text = "Dismiss", + enabled = true, + buttonState = ButtonState.Enable + ) + } + ) { + Text( + text = error.message, + style = AppTheme.textStyle.body.medium, + color = AppTheme.craftoColors.button.onPrimary + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionUiState.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionUiState.kt new file mode 100644 index 0000000..8434970 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionUiState.kt @@ -0,0 +1,11 @@ +package org.example.project.presentation.screens.auth + +import org.example.project.domain.entity.UserType +import org.example.project.presentation.shared.base.BaseScreenState +import org.example.project.presentation.shared.base.ErrorUiState + +data class UserTypeSelectionUiState( + override val isLoading: Boolean = false, + override val error: ErrorUiState? = null, + val selectedType: UserType? = null +) : BaseScreenState diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionViewModel.kt new file mode 100644 index 0000000..0b5b09d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/auth/UserTypeSelectionViewModel.kt @@ -0,0 +1,57 @@ +package org.example.project.presentation.screens.auth + +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import org.example.project.domain.entity.UserType +import org.example.project.domain.usecase.session.SaveUserTypeUseCase +import org.example.project.presentation.shared.base.BaseViewModel +import org.example.project.presentation.shared.base.ErrorUiState + +class UserTypeSelectionViewModel( + private val saveUserTypeUseCase: SaveUserTypeUseCase +) : BaseViewModel( + UserTypeSelectionUiState() +), UserTypeSelectionInteractionListener { + + init { + viewModelScope.launch { + isLoading.collect { loading -> + updateState { it.copy(isLoading = loading) } + } + } + } + + + override fun onUserTypeSelected(userType: UserType) { + updateState { it.copy(selectedType = userType) } + } + + override fun onContinueClick() { + val selectedType = state.value.selectedType + + if (selectedType == null) { + updateState { + it.copy(error = ErrorUiState("Please select a user type")) + } + return + } + + tryToCall( + call = { + saveUserTypeUseCase(selectedType) + selectedType + }, + onSuccess = { type -> + sendNewEffect(UserTypeSelectionEffect.NavigateToSetup(type)) + }, + onError = { error -> + updateState { it.copy(error = error) } + }, + showLoading = true + ) + } + + fun clearError() { + updateState { it.copy(error = null) } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt new file mode 100644 index 0000000..cde5172 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt @@ -0,0 +1,29 @@ +package org.example.project.presentation.screens.home + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.example.project.presentation.designsystem.textstyle.AppTheme + +@Composable +fun HomeScreen( + +) { + Box( + modifier = Modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Home Screen", style = AppTheme.textStyle.title.large, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt new file mode 100644 index 0000000..11da3fb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt @@ -0,0 +1,29 @@ +package org.example.project.presentation.screens.messages + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.example.project.presentation.designsystem.textstyle.AppTheme + +@Composable +fun MessagesScreen( + +) { + Box( + modifier = Modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Messages Screen", style = AppTheme.textStyle.title.large, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt new file mode 100644 index 0000000..ac7ac2a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt @@ -0,0 +1,29 @@ +package org.example.project.presentation.screens.more + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.example.project.presentation.designsystem.textstyle.AppTheme + +@Composable +fun MoreScreen( + +) { + Box( + modifier = Modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("More Screen", style = AppTheme.textStyle.title.large, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt new file mode 100644 index 0000000..d6314c9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt @@ -0,0 +1,29 @@ +package org.example.project.presentation.screens.myjobs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.example.project.presentation.designsystem.textstyle.AppTheme + +@Composable +fun MyJobsScreen( + +) { + Box( + modifier = Modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("My Jobs Screen", style = AppTheme.textStyle.title.large, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt new file mode 100644 index 0000000..2a52c98 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt @@ -0,0 +1,29 @@ +package org.example.project.presentation.screens.myrequests + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.example.project.presentation.designsystem.textstyle.AppTheme + +@Composable +fun MyRequestsScreen( + +) { + Box( + modifier = Modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("My Requests Screen", style = AppTheme.textStyle.title.large, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingScreen.kt index 957729d..d96c4c5 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope @@ -38,7 +39,6 @@ import org.example.project.presentation.designsystem.components.SecondaryButton import org.example.project.presentation.designsystem.textstyle.AppTheme import org.example.project.presentation.screens.onboarding.composable.OnboardingIndicator import org.example.project.presentation.screens.onboarding.composable.OnboardingItem -import org.example.project.presentation.screens.onboarding.OnboardingViewModel import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -46,10 +46,23 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun OnboardingScreen( - viewModel: OnboardingViewModel = koinViewModel() - + viewModel: OnboardingViewModel = koinViewModel(), + onNavigateToOtp: () -> Unit ) { val state by viewModel.state.collectAsState() + LaunchedEffect(Unit) { + viewModel.effect.collect { effect -> + when (effect) { + OnboardingScreenEffect.NavigateToGetStartedScreen, + OnboardingScreenEffect.NavigateToRegisterScreen -> { + onNavigateToOtp() + } + OnboardingScreenEffect.NavigateToNext -> { + // Internal navigation handled by pager + } + } + } + } OnboardingContent( state = state, interactions = viewModel @@ -98,7 +111,8 @@ private fun OnboardingContent( ) ) } - interactions::onSkipClick +// interactions::onSkipClick + interactions.onSkipClick() }, buttonState = ButtonState.Enable, containerColor = AppTheme.craftoColors.button.secondary, @@ -165,7 +179,8 @@ private fun OnboardingActionsRow( ) } } else { - interactions::onGetStartedClick + //interactions::onGetStartedClick + interactions.onGetStartedClick() } }, buttonState = ButtonState.Enable, diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingViewModel.kt index f22d328..e76292a 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/onboarding/OnboardingViewModel.kt @@ -5,14 +5,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import org.example.project.domain.entity.OnboardingItem import org.example.project.domain.repository.OnboardingRepository +import org.example.project.domain.usecase.session.MarkOnboardingCompleteUseCase import org.example.project.presentation.screens.onboarding.model.toUiState import org.example.project.presentation.shared.base.BaseViewModel -import org.koin.android.annotation.KoinViewModel -import org.koin.core.annotation.Provided -@KoinViewModel + class OnboardingViewModel( - @Provided private val repository: OnboardingRepository, + private val repository: OnboardingRepository, + private val markOnboardingCompleteUseCase: MarkOnboardingCompleteUseCase, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : BaseViewModel(initialState = OnboardingScreenState()), @@ -22,16 +22,17 @@ class OnboardingViewModel( loadData() } - private fun loadData() { tryToCall( call = { updateState { it.copy(loading = true) } repository.getOnboardingData() }, - onSuccess = ::onLoadDataSuccess , - onError = { errorState -> updateState { it.copy(errorMessage = errorState) } }, - dispatcher = ioDispatcher + onSuccess = ::onLoadDataSuccess, + onError = { errorState -> + updateState { it.copy(errorMessage = errorState, loading = false) } + }, + dispatcher = ioDispatcher, ) } @@ -45,6 +46,7 @@ class OnboardingViewModel( } override fun onSkipClick() { + markOnboardingComplete() sendNewEffect(OnboardingScreenEffect.NavigateToGetStartedScreen) } @@ -53,6 +55,22 @@ class OnboardingViewModel( } override fun onGetStartedClick() { + markOnboardingComplete() sendNewEffect(OnboardingScreenEffect.NavigateToRegisterScreen) } + + private fun markOnboardingComplete() { + tryToCall( + call = { + markOnboardingCompleteUseCase() + }, + onSuccess = { + // Do nothing, just continue navigation + }, + onError = { error -> + updateState { it.copy(loading = false) } + println("⚠️ Failed to mark onboarding complete: ${error.message}") + }, + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt index d319231..7d229e1 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt @@ -39,17 +39,17 @@ import org.example.project.presentation.designsystem.components.TextField import org.example.project.presentation.designsystem.textstyle.AppTheme import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.ui.tooling.preview.Preview @Composable fun RegisterScreen( modifier: Modifier = Modifier, + onRegistrationComplete: () -> Unit ) { RegisterContent( modifier = modifier, onPrivacyPolicyClick = {}, onTermsClick = {}, - onButtonClick = {} + onButtonClick = { onRegistrationComplete() } ) } @@ -176,10 +176,10 @@ private fun PrivacyAndTextSection( } } -@Preview -@Composable -private fun RegisterScreenPreview() { - AppTheme { - RegisterScreen() - } -} \ No newline at end of file +//@Preview +//@Composable +//private fun RegisterScreenPreview() { +// AppTheme { +// RegisterScreen() +// } +//} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/SetupScreenScaffold.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/SetupScreenScaffold.kt index 04994fa..dfadc50 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/SetupScreenScaffold.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/SetupScreenScaffold.kt @@ -31,10 +31,11 @@ fun SetupScreenScaffold( totalPages: Int = 4, nextButtonText: String = stringResource(Res.string.next), nextButtonEnabled: Boolean = true, + showBackButton: Boolean= true, nextButtonState: ButtonState = ButtonState.Enable, title: String, description: String, - onBackButtonClick: () -> Unit, + onBackButtonClick: () -> Unit = {}, onNextButtonClick: () -> Unit, content: @Composable (() -> Unit) ) { @@ -52,6 +53,7 @@ fun SetupScreenScaffold( totalPages = totalPages, nextButtonText = nextButtonText, nextButtonEnabled = nextButtonEnabled, + showBackButton = showBackButton, nextButtonState = nextButtonState, description = description, onBackButtonClick = onBackButtonClick, @@ -68,6 +70,7 @@ fun SetupScreenScaffold( totalPages = totalPages, nextButtonText = nextButtonText, nextButtonEnabled = nextButtonEnabled, + showBackButton = showBackButton, nextButtonState = nextButtonState, title = title, description = description, @@ -90,6 +93,7 @@ private fun PortraitLayout( nextButtonText: String, nextButtonEnabled: Boolean, nextButtonState: ButtonState, + showBackButton: Boolean = true, description: String, onBackButtonClick: () -> Unit, onNextButtonClick: () -> Unit, @@ -107,7 +111,8 @@ private fun PortraitLayout( AccountSetupTopBar( onBackButtonClick = onBackButtonClick, currentPage = currentPageNumber, - totalPages = totalPages ) + totalPages = totalPages, + showBackButton = showBackButton) TitleDescriptionBox( modifier = Modifier.weight(1f), @@ -141,6 +146,7 @@ private fun LandscapeLayout( nextButtonText: String, nextButtonEnabled: Boolean, nextButtonState: ButtonState, + showBackButton: Boolean, title: String, description: String, onBackButtonClick: () -> Unit, @@ -159,7 +165,8 @@ private fun LandscapeLayout( AccountSetupTopBar( onBackButtonClick = onBackButtonClick, currentPage = currentPageNumber, - totalPages = totalPages) + totalPages = totalPages, + showBackButton = showBackButton) Row( modifier = Modifier .fillMaxWidth() diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/page/UserTypeSelectionPage.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/page/UserTypeSelectionPage.kt index 62a8677..a2f01e4 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/page/UserTypeSelectionPage.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/composable/page/UserTypeSelectionPage.kt @@ -13,18 +13,19 @@ import crafto.composeapp.generated.resources.customer import crafto.composeapp.generated.resources.customer_description import crafto.composeapp.generated.resources.selection_craftsman import crafto.composeapp.generated.resources.selection_customer +import org.example.project.domain.entity.UserType import org.example.project.presentation.designsystem.components.SelectionCard -import org.example.project.presentation.screens.setup.craftsmansetup.UserType import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @Composable fun UserTypeSelectionPage( selectedType: UserType?, - onTypeSelected: (UserType) -> Unit + onTypeSelected: (UserType) -> Unit, + modifier: Modifier=Modifier ) { Row( - modifier = Modifier + modifier = modifier .fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupInteractionListener.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupInteractionListener.kt index bc33d78..20eebc5 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupInteractionListener.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupInteractionListener.kt @@ -5,8 +5,6 @@ import org.example.project.presentation.model.PersonalInfoUiModel import org.example.project.presentation.shared.base.ErrorUiState interface CraftsmanSetupInteractionListener { - fun onUserTypeSelected(userType: UserType) - fun onCategoryToggled(categoryId: Int) fun onPersonalInfoChanged(personalInfo: PersonalInfoUiModel) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt index d9d4369..edc6a46 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt @@ -28,16 +28,13 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import crafto.composeapp.generated.resources.Res import crafto.composeapp.generated.resources.identity_verification -import crafto.composeapp.generated.resources.location_hint import crafto.composeapp.generated.resources.personal_info import crafto.composeapp.generated.resources.portfolio_upload -import crafto.composeapp.generated.resources.registration_step_1_description import crafto.composeapp.generated.resources.registration_step_2_description import crafto.composeapp.generated.resources.registration_step_3_description import crafto.composeapp.generated.resources.registration_step_4_description import crafto.composeapp.generated.resources.registration_step_5_description import crafto.composeapp.generated.resources.service_selection -import crafto.composeapp.generated.resources.user_type import org.example.project.presentation.designsystem.components.ButtonState import org.example.project.presentation.designsystem.components.TextButton import org.example.project.presentation.designsystem.textstyle.AppTheme @@ -46,7 +43,6 @@ import org.example.project.presentation.screens.setup.composable.page.IdentityVe import org.example.project.presentation.screens.setup.composable.page.PersonalInfoPage import org.example.project.presentation.screens.setup.composable.page.PortfolioUploadPage import org.example.project.presentation.screens.setup.composable.page.ServiceSelectionPage -import org.example.project.presentation.screens.setup.composable.page.UserTypeSelectionPage import org.example.project.presentation.shared.base.ErrorUiState import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel @@ -144,8 +140,6 @@ fun CraftsmanSetupContent( } }, title = when (state.currentStep) { - RegistrationStep.USER_TYPE -> { - stringResource(Res.string.user_type) } RegistrationStep.SERVICE_SELECTION -> { stringResource(Res.string.service_selection) } @@ -160,9 +154,6 @@ fun CraftsmanSetupContent( } }, description =when (state.currentStep) { - RegistrationStep.USER_TYPE -> { - stringResource(Res.string.registration_step_1_description) - } RegistrationStep.SERVICE_SELECTION -> { stringResource(Res.string.registration_step_2_description) } @@ -187,12 +178,6 @@ fun CraftsmanSetupContent( userScrollEnabled = state.isSwipeEnabled && state.canNavigateNext ) { page -> when (RegistrationStep.fromIndex(page)) { - RegistrationStep.USER_TYPE -> { - UserTypeSelectionPage( - selectedType = state.userType, - onTypeSelected = viewModel::onUserTypeSelected, - ) - } RegistrationStep.SERVICE_SELECTION -> { ServiceSelectionPage( availableCategories = state.availableCategories, diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt index ac71fa9..532ed83 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt @@ -12,7 +12,7 @@ data class CraftsmanSetupUiState( override val error: ErrorUiState? = null, val currentPageIndex: Int = 0, - val totalPages: Int = 5, + val totalPages: Int = 4, val canNavigateNext: Boolean = false, val canNavigateBack: Boolean = false, val isSwipeEnabled: Boolean = true, @@ -23,8 +23,6 @@ data class CraftsmanSetupUiState( val uploadedPortfolioUrls: List = emptyList(), val verificationDocuments: VerificationDocuments? = null, - val userType: UserType? = null, - val availableCategories: List = emptyList(), val selectedCategoryIds: Set = emptySet(), @@ -48,12 +46,12 @@ data class CraftsmanSetupUiState( val currentStep: RegistrationStep get() = RegistrationStep.fromIndex(currentPageIndex) + //TODO: Use progress calculation val progress: Float get() = (currentPageIndex + 1) / totalPages.toFloat() val nextButtonText: String get() = when (currentStep) { - RegistrationStep.USER_TYPE -> "Next" RegistrationStep.SERVICE_SELECTION -> "Next" RegistrationStep.PERSONAL_INFO -> if (isLoading) "Creating Profile..." else "Next" RegistrationStep.PORTFOLIO_UPLOAD -> "Next" @@ -62,19 +60,13 @@ data class CraftsmanSetupUiState( } enum class RegistrationStep(val index: Int) { - USER_TYPE(0), - SERVICE_SELECTION(1), - PERSONAL_INFO(2), - PORTFOLIO_UPLOAD(3), - IDENTITY_VERIFICATION(4); + SERVICE_SELECTION(0), + PERSONAL_INFO(1), + PORTFOLIO_UPLOAD(2), + IDENTITY_VERIFICATION(3); companion object { fun fromIndex(index: Int): RegistrationStep = - entries.firstOrNull { it.index == index } ?: USER_TYPE + entries.firstOrNull { it.index == index } ?: SERVICE_SELECTION } -} - -enum class UserType { - CUSTOMER, - CRAFTSMAN } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt index 35e6f2b..216a847 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt @@ -27,22 +27,6 @@ class CraftsmanSetupViewModel( CraftsmanSetupUiState() ), CraftsmanSetupInteractionListener { - override fun onUserTypeSelected(userType: UserType) { - when (userType) { - UserType.CRAFTSMAN -> { - updateState { - it.copy( - userType = userType, - canNavigateNext = true - ) - } - } - UserType.CUSTOMER -> { - TODO("Implement Customer setup flow – redirect to CustomerSetupScreen when ready") - } - } - } - init { validateCurrentPage() fetchCategories() @@ -392,11 +376,10 @@ class CraftsmanSetupViewModel( private fun validateCurrentPage() { val canProceed = when (state.value.currentStep) { - RegistrationStep.USER_TYPE -> state.value.userType != null RegistrationStep.SERVICE_SELECTION -> state.value.selectedCategoryIds.isNotEmpty() RegistrationStep.PERSONAL_INFO -> validatePersonalInfo(state.value.personalInfo) RegistrationStep.PORTFOLIO_UPLOAD -> state.value.portfolioImages.isNotEmpty() - RegistrationStep.IDENTITY_VERIFICATION -> true // Optional step + RegistrationStep.IDENTITY_VERIFICATION -> true } updateState { it.copy(canNavigateNext = canProceed) } diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/CustomerSetupScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/CustomerSetupScreen.kt new file mode 100644 index 0000000..a3e9134 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/CustomerSetupScreen.kt @@ -0,0 +1,12 @@ +package org.example.project.presentation.screens.setup.customersetup + +import androidx.compose.runtime.Composable + + +@Composable +fun CustomerSetupScreen( + onComplete: () -> Unit={}, + onClose: () -> Unit={} +) { + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/init b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/init deleted file mode 100644 index 6a8de1b..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/customersetup/init +++ /dev/null @@ -1 +0,0 @@ -TODO() \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eee6826..11934f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -adaptive = "1.2.0-alpha07" +adaptive = "1.2.0" agp = "8.13.0" android-compileSdk = "36" android-minSdk = "24" @@ -8,19 +8,19 @@ androidx-activity = "1.11.0" androidx-appcompat = "1.7.1" androidx-core = "1.17.0" androidx-espresso = "3.7.0" -androidx-lifecycle = "2.9.4" +androidx-lifecycle = "2.9.6" androidx-testExt = "1.3.0" -calfFilePicker = "0.8.0" +calfFilePicker = "0.9.0" coilComposeVersion = "3.3.0" -composeMultiplatform = "1.9.0" +composeMultiplatform = "1.9.3" datastorePreferences = "1.1.7" junit = "4.13.2" -kotlin = "2.2.20" +kotlin = "2.2.21" ksp = "2.2.20-2.0.4" koinTest = "4.1.1" koin = "4.1.1" -koinAnnotations = "2.2.0" -ktor = "3.3.1" +koinAnnotations = "2.3.1" +ktor = "3.3.2" coil = "3.3.0" koinComposeMultiplatform = "4.1.1" kotlinxCoroutinesTest = "1.10.2" @@ -31,8 +31,9 @@ google-services = "4.4.4" #newer version of firebase-bom causes gradle errors firebase-bom = "33.16.0" firebaseCrashlytics = "3.0.6" -ktorClientCio = "3.3.1" -ktorClientMock = "3.3.1" +ktorClientCio = "3.3.2" +ktorClientMock = "3.3.2" +navigationCompose = "2.9.1" [libraries] @@ -85,6 +86,7 @@ coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } +navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } From a58fb3586a8b09718e03d241b96f0d765cf85e20 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sat, 15 Nov 2025 22:49:47 +0200 Subject: [PATCH 2/9] feat: enhance navigation system with improved layout and user experience --- .../kotlin/org/example/project/App.kt | 11 +++++- .../presentation/navigation/CraftoNavGraph.kt | 37 +++++++------------ .../presentation/navigation/Destination.kt | 14 +------ .../navigation/routes/AuthRoutes.kt | 19 ++-------- .../presentation/screens/home/HomeScreen.kt | 4 +- .../screens/messages/MessagesScreen.kt | 4 +- .../presentation/screens/more/MoreScreen.kt | 4 +- .../screens/myjobs/MyJobsScreen.kt | 4 +- .../screens/myrequests/MyRequestsScreen.kt | 4 +- .../craftsmansetup/CraftsmanSetupScreen.kt | 8 ++-- .../craftsmansetup/CraftsmanSetupUiState.kt | 5 +-- .../craftsmansetup/CraftsmanSetupViewModel.kt | 15 +++++--- 12 files changed, 58 insertions(+), 71 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/App.kt b/composeApp/src/commonMain/kotlin/org/example/project/App.kt index 3f8f0ed..50a0f78 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/App.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/App.kt @@ -1,6 +1,10 @@ package org.example.project +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController import org.example.project.presentation.designsystem.textstyle.AppTheme import org.example.project.presentation.navigation.CraftoNavGraph @@ -11,6 +15,11 @@ import org.jetbrains.compose.ui.tooling.preview.Preview fun App() { AppTheme { val navController = rememberNavController() - CraftoNavGraph(navController = navController) + CraftoNavGraph( + navController = navController, + modifier = Modifier + .fillMaxSize() + .background(AppTheme.craftoColors.background.screen) + .navigationBarsPadding()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt index c393920..0ff8180 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt @@ -2,13 +2,8 @@ package org.example.project.presentation.navigation import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Scaffold import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController @@ -32,7 +27,6 @@ fun CraftoNavGraph( val coroutineScope = rememberCoroutineScope() - // Determine start destination on first load LaunchedEffect(Unit) { coroutineScope.launch { try { @@ -41,7 +35,7 @@ fun CraftoNavGraph( startDestination = when { session.isFirstTime -> { - SplashDestination // Will navigate to Onboarding + OnboardingDestination } session.userId != null && session.userType != null -> { NavigationBarDestinations.HomeScreen @@ -50,28 +44,27 @@ fun CraftoNavGraph( UserTypeSelectionDestination } else -> { - SplashDestination // Will navigate to OTP + OtpRegistrationDestination } } } catch (e: Exception) { println("Error loading session: ${e.message}") - startDestination = SplashDestination + startDestination = OnboardingDestination } finally { isLoading = false } } } - // Show loading while checking session - if (isLoading || startDestination == null) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - return - } +// if (isLoading || startDestination == null) { +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// CraftoCircularProgressIndicator() +// } +// return +// } Scaffold( modifier = modifier, @@ -89,10 +82,7 @@ fun CraftoNavGraph( ) { innerPadding -> NavHost( navController = navController, - startDestination = startDestination!!, - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), + startDestination = startDestination?: OnboardingDestination, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { @@ -131,7 +121,6 @@ fun NavGraphBuilder.authNavigationGraph( navController: NavHostController, onUserTypeUpdated: (UserType) -> Unit ) { - splashRoute(navController) onboardingRoute(navController) otpRegistrationRoute(navController) userTypeSelectionRoute(navController, onUserTypeUpdated) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt index bc7d07e..6f9dfd4 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt @@ -29,9 +29,6 @@ val bottomNavBarDestinationsMap = mapOf( //SettingsDetailDestination::class.qualifiedName to NavigationBarDestinations.MoreScreen // Keep bottom nav selected ) -// ===== Auth Flow Destinations ===== -@Serializable -data object SplashDestination @Serializable data object OnboardingDestination @@ -42,7 +39,6 @@ data object OtpRegistrationDestination @Serializable data object UserTypeSelectionDestination -// ===== Setup Destinations ===== @Serializable data object CraftsmanSetupDestination @@ -50,12 +46,4 @@ data object CraftsmanSetupDestination data object CustomerSetupDestination //@Serializable -//data class SettingsDetailDestination(val settingType: String) - -// ============= MAIN APP (With Bottom Nav) ============= -@Serializable -data object MainRoute // Parent for bottom nav - -// ============= INITIAL ROUTE ============= -@Serializable -data object SplashRoute \ No newline at end of file +//data class SettingsDetailDestination(val settingType: String) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt index e6e96de..ce3e31a 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/routes/AuthRoutes.kt @@ -8,24 +8,11 @@ import org.example.project.presentation.navigation.CraftsmanSetupDestination import org.example.project.presentation.navigation.CustomerSetupDestination import org.example.project.presentation.navigation.OnboardingDestination import org.example.project.presentation.navigation.OtpRegistrationDestination -import org.example.project.presentation.navigation.SplashDestination import org.example.project.presentation.navigation.UserTypeSelectionDestination import org.example.project.presentation.screens.auth.UserTypeSelectionScreen import org.example.project.presentation.screens.onboarding.OnboardingScreen import org.example.project.presentation.screens.register.RegisterScreen -import org.example.project.presentation.screens.splash.SplashScreen -fun NavGraphBuilder.splashRoute(navController: NavHostController) { - composable { - SplashScreen( - onTimeout = { - navController.navigate(OnboardingDestination) { - popUpTo(SplashDestination) { inclusive = true } - } - } - ) - } -} fun NavGraphBuilder.onboardingRoute(navController: NavHostController) { composable { @@ -63,12 +50,14 @@ fun NavGraphBuilder.userTypeSelectionRoute( when (userType) { UserType.CRAFTSMAN -> { navController.navigate(CraftsmanSetupDestination) { - popUpTo(UserTypeSelectionDestination) { inclusive = true } + //popUpTo(UserTypeSelectionDestination) { inclusive = true } + launchSingleTop = true } } UserType.CUSTOMER -> { navController.navigate(CustomerSetupDestination) { - popUpTo(UserTypeSelectionDestination) { inclusive = true } + //popUpTo(UserTypeSelectionDestination) { inclusive = true } + launchSingleTop = true } } } diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt index cde5172..10a11fa 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/home/HomeScreen.kt @@ -1,6 +1,7 @@ package org.example.project.presentation.screens.home import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -19,7 +20,8 @@ fun HomeScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text("Home Screen", style = AppTheme.textStyle.title.large, textAlign = androidx.compose.ui.text.style.TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt index 11da3fb..2c00987 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/messages/MessagesScreen.kt @@ -1,6 +1,7 @@ package org.example.project.presentation.screens.messages import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -19,7 +20,8 @@ fun MessagesScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text("Messages Screen", style = AppTheme.textStyle.title.large, textAlign = androidx.compose.ui.text.style.TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt index ac7ac2a..c8ffeae 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/more/MoreScreen.kt @@ -1,6 +1,7 @@ package org.example.project.presentation.screens.more import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -19,7 +20,8 @@ fun MoreScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text("More Screen", style = AppTheme.textStyle.title.large, textAlign = androidx.compose.ui.text.style.TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt index d6314c9..7419a98 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myjobs/MyJobsScreen.kt @@ -1,6 +1,7 @@ package org.example.project.presentation.screens.myjobs import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -19,7 +20,8 @@ fun MyJobsScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text("My Jobs Screen", style = AppTheme.textStyle.title.large, textAlign = androidx.compose.ui.text.style.TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt index 2a52c98..0d54bfb 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/myrequests/MyRequestsScreen.kt @@ -1,6 +1,7 @@ package org.example.project.presentation.screens.myrequests import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -19,7 +20,8 @@ fun MyRequestsScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text("My Requests Screen", style = AppTheme.textStyle.title.large, textAlign = androidx.compose.ui.text.style.TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt index edc6a46..2faa672 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupScreen.kt @@ -130,11 +130,11 @@ fun CraftsmanSetupContent( onNextButtonClick = { when (state.currentStep) { RegistrationStep.IDENTITY_VERIFICATION -> { - if (state.idCardFront != null && state.idCardBack != null) { + if (state.hasUploadedIdCards) viewModel.onUploadIdCards() - } else { - viewModel.onSkipIdentityVerification() - } +// } else { +// viewModel.onSkipIdentityVerification() +// } } else -> viewModel.navigateNext() } diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt index 532ed83..83af7a4 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupUiState.kt @@ -46,9 +46,8 @@ data class CraftsmanSetupUiState( val currentStep: RegistrationStep get() = RegistrationStep.fromIndex(currentPageIndex) - //TODO: Use progress calculation - val progress: Float - get() = (currentPageIndex + 1) / totalPages.toFloat() + val hasUploadedIdCards: Boolean + get() = idCardFront != null && idCardBack != null val nextButtonText: String get() = when (currentStep) { diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt index 216a847..6c62473 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/setup/craftsmansetup/CraftsmanSetupViewModel.kt @@ -90,7 +90,7 @@ class CraftsmanSetupViewModel( updateState { it.copy(error = ErrorUiState("Please upload both ID card images")) } return } - updateState { it.copy(isSwipeEnabled = false) } + updateState { it.copy(isSwipeEnabled = false, isUploadingIdCards = true) } tryToCall( call = { @@ -103,14 +103,18 @@ class CraftsmanSetupViewModel( ) }, onSuccess = { verificationDocs -> - updateState { it.copy(isSwipeEnabled = true) } + updateState { it.copy( + isSwipeEnabled = true, + isUploadingIdCards = false, + verificationDocuments = verificationDocs) } sendNewEffect(CraftsmanRegistrationEffect.RegistrationComplete) }, onError = { error -> updateState { it.copy( error = error, - isSwipeEnabled = true + isSwipeEnabled = true, + isUploadingIdCards = false ) } }, @@ -119,8 +123,8 @@ class CraftsmanSetupViewModel( } override fun onSkipIdentityVerification() { - //sendNewEffect(CraftsmanRegistrationEffect.RegistrationComplete) - navigateNext() + sendNewEffect(CraftsmanRegistrationEffect.RegistrationComplete) + //navigateNext() } override fun onPortfolioImagesAdded(images: List) { @@ -333,7 +337,6 @@ class CraftsmanSetupViewModel( } if (currentIndex < state.value.totalPages - 1 && state.value.canNavigateNext) { - AppLogger.d("Navigation", "Navigating from page $currentIndex to ${currentIndex + 1}") updateState { it.copy(currentPageIndex = currentIndex + 1) } } } From b94a59f40609485cc52a185b9d8800f1daa707c0 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 12:16:42 +0200 Subject: [PATCH 3/9] feat: update navigation logic to ensure proper start destination handling --- .../project/presentation/navigation/BottomNavigation.kt | 5 +++-- .../project/presentation/navigation/CraftoNavGraph.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt index 3e0f469..cc8b35e 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -99,8 +99,9 @@ fun CraftoNavBar( fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route - - return bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> + val bottomNavBarDestinationsMap: NavigationBarDestinations?=bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> currentRoute?.startsWith(route ?: "") == true }?.value + + return bottomNavBarDestinationsMap } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt index 0ff8180..948110d 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/CraftoNavGraph.kt @@ -22,7 +22,7 @@ fun CraftoNavGraph( getUserSessionUseCase: GetUserSessionUseCase = koinInject() ) { var userType by remember { mutableStateOf(null) } - var startDestination by remember { mutableStateOf(null) } + var startDestination by remember { mutableStateOf(OnboardingDestination) } var isLoading by remember { mutableStateOf(true) } val coroutineScope = rememberCoroutineScope() @@ -82,7 +82,7 @@ fun CraftoNavGraph( ) { innerPadding -> NavHost( navController = navController, - startDestination = startDestination?: OnboardingDestination, + startDestination = startDestination, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { From 03618aca1ebe54c7fbcf56dd555a7b8f2393b564 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 12:24:02 +0200 Subject: [PATCH 4/9] feat: refine current navigation bar screen retrieval logic --- .../presentation/navigation/BottomNavigation.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt index cc8b35e..42140e6 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -99,9 +99,11 @@ fun CraftoNavBar( fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route - val bottomNavBarDestinationsMap: NavigationBarDestinations?=bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> - currentRoute?.startsWith(route ?: "") == true - }?.value - return bottomNavBarDestinationsMap + val matchedEntry: Map.Entry? = + bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> + currentRoute?.startsWith(route ?: "") == true + } + + return matchedEntry?.value as? NavigationBarDestinations } \ No newline at end of file From e32fd2fa566e1a420e204ec3bde71dbaa02da98b Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 12:39:05 +0200 Subject: [PATCH 5/9] feat: simplify current navigation bar screen retrieval logic --- .../presentation/navigation/BottomNavigation.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt index 42140e6..998d35e 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -99,11 +99,13 @@ fun CraftoNavBar( fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route - - val matchedEntry: Map.Entry? = - bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> - currentRoute?.startsWith(route ?: "") == true - } - - return matchedEntry?.value as? NavigationBarDestinations + return when { + currentRoute == null -> null + currentRoute.contains("HomeScreen") -> NavigationBarDestinations.HomeScreen + currentRoute.contains("MyRequestsScreen") -> NavigationBarDestinations.MyRequestsScreen + currentRoute.contains("MyJobsScreen") -> NavigationBarDestinations.MyJobsScreen + currentRoute.contains("MessagesScreen") -> NavigationBarDestinations.MessagesScreen + currentRoute.contains("MoreScreen") -> NavigationBarDestinations.MoreScreen + else -> null + } } \ No newline at end of file From fca690ee2e42baad07c84dbab3e5caa7426da892 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 12:53:32 +0200 Subject: [PATCH 6/9] feat: simplify current navigation bar screen retrieval logic --- .gitignore | 8 ++++---- .../presentation/navigation/BottomNavigation.kt | 14 +++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index a4228d2..612c24d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,9 @@ captures !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings -# Google Service -composeApp/google-service.json -**/google-service.json -google-service.json +# Google Services +composeApp/google-services.json +**/google-services.json +google-services.json # Project exclude paths \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt index 998d35e..ee90205 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -99,13 +99,9 @@ fun CraftoNavBar( fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route - return when { - currentRoute == null -> null - currentRoute.contains("HomeScreen") -> NavigationBarDestinations.HomeScreen - currentRoute.contains("MyRequestsScreen") -> NavigationBarDestinations.MyRequestsScreen - currentRoute.contains("MyJobsScreen") -> NavigationBarDestinations.MyJobsScreen - currentRoute.contains("MessagesScreen") -> NavigationBarDestinations.MessagesScreen - currentRoute.contains("MoreScreen") -> NavigationBarDestinations.MoreScreen - else -> null - } + val bottomNavBarDestinationsMap: NavigationBarDestinations?=bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> + currentRoute?.startsWith(route!!) == true + }?.value + + return bottomNavBarDestinationsMap } \ No newline at end of file From b8c90837fa39f2c31cb808de9e682190a1afb193 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 12:59:27 +0200 Subject: [PATCH 7/9] update gitignore file --- composeApp/google-services.json | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 composeApp/google-services.json diff --git a/composeApp/google-services.json b/composeApp/google-services.json deleted file mode 100644 index bc677ab..0000000 --- a/composeApp/google-services.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "project_info": { - "project_number": "798724632011", - "project_id": "crafto-33c46", - "storage_bucket": "crafto-33c46.firebasestorage.app" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:798724632011:android:c6bc14b43d5ed1ea4f1a4e", - "android_client_info": { - "package_name": "org.example.project" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyCAA3M9zscl98Nbl0aa3oEFwClOyAE8-4c" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file From 188eba2ed543d2fbb5455a5971b460f20f1601dd Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 15:18:57 +0200 Subject: [PATCH 8/9] feat: enhance navigation bar screen retrieval logic for better null safety --- .../presentation/navigation/BottomNavigation.kt | 12 ++++++------ .../project/presentation/navigation/Destination.kt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt index ee90205..b540d6b 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/BottomNavigation.kt @@ -98,10 +98,10 @@ fun CraftoNavBar( @Composable fun getCurrentNavBarScreen(navController: NavController): NavigationBarDestinations? { val backStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = backStackEntry?.destination?.route - val bottomNavBarDestinationsMap: NavigationBarDestinations?=bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> - currentRoute?.startsWith(route!!) == true - }?.value - - return bottomNavBarDestinationsMap + val currentRoute: String? = backStackEntry?.destination?.route + val matchedEntry: Map.Entry? = + bottomNavBarDestinationsMap.entries.firstOrNull { (route, _) -> + currentRoute != null && route != null && currentRoute.startsWith(route) + } + return matchedEntry?.value } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt index 6f9dfd4..7cdfaa8 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/navigation/Destination.kt @@ -20,7 +20,7 @@ sealed interface NavigationBarDestinations { data object MoreScreen : NavigationBarDestinations } -val bottomNavBarDestinationsMap = mapOf( +val bottomNavBarDestinationsMap: Map = mapOf( NavigationBarDestinations.HomeScreen::class.qualifiedName to NavigationBarDestinations.HomeScreen, NavigationBarDestinations.MyRequestsScreen::class.qualifiedName to NavigationBarDestinations.MyRequestsScreen, NavigationBarDestinations.MyJobsScreen::class.qualifiedName to NavigationBarDestinations.MyJobsScreen, From 54910fdfa820b98f7898e9d54ea07abb39fb5f41 Mon Sep 17 00:00:00 2001 From: Amr Ashraf Date: Sun, 16 Nov 2025 15:49:17 +0200 Subject: [PATCH 9/9] feat: enhance navigation bar screen retrieval logic for better null safety --- .gitignore | 6 +++--- composeApp/google-services.json | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 composeApp/google-services.json diff --git a/.gitignore b/.gitignore index 612c24d..d1429a8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,8 @@ captures **/xcshareddata/WorkspaceSettings.xcsettings # Google Services -composeApp/google-services.json -**/google-services.json -google-services.json +#composeApp/google-services.json +#**/google-services.json +#google-services.json # Project exclude paths \ No newline at end of file diff --git a/composeApp/google-services.json b/composeApp/google-services.json new file mode 100644 index 0000000..bc677ab --- /dev/null +++ b/composeApp/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "798724632011", + "project_id": "crafto-33c46", + "storage_bucket": "crafto-33c46.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:798724632011:android:c6bc14b43d5ed1ea4f1a4e", + "android_client_info": { + "package_name": "org.example.project" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCAA3M9zscl98Nbl0aa3oEFwClOyAE8-4c" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file