diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index c856674a..a01d2fa2 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -12,6 +12,8 @@ android { dependencies { implementation(projects.core.designsystem) + implementation(projects.domain) implementation(libs.compose.navigation) implementation(libs.hilt.navigation.compose) + implementation(libs.kotlinx.serialization.json) } diff --git a/core/common/src/main/java/com/yapp/common/navigation/JsonNavType.kt b/core/common/src/main/java/com/yapp/common/navigation/JsonNavType.kt deleted file mode 100644 index 4cc9ac3f..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/JsonNavType.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.yapp.common.navigation - -import android.os.Bundle -import androidx.navigation.NavType - -abstract class JsonNavType : NavType(isNullableAllowed = false) { - abstract fun fromJsonParse(value: String): T - abstract fun T.getJsonParse(): String - - override fun get(bundle: Bundle, key: String): T? = - bundle.getString(key)?.let { parseValue(it) } - - override fun parseValue(value: String): T = fromJsonParse(value) - - override fun put(bundle: Bundle, key: String, value: T) { - bundle.putString(key, value.getJsonParse()) - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt index e465ab6f..34ceed6f 100644 --- a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt +++ b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt @@ -1,63 +1,88 @@ package com.yapp.common.navigation +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.navigation.NavDestination import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.NavOptions import androidx.navigation.compose.rememberNavController -import androidx.navigation.navOptions -import com.yapp.common.navigation.destination.HomeDestination -import com.yapp.common.navigation.destination.SplashDestination -import com.yapp.common.navigation.destination.TopLevelDestination +import com.yapp.common.navigation.route.AlarmInteractionDestination +import com.yapp.common.navigation.route.FortuneBaseRoute +import com.yapp.common.navigation.route.FortuneDestination +import com.yapp.common.navigation.route.HomeBaseRoute +import com.yapp.common.navigation.route.HomeDestination +import com.yapp.common.navigation.route.OnboardingBaseRoute +import com.yapp.common.navigation.route.OnboardingDestination +import com.yapp.common.navigation.route.SettingBaseRoute +import com.yapp.common.navigation.route.SettingDestination +import com.yapp.common.navigation.route.SplashRoute +import com.yapp.common.navigation.route.WebViewRoute +import com.yapp.domain.model.Alarm class OrbitNavigator( val navController: NavHostController, ) { - val startDestination = SplashDestination.Route.route - - private val currentDestination: NavDestination? - @Composable get() = navController - .currentBackStackEntryAsState().value?.destination - - val currentTab: TopLevelDestination? - @Composable get() = currentDestination - ?.route - ?.let(TopLevelDestination.Companion::find) - - fun navigateTo(route: String, popUpTo: String? = null, inclusive: Boolean = false) { - navController.navigate(route) { - popUpTo?.let { - popUpTo(it) { this.inclusive = inclusive } - } - launchSingleTop = true - restoreState = true + val startDestination = SplashRoute + + fun navigateToOnboarding(navOptions: NavOptions? = null) { + navController.navigate(OnboardingBaseRoute, navOptions) + } + + fun navigateToOnboardingNextStep(currentStep: Int, navOptions: NavOptions? = null) { + val instance = OnboardingDestination.getNextRouteForStep(currentStep)?.objectInstance + if (instance != null) { + navController.navigate(instance, navOptions) + } else { + Log.e("Navigator", "Invalid route at step: $currentStep") } } - fun navigateBack() { - navController.popBackStack() + fun navigateToAddAlarm(navOptions: NavOptions? = null) { + navController.navigate(HomeDestination.AlarmAddEdit(-1), navOptions) } - fun navigateToTopLevelDestination(tab: TopLevelDestination) { - val navOptions = navOptions { - popUpTo(Routes.Home.ROUTE) { - saveState = true - } - launchSingleTop = true - restoreState = true - } + fun navigateToEditAlarm(alarmId: Long, navOptions: NavOptions? = null) { + navController.navigate(HomeDestination.AlarmAddEdit(alarmId), navOptions) + } - when (tab) { - TopLevelDestination.HOME -> navController.navigate(Routes.Home.ROUTE, navOptions) - TopLevelDestination.MYPAGE -> navController.navigate(Routes.MyPage.ROUTE, navOptions) - } + fun navigateToHome(navOptions: NavOptions? = null) { + navController.navigate(HomeBaseRoute, navOptions) + } + + fun navigateToAlarmAction(alarm: Alarm, navOptions: NavOptions? = null) { + navController.navigate(AlarmInteractionDestination.AlarmAction(alarm), navOptions) + } + + fun navigateToAlarmSnoozeTimer(alarm: Alarm, navOptions: NavOptions? = null) { + navController.navigate(AlarmInteractionDestination.AlarmSnoozeTimer(alarm), navOptions) + } + + fun navigateToFortune(navOptions: NavOptions? = null) { + navController.navigate(FortuneBaseRoute, navOptions) + } + + fun navigateToFortuneReward(navOptions: NavOptions? = null) { + navController.navigate(FortuneDestination.Reward, navOptions) + } + + fun navigateToSetting(navOptions: NavOptions? = null) { + navController.navigate(SettingBaseRoute, navOptions) + } + + fun navigateToEditProfile(navOptions: NavOptions? = null) { + navController.navigate(SettingDestination.EditProfile, navOptions) } - @Composable - fun shouldHaveNavigationBarsPadding(): Boolean { - val currentRoute = currentDestination?.route ?: return false - return currentRoute !in HomeDestination.Home.route + fun navigateToEditBirthDay(navOptions: NavOptions? = null) { + navController.navigate(SettingDestination.EditBirthday, navOptions) + } + + fun navigateToWebView(url: String, navOptions: NavOptions? = null) { + navController.navigate(WebViewRoute(url), navOptions) + } + + fun navigateBack() { + navController.popBackStack() } } diff --git a/core/common/src/main/java/com/yapp/common/navigation/Routes.kt b/core/common/src/main/java/com/yapp/common/navigation/Routes.kt deleted file mode 100644 index d8fcb620..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/Routes.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.yapp.common.navigation - -object Routes { - object Onboarding { - const val ROUTE = "onboarding_route" - const val EXPLAIN = "onboarding_explain" - const val ALARM_TIME_SELECTION = "onboarding_alarm_time_selection" - const val BIRTHDAY = "onboarding_birthday" - const val TIME_OF_BIRTH = "onboarding_time_of_birth" - const val NAME = "onboarding_name" - const val GENDER = "onboarding_gender" - const val ACCESS = "onboarding_access" - const val COMPLETE_FIRST = "onboarding_complete_first" - const val COMPLETE_SECOND = "onboarding_complete_second" - } - - object Home { - const val ROUTE = "home_route" - const val HOME = "home" - const val ALARM_ADD_EDIT = "alarm_add_edit" - } - - object AlarmInteraction { - const val ROUTE = "alarm_interaction_route" - const val ALARM_ACTION = "alarm_action" - const val ALARM_SNOOZE_TIMER = "alarm_snooze_timer" - } - - object MyPage { - const val ROUTE = "mypage_route" - const val MYPAGE = "mypage" - } - - object Mission { - const val ROUTE = "mission_route" - const val MISSION = "mission_main" - const val PROGRESS = "mission_progress" - } - - object Fortune { - const val ROUTE = "fortune_route" - const val FORTUNE = "fortune_main" - const val REWARD = "fortune_reward" - } - - object Setting { - const val ROUTE = "setting_route" - const val SETTING = "setting_main" - const val EDIT_PROFILE = "setting_edit_profile" - const val EDIT_BIRTHDAY = "setting_edit_birthday" - } - - object Splash { - const val ROUTE = "splash_route" - const val SPLASH = "splash" - } - - object WebView { - const val ROUTE = "webview_route" - const val WEBVIEW = "webview" - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/AlarmInteractionDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/AlarmInteractionDestination.kt deleted file mode 100644 index 47b8d731..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/AlarmInteractionDestination.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class AlarmInteractionDestination(val route: String) { - data object Route : AlarmInteractionDestination(Routes.AlarmInteraction.ROUTE) - data object AlarmAction : HomeDestination(Routes.AlarmInteraction.ALARM_ACTION) - data object AlarmSnoozeTimer : HomeDestination(Routes.AlarmInteraction.ALARM_SNOOZE_TIMER) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/FortuneDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/FortuneDestination.kt deleted file mode 100644 index 808c3f62..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/FortuneDestination.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class FortuneDestination(val route: String) { - data object Route : FortuneDestination(Routes.Fortune.ROUTE) - data object Fortune : FortuneDestination(Routes.Fortune.FORTUNE) - data object Reward : FortuneDestination(Routes.Fortune.REWARD) - - companion object { - val routes = listOf(Fortune, Reward) - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/HomeDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/HomeDestination.kt deleted file mode 100644 index 81a71aed..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/HomeDestination.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class HomeDestination(val route: String) { - data object Route : HomeDestination(Routes.Home.ROUTE) - data object Home : HomeDestination(Routes.Home.HOME) - data object AlarmAddEdit : HomeDestination(Routes.Home.ALARM_ADD_EDIT) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/MissionDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/MissionDestination.kt deleted file mode 100644 index 3366da3d..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/MissionDestination.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class MissionDestination(val route: String) { - data object Route : MissionDestination(Routes.Mission.ROUTE) - data object Mission : MissionDestination(Routes.Mission.MISSION) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/MyPageDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/MyPageDestination.kt deleted file mode 100644 index 9ccb7d9c..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/MyPageDestination.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class MyPageDestination(val route: String) { - data object Route : MyPageDestination(Routes.MyPage.ROUTE) - data object MyPage : MyPageDestination(Routes.MyPage.MYPAGE) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/OnboardingDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/OnboardingDestination.kt deleted file mode 100644 index 89c4afe8..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/OnboardingDestination.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class OnboardingDestination(val route: String) { - data object Route : OnboardingDestination(Routes.Onboarding.ROUTE) - data object Explain : OnboardingDestination(Routes.Onboarding.EXPLAIN) - data object AlarmTimeSelection : OnboardingDestination(Routes.Onboarding.ALARM_TIME_SELECTION) - data object Birthday : OnboardingDestination(Routes.Onboarding.BIRTHDAY) - data object TimeOfBirth : OnboardingDestination(Routes.Onboarding.TIME_OF_BIRTH) - data object Name : OnboardingDestination(Routes.Onboarding.NAME) - data object Gender : OnboardingDestination(Routes.Onboarding.GENDER) - data object Access : OnboardingDestination(Routes.Onboarding.ACCESS) - data object Complete1 : OnboardingDestination(Routes.Onboarding.COMPLETE_FIRST) - data object Complete2 : OnboardingDestination(Routes.Onboarding.COMPLETE_SECOND) - - companion object { - val routes = listOf(Explain, AlarmTimeSelection, Birthday, TimeOfBirth, Name, Gender, Access, Complete1, Complete2) - - fun nextRoute(currentStep: Int): String? { - return routes.getOrNull(currentStep)?.route - } - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/SettingDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/SettingDestination.kt deleted file mode 100644 index 0b1f8e8f..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/SettingDestination.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class SettingDestination(val route: String) { - data object Route : SettingDestination(Routes.Setting.ROUTE) - data object Setting : SettingDestination(Routes.Setting.SETTING) - data object EditProfile : SettingDestination(Routes.Setting.EDIT_PROFILE) - data object EditBirthday : SettingDestination(Routes.Setting.EDIT_BIRTHDAY) - - companion object { - val routes = listOf(Setting, EditProfile, EditBirthday) - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/SplashDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/SplashDestination.kt deleted file mode 100644 index 9ea3934f..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/SplashDestination.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class SplashDestination(val route: String) { - data object Route : SplashDestination(Routes.Splash.ROUTE) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/TopLevelDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/TopLevelDestination.kt deleted file mode 100644 index 3a9fcc52..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/TopLevelDestination.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.yapp.common.navigation.destination - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import com.yapp.common.navigation.Routes - -enum class TopLevelDestination( - @DrawableRes val iconId: Int, - @StringRes val titleId: Int, - val route: String, -) { - HOME( - iconId = core.designsystem.R.drawable.ic_launcher_foreground, - titleId = core.designsystem.R.string.app_name, - route = Routes.Home.ROUTE, - ), - MYPAGE( - iconId = core.designsystem.R.drawable.ic_launcher_foreground, - titleId = core.designsystem.R.string.app_name, - route = Routes.MyPage.MYPAGE, - ), - ; - - companion object { - operator fun contains(route: String): Boolean = entries.any { it.route == route } - fun find(route: String): TopLevelDestination? = entries.find { it.route == route } - } -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/destination/WebViewDestination.kt b/core/common/src/main/java/com/yapp/common/navigation/destination/WebViewDestination.kt deleted file mode 100644 index 9533099b..00000000 --- a/core/common/src/main/java/com/yapp/common/navigation/destination/WebViewDestination.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.yapp.common.navigation.destination - -import com.yapp.common.navigation.Routes - -sealed class WebViewDestination(val route: String) { - object Route : WebViewDestination(Routes.WebView.ROUTE) - object WebView : WebViewDestination(Routes.WebView.WEBVIEW) -} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/AlarmInteractionRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/AlarmInteractionRoute.kt new file mode 100644 index 00000000..21310ba9 --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/AlarmInteractionRoute.kt @@ -0,0 +1,18 @@ +package com.yapp.common.navigation.route + +import com.yapp.domain.model.Alarm +import kotlinx.serialization.Serializable + +@Serializable +data object AlarmInteractionBaseRoute + +sealed interface AlarmInteractionDestination { + @Serializable + data object Route : AlarmInteractionDestination + + @Serializable + data class AlarmAction(val alarm: Alarm) : AlarmInteractionDestination + + @Serializable + data class AlarmSnoozeTimer(val alarm: Alarm) : AlarmInteractionDestination +} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/FortuneRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/FortuneRoute.kt new file mode 100644 index 00000000..c581c8ed --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/FortuneRoute.kt @@ -0,0 +1,14 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data object FortuneBaseRoute + +sealed interface FortuneDestination { + @Serializable + data object Fortune : FortuneDestination + + @Serializable + data object Reward : FortuneDestination +} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/HomeRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/HomeRoute.kt new file mode 100644 index 00000000..04b8b4da --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/HomeRoute.kt @@ -0,0 +1,14 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data object HomeBaseRoute + +sealed interface HomeDestination { + @Serializable + data object Route : HomeDestination + + @Serializable + data class AlarmAddEdit(val alarmId: Long? = null) : HomeDestination +} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt new file mode 100644 index 00000000..d5a04949 --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt @@ -0,0 +1,6 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data object MissionRoute diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/OnboardingRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/OnboardingRoute.kt new file mode 100644 index 00000000..ff4faea4 --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/OnboardingRoute.kt @@ -0,0 +1,55 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable +import kotlin.reflect.KClass + +@Serializable +data object OnboardingBaseRoute + +sealed class OnboardingDestination { + @Serializable + data object Explain : OnboardingDestination() + + @Serializable + data object AlarmTimeSelection : OnboardingDestination() + + @Serializable + data object Birthday : OnboardingDestination() + + @Serializable + data object TimeOfBirth : OnboardingDestination() + + @Serializable + data object Name : OnboardingDestination() + + @Serializable + data object Gender : OnboardingDestination() + + @Serializable + data object Access : OnboardingDestination() + + @Serializable + data object Complete1 : OnboardingDestination() + + @Serializable + data object Complete2 : OnboardingDestination() + + companion object { + val routes: List> = listOf( + Explain::class, + AlarmTimeSelection::class, + Birthday::class, + TimeOfBirth::class, + Name::class, + Gender::class, + Access::class, + Complete1::class, + Complete2::class, + ) + + fun getNextRouteForStep(currentStep: Int): KClass? { + val nextRoute = routes.getOrNull(currentStep + 1) + return nextRoute + } + } +} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/SettingRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/SettingRoute.kt new file mode 100644 index 00000000..9246eed0 --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/SettingRoute.kt @@ -0,0 +1,18 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data object SettingBaseRoute + +@Serializable +sealed interface SettingDestination { + @Serializable + data object Setting : SettingDestination + + @Serializable + data object EditProfile : SettingDestination + + @Serializable + data object EditBirthday : SettingDestination +} diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/SplashRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/SplashRoute.kt new file mode 100644 index 00000000..81724c65 --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/SplashRoute.kt @@ -0,0 +1,6 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data object SplashRoute diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/WebViewRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/WebViewRoute.kt new file mode 100644 index 00000000..9ced251c --- /dev/null +++ b/core/common/src/main/java/com/yapp/common/navigation/route/WebViewRoute.kt @@ -0,0 +1,6 @@ +package com.yapp.common.navigation.route + +import kotlinx.serialization.Serializable + +@Serializable +data class WebViewRoute(val url: String) diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index de2c4bb0..e5567cb0 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -10,5 +10,5 @@ android { } dependencies { - implementation(libs.gson) + implementation(libs.kotlinx.serialization.json) } diff --git a/domain/src/main/java/com/yapp/domain/model/Alarm.kt b/domain/src/main/java/com/yapp/domain/model/Alarm.kt index c3cf50ba..f04d4148 100644 --- a/domain/src/main/java/com/yapp/domain/model/Alarm.kt +++ b/domain/src/main/java/com/yapp/domain/model/Alarm.kt @@ -2,11 +2,13 @@ package com.yapp.domain.model import android.net.Uri import android.os.Parcelable -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json @Parcelize +@Serializable data class Alarm( val id: Long = 0, @@ -37,11 +39,11 @@ data class Alarm( companion object { fun fromJson(json: String): Alarm { - return Gson().fromJson(json, object : TypeToken() {}.type) + return Json.decodeFromString(json) } } - override fun toString(): String = Uri.encode(Gson().toJson(this)) + override fun toString(): String = Uri.encode(Json.encodeToString(this)) } fun Alarm.copyFrom(source: Alarm): Alarm { diff --git a/feature/alarm-interaction/build.gradle.kts b/feature/alarm-interaction/build.gradle.kts index 50a1074e..efc6eeec 100644 --- a/feature/alarm-interaction/build.gradle.kts +++ b/feature/alarm-interaction/build.gradle.kts @@ -21,6 +21,6 @@ dependencies { implementation(libs.orbit.viewmodel) implementation(libs.androidx.material.android) implementation(libs.androidx.annotation) - implementation(libs.gson) implementation(libs.play.services.ads) + implementation(libs.kotlinx.serialization.json) } diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt index 443027a5..5860eb6d 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt @@ -16,14 +16,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.Surface import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.core.util.Consumer import androidx.navigation.compose.NavHost import com.yapp.alarm.AlarmConstants import com.yapp.alarm.receivers.AlarmInteractionActivityReceiver -import com.yapp.common.navigation.destination.AlarmInteractionDestination import com.yapp.common.navigation.rememberOrbitNavigator +import com.yapp.common.navigation.route.AlarmInteractionBaseRoute import com.yapp.designsystem.theme.OrbitTheme import com.yapp.domain.model.Alarm import dagger.hilt.android.AndroidEntryPoint @@ -70,11 +69,12 @@ class AlarmInteractionActivity : ComponentActivity() { ) { NavHost( navController = navigator.navController, - startDestination = AlarmInteractionDestination.Route.route, + startDestination = AlarmInteractionBaseRoute, modifier = Modifier.navigationBarsPadding(), ) { alarmInteractionNavGraph( navigator = navigator, + alarm = alarm, ) } } @@ -89,7 +89,7 @@ class AlarmInteractionActivity : ComponentActivity() { } Log.d("AlarmInteractionActivity", "New Intent: $newIntent") newAlarm?.let { alarm -> - navigator.navController.navigate("${AlarmInteractionDestination.AlarmAction.route}/$alarm") + navigator.navigateToAlarmAction(alarm = alarm) } } @@ -99,13 +99,6 @@ class AlarmInteractionActivity : ComponentActivity() { this@AlarmInteractionActivity.removeOnNewIntentListener(onNewIntentConsumer) } } - - LaunchedEffect(Unit) { - val route = "${AlarmInteractionDestination.AlarmAction.route}/$alarm" - navigator.navController.navigate(route) { - popUpTo(AlarmInteractionDestination.Route.route) { inclusive = true } - } - } } } diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt index d87492e6..bc8d0035 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt @@ -1,56 +1,75 @@ package com.yapp.alarm.interaction +import android.os.Bundle +import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navArgument +import androidx.navigation.navOptions import androidx.navigation.navigation -import com.google.gson.Gson import com.yapp.alarm.interaction.action.AlarmActionRoute import com.yapp.alarm.interaction.snooze.AlarmSnoozeTimerRoute -import com.yapp.common.navigation.JsonNavType import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.AlarmInteractionDestination +import com.yapp.common.navigation.route.AlarmInteractionBaseRoute +import com.yapp.common.navigation.route.AlarmInteractionDestination import com.yapp.domain.model.Alarm +import kotlinx.serialization.json.Json +import kotlin.reflect.typeOf -class AlarmArgType : JsonNavType() { - override fun fromJsonParse(value: String): Alarm { - return Gson().fromJson(value, Alarm::class.java) +val AlarmArgType = object : NavType(isNullableAllowed = false) { + override fun get(bundle: Bundle, key: String): Alarm? { + return bundle.getString(key)?.let { Alarm.fromJson(it) } } - override fun Alarm.getJsonParse(): String { - return Gson().toJson(this) + override fun parseValue(value: String): Alarm { + return Alarm.fromJson(value) + } + + override fun put(bundle: Bundle, key: String, value: Alarm) { + bundle.putString(key, Json.encodeToString(Alarm.serializer(), value)) } } fun NavGraphBuilder.alarmInteractionNavGraph( navigator: OrbitNavigator, + alarm: Alarm?, ) { - navigation( - route = AlarmInteractionDestination.Route.route, - startDestination = "${AlarmInteractionDestination.AlarmAction.route}/{alarm}", + navigation( + startDestination = AlarmInteractionDestination.Route, ) { - composable( - route = "${AlarmInteractionDestination.AlarmAction.route}/{alarm}", - arguments = listOf( - navArgument("alarm") { - type = AlarmArgType() - defaultValue = Alarm() - }, - ), + composable { + LaunchedEffect(Unit) { + alarm?.let { + navigator.navigateToAlarmAction( + it, + navOptions { + popUpTo(AlarmInteractionBaseRoute) { + inclusive = true + } + }, + ) + } ?: run { + navigator.navigateToHome( + navOptions { + popUpTo(AlarmInteractionBaseRoute) { + inclusive = true + } + }, + ) + } + } + } + + composable( + typeMap = mapOf(typeOf() to AlarmArgType), ) { AlarmActionRoute( navigator = navigator, ) } - composable( - route = "${AlarmInteractionDestination.AlarmSnoozeTimer.route}/{alarm}", - arguments = listOf( - navArgument("alarm") { - type = AlarmArgType() - defaultValue = Alarm() - }, - ), + composable( + typeMap = mapOf(typeOf() to AlarmArgType), ) { AlarmSnoozeTimerRoute( navigator = navigator, diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt index cbc6d959..470f89c3 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt @@ -1,5 +1,7 @@ package com.yapp.alarm.interaction.action +import com.yapp.domain.model.Alarm +import com.yapp.ui.base.SideEffect import com.yapp.ui.base.UiState class AlarmActionContract { @@ -22,10 +24,6 @@ class AlarmActionContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() + data class NavigateToAlarmSnooze(val alarm: Alarm) : SideEffect() } } diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt index c44edb40..4db7ed7b 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt @@ -49,12 +49,8 @@ internal fun AlarmActionRoute( LaunchedEffect(sideEffect) { sideEffect.collect { action -> when (action) { - is AlarmActionContract.SideEffect.Navigate -> { - navigator.navigateTo( - route = action.route, - popUpTo = action.popUpTo, - inclusive = action.inclusive, - ) + is AlarmActionContract.SideEffect.NavigateToAlarmSnooze -> { + navigator.navigateToAlarmSnoozeTimer(action.alarm) } } } diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt index b6fa6e74..57e6cd8c 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozeIntent -import com.yapp.common.navigation.Routes import com.yapp.datastore.UserPreferences import com.yapp.domain.model.Alarm import com.yapp.ui.base.BaseViewModel @@ -15,7 +14,10 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.time.LocalDate +import java.time.LocalTime import java.time.format.DateTimeFormatter +import java.time.format.TextStyle +import java.util.Locale import javax.inject.Inject @HiltViewModel @@ -57,9 +59,9 @@ class AlarmActionViewModel @Inject constructor( private fun startClock() { viewModelScope.launch { while (isActive) { - val now = java.time.LocalTime.now() - val today = java.time.LocalDate.now() - val dayOfWeek = today.dayOfWeek.getDisplayName(java.time.format.TextStyle.FULL, java.util.Locale.KOREAN) + val now = LocalTime.now() + val today = LocalDate.now() + val dayOfWeek = today.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN) updateState { copy( @@ -94,13 +96,9 @@ class AlarmActionViewModel @Inject constructor( }, ) } - emitSideEffect( - AlarmActionContract.SideEffect.Navigate( - route = "${Routes.AlarmInteraction.ALARM_SNOOZE_TIMER}/$alarm", - popUpTo = Routes.AlarmInteraction.ALARM_ACTION, - inclusive = true, - ), - ) + alarm?.let { + emitSideEffect(AlarmActionContract.SideEffect.NavigateToAlarmSnooze(it)) + } } private fun dismiss() { diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerContract.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerContract.kt index f7adfee9..dcca8639 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerContract.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerContract.kt @@ -16,11 +16,5 @@ class AlarmSnoozeTimerContract { data object Dismiss : Action() } - sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() - } + sealed class SideEffect : com.yapp.ui.base.SideEffect } diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt index d770b69f..e914f506 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt @@ -57,17 +57,7 @@ internal fun AlarmSnoozeTimerRoute( val sideEffect = viewModel.container.sideEffectFlow LaunchedEffect(sideEffect) { - sideEffect.collect { action -> - when (action) { - is AlarmSnoozeTimerContract.SideEffect.Navigate -> { - navigator.navigateTo( - route = action.route, - popUpTo = action.popUpTo, - inclusive = action.inclusive, - ) - } - } - } + sideEffect.collect { } } AlarmSnoozeTimerScreen( diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneContract.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneContract.kt index 0083828d..6c7528e0 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneContract.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneContract.kt @@ -25,11 +25,9 @@ sealed class FortuneContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() + data object NavigateToFortuneReward : SideEffect() + + data object NavigateToHome : SideEffect() data object NavigateBack : SideEffect() diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt index 86f87fee..5f1b5133 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt @@ -1,77 +1,94 @@ package com.yapp.fortune import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.navOptions import androidx.navigation.navigation import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.FortuneDestination import com.yapp.common.navigation.extensions.sharedHiltViewModel +import com.yapp.common.navigation.route.FortuneBaseRoute +import com.yapp.common.navigation.route.FortuneDestination import com.yapp.ui.component.snackbar.showCustomSnackBar +import kotlinx.coroutines.CoroutineScope fun NavGraphBuilder.fortuneNavGraph( navigator: OrbitNavigator, snackBarHostState: SnackbarHostState, ) { - navigation( - route = FortuneDestination.Route.route, - startDestination = FortuneDestination.Fortune.route, - ) { - FortuneDestination.routes.forEach { destination -> - composable(destination.route) { backStackEntry -> - val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) - val coroutineScope = rememberCoroutineScope() + navigation(startDestination = FortuneDestination.Fortune) { + composable { backStackEntry -> + val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) + val coroutineScope = rememberCoroutineScope() - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collect { sideEffect -> - when (sideEffect) { - is FortuneContract.SideEffect.Navigate -> navigator.navigateTo( - route = sideEffect.route, - popUpTo = sideEffect.popUpTo, - inclusive = sideEffect.inclusive, - ) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collect { sideEffect -> + handleSideEffect(sideEffect, navigator, snackBarHostState, coroutineScope) + } + } - FortuneContract.SideEffect.NavigateBack -> navigator.navigateBack() + FortuneRoute( + viewModel = viewModel, + navigator = navigator, + ) + } - is FortuneContract.SideEffect.ShowSnackBar -> showCustomSnackBar( - scope = coroutineScope, - snackBarHostState = snackBarHostState, - message = sideEffect.message, - actionLabel = sideEffect.label, - iconRes = sideEffect.iconRes, - bottomPadding = sideEffect.bottomPadding, - durationMillis = sideEffect.durationMillis, - onDismiss = sideEffect.onDismiss, - onAction = sideEffect.onAction, - ) - } - } - } + composable { backStackEntry -> + val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) + val coroutineScope = rememberCoroutineScope() - when (destination) { - FortuneDestination.Fortune -> FortuneRoute( - viewModel = viewModel, - navigator = navigator, - ) - FortuneDestination.Reward -> FortuneRewardRoute(viewModel) - else -> {} + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collect { sideEffect -> + handleSideEffect(sideEffect, navigator, snackBarHostState, coroutineScope) } } + + FortuneRewardRoute( + viewModel = viewModel, + ) } } } -@Composable -private fun handleFortuneSideEffect( +private suspend fun handleSideEffect( sideEffect: FortuneContract.SideEffect, navigator: OrbitNavigator, snackBarHostState: SnackbarHostState, + coroutineScope: CoroutineScope, ) { - val coroutineScope = rememberCoroutineScope() + when (sideEffect) { + FortuneContract.SideEffect.NavigateToFortuneReward -> { + navigator.navigateToFortuneReward( + navOptions = navOptions { + popUpTo(FortuneDestination.Fortune) { + inclusive = true + } + }, + ) + } + + FortuneContract.SideEffect.NavigateToHome -> navigator.navigateToHome( + navOptions = navOptions { + popUpTo(FortuneBaseRoute) { + inclusive = true + } + }, + ) + + FortuneContract.SideEffect.NavigateBack -> navigator.navigateBack() - LaunchedEffect(sideEffect) { + is FortuneContract.SideEffect.ShowSnackBar -> showCustomSnackBar( + scope = coroutineScope, + snackBarHostState = snackBarHostState, + message = sideEffect.message, + actionLabel = sideEffect.label, + iconRes = sideEffect.iconRes, + bottomPadding = sideEffect.bottomPadding, + durationMillis = sideEffect.durationMillis, + onDismiss = sideEffect.onDismiss, + onAction = sideEffect.onAction, + ) } } diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt index cfd930f5..b4890bfb 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt @@ -4,8 +4,6 @@ import android.app.Application import android.util.Log import androidx.annotation.DrawableRes import androidx.lifecycle.viewModelScope -import com.yapp.common.navigation.destination.FortuneDestination -import com.yapp.common.navigation.destination.HomeDestination import com.yapp.datastore.UserPreferences import com.yapp.domain.repository.FortuneRepository import com.yapp.domain.repository.ImageRepository @@ -78,13 +76,7 @@ class FortuneViewModel @Inject constructor( when (action) { is FortuneContract.Action.NextStep -> { if (state.hasReward) { - postSideEffect( - FortuneContract.SideEffect.Navigate( - route = FortuneDestination.Reward.route, - popUpTo = FortuneDestination.Fortune.route, - inclusive = true, - ), - ) + postSideEffect(FortuneContract.SideEffect.NavigateToFortuneReward) } else { reduce { state.copy(currentStep = (state.currentStep + 1).coerceAtMost(5)) } } @@ -102,13 +94,7 @@ class FortuneViewModel @Inject constructor( } private fun navigateToHome() { - emitSideEffect( - FortuneContract.SideEffect.Navigate( - route = HomeDestination.Route.route, - popUpTo = FortuneDestination.Route.route, - inclusive = true, - ), - ) + emitSideEffect(FortuneContract.SideEffect.NavigateToHome) } private fun saveImage(@DrawableRes resId: Int) = viewModelScope.launch { diff --git a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt index 2f9c710f..de6da472 100644 --- a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt @@ -95,12 +95,6 @@ sealed class AlarmAddEditContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() - data object NavigateBack : SideEffect() data class SaveAlarm(val id: Long) : SideEffect() diff --git a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt index 5b7ba527..d479e3f6 100644 --- a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt @@ -88,13 +88,6 @@ fun AlarmAddEditRoute( is AlarmAddEditContract.SideEffect.NavigateBack -> { navigator.navigateBack() } - is AlarmAddEditContract.SideEffect.Navigate -> { - navigator.navigateTo( - route = effect.route, - popUpTo = effect.popUpTo, - inclusive = effect.inclusive, - ) - } is AlarmAddEditContract.SideEffect.SaveAlarm -> { navigator.navController.previousBackStackEntry ?.savedStateHandle diff --git a/feature/home/src/main/java/com/yapp/home/HomeContract.kt b/feature/home/src/main/java/com/yapp/home/HomeContract.kt index ab55314f..ee3f5dd1 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeContract.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeContract.kt @@ -3,6 +3,7 @@ package com.yapp.home import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.yapp.domain.model.Alarm +import com.yapp.ui.base.SideEffect import com.yapp.ui.base.UiState sealed class HomeContract { @@ -72,11 +73,13 @@ sealed class HomeContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() + data object NavigateToAddAlarm : SideEffect() + + data class NavigateToEditAlarm(val alarmId: Long) : SideEffect() + + data object NavigateToFortune : SideEffect() + + data object NavigateToSetting : SideEffect() data class ShowSnackBar( val message: String, diff --git a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt index ec43adbe..b044e9de 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt @@ -2,13 +2,12 @@ package com.yapp.home import androidx.compose.material3.SnackbarHostState import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.compose.navigation -import androidx.navigation.navArgument import com.yapp.alarm.addedit.AlarmAddEditRoute import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.HomeDestination +import com.yapp.common.navigation.route.HomeBaseRoute +import com.yapp.common.navigation.route.HomeDestination const val ADD_ALARM_RESULT_KEY = "addAlarmResult" const val UPDATE_ALARM_RESULT_KEY = "updateAlarmResult" @@ -18,26 +17,17 @@ fun NavGraphBuilder.homeNavGraph( navigator: OrbitNavigator, snackBarHostState: SnackbarHostState, ) { - navigation( - route = HomeDestination.Route.route, - startDestination = HomeDestination.Home.route, + navigation( + startDestination = HomeDestination.Route, ) { - composable(route = HomeDestination.Home.route) { + composable { HomeRoute( navigator = navigator, snackBarHostState = snackBarHostState, ) } - composable( - route = "${HomeDestination.AlarmAddEdit.route}?id={alarmId}", - arguments = listOf( - navArgument("alarmId") { - type = NavType.LongType - defaultValue = -1 - }, - ), - ) { + composable { AlarmAddEditRoute( navigator = navigator, snackBarHostState = snackBarHostState, diff --git a/feature/home/src/main/java/com/yapp/home/HomeScreen.kt b/feature/home/src/main/java/com/yapp/home/HomeScreen.kt index 270922a1..b8a4dc46 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeScreen.kt @@ -123,13 +123,22 @@ fun HomeRoute( LaunchedEffect(sideEffect) { sideEffect.collectLatest { effect -> when (effect) { - is HomeContract.SideEffect.Navigate -> { - navigator.navigateTo( - route = effect.route, - popUpTo = effect.popUpTo, - inclusive = effect.inclusive, - ) + is HomeContract.SideEffect.NavigateToAddAlarm -> { + navigator.navigateToAddAlarm() + } + + is HomeContract.SideEffect.NavigateToEditAlarm -> { + navigator.navigateToEditAlarm(effect.alarmId) + } + + is HomeContract.SideEffect.NavigateToFortune -> { + navigator.navigateToFortune() + } + + is HomeContract.SideEffect.NavigateToSetting -> { + navigator.navigateToSetting() } + is HomeContract.SideEffect.ShowSnackBar -> { val result = showCustomSnackBar( scope = coroutineScope, diff --git a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt index 67d3ee67..9c5dccc6 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt @@ -3,9 +3,6 @@ package com.yapp.home import android.util.Log import androidx.lifecycle.viewModelScope import com.yapp.alarm.AlarmHelper -import com.yapp.common.navigation.destination.FortuneDestination -import com.yapp.common.navigation.destination.HomeDestination -import com.yapp.common.navigation.destination.SettingDestination import com.yapp.common.util.ResourceProvider import com.yapp.datastore.UserPreferences import com.yapp.domain.model.Alarm @@ -103,7 +100,7 @@ class HomeViewModel @Inject constructor( } private fun navigateToAlarmCreation() { - emitSideEffect(HomeContract.SideEffect.Navigate(HomeDestination.AlarmAddEdit.route)) + emitSideEffect(HomeContract.SideEffect.NavigateToAddAlarm) } private fun toggleMultiSelectionMode() { @@ -316,7 +313,7 @@ class HomeViewModel @Inject constructor( } private fun editAlarm(alarmId: Long) { - emitSideEffect(HomeContract.SideEffect.Navigate("${HomeDestination.AlarmAddEdit.route}?id=$alarmId")) + emitSideEffect(HomeContract.SideEffect.NavigateToEditAlarm(alarmId)) } private fun updateDeliveryTime(alarms: List) { @@ -404,9 +401,7 @@ class HomeViewModel @Inject constructor( processAction(HomeContract.Action.ShowNoDailyFortuneDialog) } else { userPreferences.markFortuneAsChecked() - emitSideEffect( - HomeContract.SideEffect.Navigate(FortuneDestination.Fortune.route), - ) + emitSideEffect(HomeContract.SideEffect.NavigateToFortune) } } } @@ -457,11 +452,7 @@ class HomeViewModel @Inject constructor( } private fun navigateToSetting() { - emitSideEffect( - HomeContract.SideEffect.Navigate( - route = SettingDestination.Route.route, - ), - ) + emitSideEffect(HomeContract.SideEffect.NavigateToSetting) } private fun showItemMenu(alarmId: Long, x: Float, y: Float) { diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt index 505ae265..f1812c49 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt @@ -28,11 +28,7 @@ sealed class MissionContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() + data object NavigateToFortune : SideEffect() data object NavigateBack : SideEffect() } diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt b/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt index 076a59a6..a24f0545 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt @@ -4,51 +4,41 @@ import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink -import androidx.navigation.navigation +import androidx.navigation.navOptions import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.MissionDestination import com.yapp.common.navigation.extensions.sharedHiltViewModel +import com.yapp.common.navigation.route.MissionRoute -fun NavGraphBuilder.missionNavGraph( +fun NavGraphBuilder.missionScreen( navigator: OrbitNavigator, ) { - navigation( - route = MissionDestination.Route.route, - startDestination = MissionDestination.Mission.route, + composable( + deepLinks = listOf( + navDeepLink { + uriPattern = "orbitapp://mission?notificationId={notificationId}" + }, + ), ) { - composable( - route = MissionDestination.Mission.route, - deepLinks = listOf( - navDeepLink { - uriPattern = "orbitapp://mission?notificationId={notificationId}" - }, - ), - ) { backStackEntry -> - val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) + val viewModel = it.sharedHiltViewModel(navigator.navController) - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collect { sideEffect -> - handleMissionSideEffect(sideEffect, navigator, viewModel) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collect { sideEffect -> + when (sideEffect) { + MissionContract.SideEffect.NavigateToFortune -> { + navigator.navigateToFortune( + navOptions = navOptions { + popUpTo(MissionRoute) { + inclusive = true + } + }, + ) + } + + MissionContract.SideEffect.NavigateBack -> navigator.navigateBack() } } - - MissionRoute(viewModel) } - } -} - -private fun handleMissionSideEffect( - sideEffect: MissionContract.SideEffect, - navigator: OrbitNavigator, - viewModel: MissionViewModel, -) { - when (sideEffect) { - is MissionContract.SideEffect.Navigate -> navigator.navigateTo( - route = sideEffect.route, - popUpTo = sideEffect.popUpTo, - inclusive = sideEffect.inclusive, - ) - MissionContract.SideEffect.NavigateBack -> navigator.navigateBack() + MissionRoute(viewModel) } } diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt index 94512478..38512436 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt @@ -7,9 +7,6 @@ import androidx.lifecycle.viewModelScope import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent import com.yapp.analytics.AnalyticsEvent import com.yapp.analytics.AnalyticsHelper -import com.yapp.common.navigation.destination.FortuneDestination -import com.yapp.common.navigation.destination.HomeDestination -import com.yapp.common.navigation.destination.MissionDestination import com.yapp.datastore.UserPreferences import com.yapp.domain.model.MissionType import com.yapp.domain.repository.FortuneRepository @@ -131,13 +128,7 @@ class MissionViewModel @Inject constructor( userPreferences.saveFortuneId(data.id) userPreferences.saveFortuneScore(data.avgFortuneScore) - emitSideEffect( - MissionContract.SideEffect.Navigate( - route = FortuneDestination.Route.route, - popUpTo = MissionDestination.Route.route, - inclusive = true, - ), - ) + emitSideEffect(MissionContract.SideEffect.NavigateToFortune) }.onFailure { error -> Log.e("MissionViewModel", "운세 데이터 요청 실패: ${error.message}") updateState { copy(errorMessage = error.message) } @@ -159,13 +150,7 @@ class MissionViewModel @Inject constructor( userPreferences.saveFortuneId(data.id) userPreferences.saveFortuneScore(data.avgFortuneScore) - emitSideEffect( - MissionContract.SideEffect.Navigate( - route = FortuneDestination.Route.route, - popUpTo = MissionDestination.Route.route, - inclusive = true, - ), - ) + emitSideEffect(MissionContract.SideEffect.NavigateToFortune) }.onFailure { Log.e("MissionViewModel", "운세 재요청 실패: ${it.message}") navigateToHome() @@ -195,13 +180,7 @@ class MissionViewModel @Inject constructor( } private fun navigateToHome() { - emitSideEffect( - MissionContract.SideEffect.Navigate( - route = HomeDestination.Route.route, - popUpTo = MissionDestination.Route.route, - inclusive = true, - ), - ) + emitSideEffect(MissionContract.SideEffect.NavigateToFortune) } private fun sendAlarmDismissIntent(id: Long) { diff --git a/feature/navigator/build.gradle.kts b/feature/navigator/build.gradle.kts index cc268c3d..a8db6273 100644 --- a/feature/navigator/build.gradle.kts +++ b/feature/navigator/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.compose) implementation(libs.orbit.viewmodel) + implementation(libs.kotlin.reflect) implementation(projects.feature.home) implementation(projects.feature.alarmInteraction) implementation(projects.feature.onboarding) diff --git a/feature/navigator/src/main/java/com/yapp/navigator/OrbitBottomNavigationBar.kt b/feature/navigator/src/main/java/com/yapp/navigator/OrbitBottomNavigationBar.kt deleted file mode 100644 index 60d481ba..00000000 --- a/feature/navigator/src/main/java/com/yapp/navigator/OrbitBottomNavigationBar.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.yapp.navigator - -import androidx.annotation.DrawableRes -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.height -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.yapp.common.navigation.destination.TopLevelDestination -import kotlinx.collections.immutable.ImmutableList - -@Composable -internal fun OrbitBottomNavigationBar( - modifier: Modifier = Modifier, - visible: Boolean, - currentTab: TopLevelDestination?, - entries: ImmutableList, - onClickItem: (TopLevelDestination) -> Unit, -) { - AnimatedVisibility(visible = visible) { - Column { - HorizontalDivider(color = Color.Black, thickness = 1.dp) - Row( - modifier = Modifier - .height(56.dp) - .background(color = Color.White), - ) { - entries.forEach { tab -> - NavItem( - selected = tab == currentTab, - label = stringResource(id = tab.titleId), - iconId = tab.iconId, - onClick = { onClickItem(tab) }, - ) - } - } - } - } -} - -@Composable -fun RowScope.NavItem( - modifier: Modifier = Modifier, - selected: Boolean, - label: String, - @DrawableRes iconId: Int, - onClick: () -> Unit, -) { - Column( - modifier = modifier - .weight(1f) - .fillMaxHeight() - .clickable(onClick = onClick), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Icon( - painter = painterResource(id = iconId), - contentDescription = label, - tint = if (selected) Color.Blue else Color.Gray, - ) - Text(text = label, color = if (selected) Color.Blue else Color.Black) - } -} diff --git a/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt b/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt index 6fe6b82a..082c2ca5 100644 --- a/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt +++ b/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt @@ -16,22 +16,18 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.SplashDestination -import com.yapp.common.navigation.destination.TopLevelDestination import com.yapp.common.navigation.rememberOrbitNavigator import com.yapp.designsystem.theme.OrbitTheme import com.yapp.fortune.fortuneNavGraph import com.yapp.home.homeNavGraph -import com.yapp.mission.missionNavGraph +import com.yapp.mission.missionScreen import com.yapp.onboarding.onboardingNavGraph import com.yapp.setting.settingNavGraph -import com.yapp.splash.SplashRoute +import com.yapp.splash.splashScreen import com.yapp.ui.component.snackbar.CustomSnackBarVisuals import com.yapp.ui.component.snackbar.OrbitSnackBar -import com.yapp.webview.webViewNavGraph -import kotlinx.collections.immutable.toImmutableList +import com.yapp.webview.webViewScreen @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable @@ -43,14 +39,6 @@ internal fun OrbitNavHost( Scaffold( modifier = modifier, - bottomBar = { - OrbitBottomNavigationBar( - visible = false, - currentTab = navigator.currentTab, - entries = TopLevelDestination.entries.toImmutableList(), - onClickItem = navigator::navigateToTopLevelDestination, - ) - }, snackbarHost = { OrbitSnackBarHost(snackBarHostState = snackBarHostState) }, @@ -61,24 +49,19 @@ internal fun OrbitNavHost( startDestination = navigator.startDestination, modifier = Modifier.navigationBarsPadding(), ) { - composable(SplashDestination.Route.route) { - SplashRoute(navigator) - } - onboardingNavGraph( - navigator = navigator, - onFinishOnboarding = { navigator.navigateToTopLevelDestination(TopLevelDestination.HOME) }, - ) + splashScreen(navigator = navigator) + onboardingNavGraph(navigator = navigator) homeNavGraph( navigator = navigator, snackBarHostState = snackBarHostState, ) - missionNavGraph(navigator = navigator) + missionScreen(navigator = navigator) fortuneNavGraph( navigator = navigator, snackBarHostState = snackBarHostState, ) settingNavGraph(navigator = navigator) - webViewNavGraph(navigator = navigator) + webViewScreen(navigator = navigator) } } } diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt index 5cb2a890..5e8b83c2 100644 --- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt +++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt @@ -72,14 +72,12 @@ sealed class OnboardingContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() + data class NavigateToNextStep(val currentStep: Int) : SideEffect() data object NavigateBack : SideEffect() + data object OnboardingCompleted : SideEffect() + data class OpenWebView(val url: String) : SideEffect() } diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt index 903c1c30..ee57af2e 100644 --- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt +++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt @@ -4,61 +4,106 @@ import android.net.Uri import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.navOptions import androidx.navigation.navigation import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.OnboardingDestination -import com.yapp.common.navigation.destination.WebViewDestination import com.yapp.common.navigation.extensions.sharedHiltViewModel +import com.yapp.common.navigation.route.OnboardingBaseRoute +import com.yapp.common.navigation.route.OnboardingDestination import kotlinx.coroutines.flow.collectLatest fun NavGraphBuilder.onboardingNavGraph( navigator: OrbitNavigator, - onFinishOnboarding: () -> Unit, ) { - navigation( - route = OnboardingDestination.Route.route, - startDestination = OnboardingDestination.Explain.route, - ) { - OnboardingDestination.routes.forEach { destination -> - composable(destination.route) { backStackEntry -> - val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) + navigation(startDestination = OnboardingDestination.Explain) { + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingExplainRoute(viewModel) + } - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collectLatest { sideEffect -> - handleSideEffect(sideEffect, navigator, viewModel, onFinishOnboarding) - } + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) } + } + OnboardingAlarmTimeSelectionRoute(viewModel) + } - when (destination) { - OnboardingDestination.Route, OnboardingDestination.Explain -> { - OnboardingExplainRoute(viewModel) - } - OnboardingDestination.AlarmTimeSelection -> { - OnboardingAlarmTimeSelectionRoute(viewModel) - } - OnboardingDestination.Birthday -> { - OnboardingBirthdayRoute(viewModel) - } - OnboardingDestination.TimeOfBirth -> { - OnboardingTimeOfBirthRoute(viewModel) - } - OnboardingDestination.Name -> { - OnboardingNameRoute(viewModel) - } - OnboardingDestination.Gender -> { - OnboardingGenderRoute(viewModel) - } - OnboardingDestination.Access -> { - OnboardingAccessRoute(viewModel) - } - OnboardingDestination.Complete1 -> { - OnboardingCompleteRoute(viewModel) - } - OnboardingDestination.Complete2 -> { - OnboardingCompleteRoute2(viewModel) - } + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingBirthdayRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingTimeOfBirthRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingNameRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingGenderRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingAccessRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) + } + } + OnboardingCompleteRoute(viewModel) + } + + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator, viewModel) } } + OnboardingCompleteRoute2(viewModel) } } } @@ -67,22 +112,29 @@ private fun handleSideEffect( sideEffect: OnboardingContract.SideEffect, navigator: OrbitNavigator, viewModel: OnboardingViewModel, - onFinishOnboarding: () -> Unit, ) { when (sideEffect) { - is OnboardingContract.SideEffect.Navigate -> navigator.navigateTo( - route = sideEffect.route, - popUpTo = sideEffect.popUpTo, - inclusive = sideEffect.inclusive, - ) + is OnboardingContract.SideEffect.NavigateToNextStep -> { + navigator.navigateToOnboardingNextStep(sideEffect.currentStep) + } + OnboardingContract.SideEffect.NavigateBack -> { viewModel.processAction(OnboardingContract.Action.Reset) navigator.navigateBack() } - OnboardingContract.SideEffect.OnboardingCompleted -> onFinishOnboarding() + + OnboardingContract.SideEffect.OnboardingCompleted -> { + navigator.navigateToHome( + navOptions = navOptions { + popUpTo(OnboardingBaseRoute) { + inclusive = true + } + }, + ) + } is OnboardingContract.SideEffect.OpenWebView -> { - navigator.navigateTo("${WebViewDestination.WebView.route}/${Uri.encode(sideEffect.url)}") + navigator.navigateToWebView(Uri.encode(sideEffect.url)) } } } diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt index 72e873a5..ec145349 100644 --- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt @@ -5,8 +5,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.yapp.analytics.AnalyticsEvent import com.yapp.analytics.AnalyticsHelper -import com.yapp.common.navigation.destination.HomeDestination -import com.yapp.common.navigation.destination.OnboardingDestination +import com.yapp.common.navigation.route.OnboardingDestination import com.yapp.datastore.UserPreferences import com.yapp.domain.model.Alarm import com.yapp.domain.model.AlarmDay @@ -19,6 +18,7 @@ import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.reflect.KClass @HiltViewModel class OnboardingViewModel @Inject constructor( @@ -35,8 +35,8 @@ class OnboardingViewModel @Inject constructor( birthType = savedStateHandle["birthType"] ?: "양력", ), ) { - private val currentRoute: String? - get() = OnboardingDestination.routes.getOrNull(currentState.currentStep - 1)?.route + private val currentRoute: KClass? + get() = OnboardingDestination.routes.getOrNull(currentState.currentStep) fun processAction(action: OnboardingContract.Action) { when (action) { @@ -96,7 +96,7 @@ class OnboardingViewModel @Inject constructor( private fun moveToNextStep() { val currentStep = container.stateFlow.value.currentStep val nextStep = currentStep + 1 - val nextRoute = OnboardingDestination.nextRoute(currentStep) + val nextRoute = OnboardingDestination.getNextRouteForStep(currentStep) savedStateHandle["birthDate"] = currentState.birthDate savedStateHandle["birthType"] = currentState.birthType @@ -104,7 +104,7 @@ class OnboardingViewModel @Inject constructor( if (nextRoute != null) { savedStateHandle["currentStep"] = nextStep updateState { copy(currentStep = nextStep) } - emitSideEffect(OnboardingContract.SideEffect.Navigate(nextRoute)) + emitSideEffect(OnboardingContract.SideEffect.NavigateToNextStep(currentStep)) } else { emitSideEffect(OnboardingContract.SideEffect.OnboardingCompleted) } @@ -201,7 +201,7 @@ class OnboardingViewModel @Inject constructor( } private fun updateBirthDate(lunar: String, year: Int, month: Int, day: Int) { - if (currentRoute != OnboardingDestination.Birthday.route) return + if (currentRoute != OnboardingDestination.Birthday::class) return val formattedDate = "$year-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}" @@ -241,13 +241,7 @@ class OnboardingViewModel @Inject constructor( private fun completeOnboarding() { viewModelScope.launch { userPreferences.setOnboardingCompleted() - emitSideEffect( - OnboardingContract.SideEffect.Navigate( - route = HomeDestination.Route.route, - popUpTo = OnboardingDestination.Route.route, - inclusive = true, - ), - ) + emitSideEffect(OnboardingContract.SideEffect.OnboardingCompleted) } } diff --git a/feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt b/feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt index a5d3adf2..166388b3 100644 --- a/feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt +++ b/feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt @@ -2,7 +2,6 @@ package com.yapp.setting import android.util.Log import androidx.lifecycle.viewModelScope -import com.yapp.common.navigation.destination.SettingDestination import com.yapp.datastore.UserPreferences import com.yapp.domain.model.EditUser import com.yapp.domain.repository.UserInfoRepository @@ -150,15 +149,7 @@ class EditProfileViewModel @Inject constructor( if (result.isSuccess) { userPreferences.saveUserName(state.name) - emitSideEffect(SettingContract.SideEffect.UserInfoUpdated) - - emitSideEffect( - SettingContract.SideEffect.Navigate( - route = SettingDestination.Setting.route, - popUpTo = SettingDestination.Setting.route, - inclusive = true, - ), - ) + emitSideEffect(SettingContract.SideEffect.NavigateToSettingRoute) } else { Log.e("EditProfileViewModel", "사용자 정보 수정 실패") } @@ -168,9 +159,9 @@ class EditProfileViewModel @Inject constructor( return formattedDate.replace(Regex("[^0-9-]"), "") } - private fun navigateToEditBirthday() = intent { + private fun navigateToEditBirthday() { updateState { copy(shouldFetchUserInfo = false) } - emitSideEffect(SettingContract.SideEffect.Navigate(SettingDestination.EditBirthday.route)) + emitSideEffect(SettingContract.SideEffect.NavigateToEditBirthday) } private fun refreshUserInfo() { diff --git a/feature/setting/src/main/java/com/yapp/setting/SettingContract.kt b/feature/setting/src/main/java/com/yapp/setting/SettingContract.kt index b6b65a66..6f957cc3 100644 --- a/feature/setting/src/main/java/com/yapp/setting/SettingContract.kt +++ b/feature/setting/src/main/java/com/yapp/setting/SettingContract.kt @@ -65,14 +65,14 @@ sealed class SettingContract { } sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate( - val route: String, - val popUpTo: String? = null, - val inclusive: Boolean = false, - ) : SideEffect() - data object NavigateBack : SideEffect() + + data object NavigateToSettingRoute : SideEffect() + + data object NavigateToEditProfile : SideEffect() + + data object NavigateToEditBirthday : SideEffect() + data class OpenWebView(val url: String) : SideEffect() - data object UserInfoUpdated : SideEffect() } } diff --git a/feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt b/feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt index dfaf89ea..054c523b 100644 --- a/feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt +++ b/feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt @@ -8,129 +8,122 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.navOptions import androidx.navigation.navigation import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.SettingDestination -import com.yapp.common.navigation.destination.WebViewDestination import com.yapp.common.navigation.extensions.sharedHiltViewModel -import com.yapp.ui.base.BaseViewModel +import com.yapp.common.navigation.route.SettingBaseRoute +import com.yapp.common.navigation.route.SettingDestination import kotlinx.coroutines.flow.collectLatest fun NavGraphBuilder.settingNavGraph( navigator: OrbitNavigator, ) { - navigation( - route = SettingDestination.Route.route, - startDestination = SettingDestination.Setting.route, + navigation( + startDestination = SettingDestination.Setting, ) { - SettingDestination.routes.forEach { destination -> - when (destination) { - SettingDestination.Setting -> { - composable(route = destination.route) { backStackEntry -> - val viewModel = - backStackEntry.sharedHiltViewModel(navigator.navController) + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collectLatest { sideEffect -> - handleSettingSideEffect(sideEffect, navigator, viewModel) - } - } - - SettingRoute(viewModel) - } + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator) } + } - SettingDestination.EditProfile -> { - composable(route = destination.route) { backStackEntry -> - val viewModel = - backStackEntry.sharedHiltViewModel(navigator.navController) + SettingRoute(viewModel) + } - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collect { sideEffect -> - handleSettingSideEffect(sideEffect, navigator, viewModel) - } - } + composable { + val viewModel = it.sharedHiltViewModel(navigator.navController) - EditProfileRoute(viewModel) - } + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator) } + } - SettingDestination.EditBirthday -> { - composable( - route = destination.route, - enterTransition = { - slideInVertically( - initialOffsetY = { it }, - animationSpec = tween( - durationMillis = 350, - easing = FastOutSlowInEasing, - ), - ) - }, - exitTransition = { - slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween( - durationMillis = 250, - easing = FastOutSlowInEasing, - ), - ) - }, - popEnterTransition = { - slideInVertically( - initialOffsetY = { -it }, - animationSpec = tween( - durationMillis = 300, - easing = FastOutSlowInEasing, - ), - ) - }, - popExitTransition = { - slideOutVertically( - targetOffsetY = { it }, - animationSpec = tween( - durationMillis = 300, - easing = FastOutSlowInEasing, - ), - ) - }, - ) { backStackEntry -> - val viewModel = - backStackEntry.sharedHiltViewModel(navigator.navController) + EditProfileRoute(viewModel) + } - LaunchedEffect(viewModel) { - viewModel.container.sideEffectFlow.collectLatest { sideEffect -> - handleSettingSideEffect(sideEffect, navigator, viewModel) - } - } + composable( + enterTransition = { + slideInVertically( + initialOffsetY = { it }, + animationSpec = tween( + durationMillis = 350, + easing = FastOutSlowInEasing, + ), + ) + }, + exitTransition = { + slideOutVertically( + targetOffsetY = { -it }, + animationSpec = tween( + durationMillis = 250, + easing = FastOutSlowInEasing, + ), + ) + }, + popEnterTransition = { + slideInVertically( + initialOffsetY = { -it }, + animationSpec = tween( + durationMillis = 300, + easing = FastOutSlowInEasing, + ), + ) + }, + popExitTransition = { + slideOutVertically( + targetOffsetY = { it }, + animationSpec = tween( + durationMillis = 300, + easing = FastOutSlowInEasing, + ), + ) + }, + ) { + val viewModel = it.sharedHiltViewModel(navigator.navController) - EditBirthdayRoute(viewModel) - } + LaunchedEffect(viewModel) { + viewModel.container.sideEffectFlow.collectLatest { sideEffect -> + handleSideEffect(sideEffect, navigator) } - - else -> {} } + + EditBirthdayRoute(viewModel) } } } -private fun handleSettingSideEffect( +private fun handleSideEffect( sideEffect: SettingContract.SideEffect, navigator: OrbitNavigator, - viewModel: BaseViewModel<*, *>, ) { when (sideEffect) { - is SettingContract.SideEffect.Navigate -> navigator.navigateTo( - route = sideEffect.route, - popUpTo = sideEffect.popUpTo, - inclusive = sideEffect.inclusive, - ) - SettingContract.SideEffect.NavigateBack -> navigator.navigateBack() + SettingContract.SideEffect.NavigateToSettingRoute -> { + navigator.navigateToSetting( + navOptions = navOptions { + popUpTo(SettingBaseRoute) { + inclusive = true + } + }, + ) + } + + SettingContract.SideEffect.NavigateToEditProfile -> { + navigator.navigateToEditProfile() + } + + SettingContract.SideEffect.NavigateToEditBirthday -> { + navigator.navigateToEditBirthDay() + } + is SettingContract.SideEffect.OpenWebView -> { - navigator.navigateTo("${WebViewDestination.WebView.route}/${Uri.encode(sideEffect.url)}") + navigator.navigateToWebView(Uri.encode(sideEffect.url)) } - else -> {} } } diff --git a/feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt b/feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt index 621d7904..03f79fd0 100644 --- a/feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt +++ b/feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt @@ -46,9 +46,7 @@ fun SettingRoute( SettingScreen( state = state, onNavigateToEditProfile = { - viewModel.onAction( - SettingContract.Action.NavigateToEditProfile, - ) + viewModel.onAction(SettingContract.Action.NavigateToEditProfile) }, onBackClick = { viewModel.onAction(SettingContract.Action.PreviousStep) }, onInquiryClick = { diff --git a/feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt b/feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt index 1dcbd1d7..2e0773c0 100644 --- a/feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt +++ b/feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt @@ -2,7 +2,6 @@ package com.yapp.setting import android.util.Log import androidx.lifecycle.viewModelScope -import com.yapp.common.navigation.destination.SettingDestination import com.yapp.datastore.UserPreferences import com.yapp.domain.repository.UserInfoRepository import com.yapp.ui.base.BaseViewModel @@ -49,8 +48,8 @@ class SettingViewModel @Inject constructor( } } - private fun navigateToEditProfile() = intent { - emitSideEffect(SettingContract.SideEffect.Navigate(SettingDestination.EditProfile.route)) + private fun navigateToEditProfile() { + emitSideEffect(SettingContract.SideEffect.NavigateToEditProfile) } private fun openWebView(url: String) { diff --git a/feature/splash/src/main/java/com/yapp/splash/SplashContract.kt b/feature/splash/src/main/java/com/yapp/splash/SplashContract.kt index 5cebff65..d83dff65 100644 --- a/feature/splash/src/main/java/com/yapp/splash/SplashContract.kt +++ b/feature/splash/src/main/java/com/yapp/splash/SplashContract.kt @@ -9,6 +9,8 @@ sealed class SplashContract { ) : UiState sealed class SideEffect : com.yapp.ui.base.SideEffect { - data class Navigate(val route: String) : SideEffect() + data object NavigateToHome : SideEffect() + + data object NavigateToOnboarding : SideEffect() } } diff --git a/feature/splash/src/main/java/com/yapp/splash/SplashNavGraph.kt b/feature/splash/src/main/java/com/yapp/splash/SplashNavGraph.kt new file mode 100644 index 00000000..5a8e0e33 --- /dev/null +++ b/feature/splash/src/main/java/com/yapp/splash/SplashNavGraph.kt @@ -0,0 +1,14 @@ +package com.yapp.splash + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.yapp.common.navigation.OrbitNavigator +import com.yapp.common.navigation.route.SplashRoute + +fun NavGraphBuilder.splashScreen( + navigator: OrbitNavigator, +) { + composable { + SplashRoute(navigator) + } +} diff --git a/feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt b/feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt index 152edd15..272dea8a 100644 --- a/feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt +++ b/feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt @@ -19,8 +19,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.navOptions import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.SplashDestination +import com.yapp.common.navigation.route.SplashRoute import com.yapp.designsystem.theme.OrbitTheme import kotlinx.coroutines.flow.collectLatest @@ -35,11 +36,23 @@ fun SplashRoute( LaunchedEffect(sideEffect) { sideEffect.collectLatest { effect -> when (effect) { - is SplashContract.SideEffect.Navigate -> { - navigator.navigateTo( - route = effect.route, - popUpTo = SplashDestination.Route.route, - inclusive = true, + is SplashContract.SideEffect.NavigateToOnboarding -> { + navigator.navigateToOnboarding( + navOptions = navOptions { + popUpTo(SplashRoute) { + inclusive = true + } + }, + ) + } + + is SplashContract.SideEffect.NavigateToHome -> { + navigator.navigateToHome( + navOptions = navOptions { + popUpTo(SplashRoute) { + inclusive = true + } + }, ) } } diff --git a/feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt b/feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt index 93649d48..db697f4b 100644 --- a/feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt +++ b/feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt @@ -1,8 +1,6 @@ package com.yapp.splash import androidx.lifecycle.viewModelScope -import com.yapp.common.navigation.destination.OnboardingDestination -import com.yapp.common.navigation.destination.TopLevelDestination import com.yapp.datastore.UserPreferences import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -40,12 +38,11 @@ class SplashViewModel @Inject constructor( ) { userId, onboardingCompleted -> Pair(userId, onboardingCompleted) }.collect { (userId, onboardingCompleted) -> - val destination = if (userId != null && onboardingCompleted) { - TopLevelDestination.HOME.route + if (userId != null && onboardingCompleted) { + emitSideEffect(SplashContract.SideEffect.NavigateToHome) } else { - OnboardingDestination.Route.route + emitSideEffect(SplashContract.SideEffect.NavigateToOnboarding) } - emitSideEffect(SplashContract.SideEffect.Navigate(destination)) } } } diff --git a/feature/webview/src/main/java/com/yapp/webview/WebViewNavGraph.kt b/feature/webview/src/main/java/com/yapp/webview/WebViewNavGraph.kt index 039cbcd8..1e8c6458 100644 --- a/feature/webview/src/main/java/com/yapp/webview/WebViewNavGraph.kt +++ b/feature/webview/src/main/java/com/yapp/webview/WebViewNavGraph.kt @@ -1,27 +1,19 @@ package com.yapp.webview import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navArgument -import androidx.navigation.navigation +import androidx.navigation.toRoute import com.yapp.common.navigation.OrbitNavigator -import com.yapp.common.navigation.destination.WebViewDestination +import com.yapp.common.navigation.route.WebViewRoute -fun NavGraphBuilder.webViewNavGraph(navigator: OrbitNavigator) { - navigation( - route = WebViewDestination.Route.route, - startDestination = WebViewDestination.WebView.route, - ) { - composable( - route = "${WebViewDestination.WebView.route}/{url}", - arguments = listOf(navArgument("url") { type = NavType.StringType }), - ) { backStackEntry -> - val url = backStackEntry.arguments?.getString("url") ?: "" - WebViewRoute( - url = url, - navController = navigator.navController, - ) - } +fun NavGraphBuilder.webViewScreen( + navigator: OrbitNavigator, +) { + composable { entry -> + val route = entry.toRoute() + WebViewRoute( + url = route.url, + navController = navigator.navController, + ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73a719e9..51c70bb2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -149,7 +149,6 @@ okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", flexible-bottomsheet = { group = "com.github.skydoves", name = "flexible-bottomsheet-material3", version.ref = "flexible-bottomsheet" } #sentry-android = { group = "io.sentry", name = "sentry-android", version.ref = "sentry-android" } #sentry-compose = { group = "io.sentry", name = "sentry-compose", version.ref = "sentry-compose" } -gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } # Google Libraries firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } @@ -182,6 +181,8 @@ amplitude-analytics = { group = "com.amplitude", name = "analytics-android", ver play-services-ads = { group = "com.google.android.gms", name = "play-services-ads", version.ref = "playServicesAd" } +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } + [plugins] ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }