From 8391331a25deba711dfd78a203107b0f24d960cf Mon Sep 17 00:00:00 2001 From: "Joker.X" Date: Sat, 14 Feb 2026 15:59:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E9=87=8D=E6=9E=84=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=88=B0=20nav3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 8 +- .../main/java/com/joker/kit/Application.kt | 1 - .../main/java/com/joker/kit/MainActivity.kt | 4 +- .../com/joker/kit/MainActivityViewModel.kt | 2 +- .../viewmodel/BaseNetWorkListViewModel.kt | 54 ++-- .../base/viewmodel/BaseNetWorkViewModel.kt | 57 ++--- .../kit/core/base/viewmodel/BaseViewModel.kt | 187 +------------- .../data/repository/UserInfoRepository.kt | 2 +- .../userinfo/UserInfoStoreDataSourceImpl.kt | 2 +- .../joker/kit/core/navigation/AppNavHost.kt | 126 +++++++++ .../joker/kit/core/navigation/AppNavigator.kt | 192 ++++++++++++++ .../BackStackNavigationController.kt | 114 +++++++++ .../kit/core/navigation/NavigationCommand.kt | 101 ++++++++ .../core/navigation/NavigationController.kt | 46 ++++ .../kit/core/navigation/NavigationOptions.kt | 15 ++ .../kit/core/navigation/NavigationParam.kt | 23 ++ .../kit/core/navigation/NavigationResult.kt | 17 ++ .../navigation/NavigationResultKey.kt | 10 +- .../kit/core/navigation/NavigationService.kt | 241 ++++++++++++++++++ .../kit/core/navigation/RefreshResultKey.kt | 23 ++ .../kit/core/navigation/RouteInterceptor.kt | 48 ++++ .../kit/core/navigation/auth/AuthRoutes.kt | 20 ++ .../kit/core/navigation/demo/DemoNavigator.kt | 87 +++++++ .../navigation/demo}/DemoResultKey.kt | 18 +- .../kit/core/navigation/demo/DemoRoutes.kt | 78 ++++++ .../navigation/main}/MainRoutes.kt | 10 +- .../kit/core/navigation/user/UserNavigator.kt | 20 ++ .../kit/core/navigation/user/UserRoutes.kt | 20 ++ .../kit/core/network/di/NetworkModule.kt | 2 +- .../network/interceptor/AuthInterceptor.kt | 1 - .../joker/kit/core/state/DemoCounterState.kt | 4 +- .../kit/core/ui/component/empty/Empty.kt | 2 +- .../component/network/BaseNetWorkListView.kt | 2 +- .../ui/component/network/BaseNetWorkView.kt | 2 +- .../joker/kit/core/ui/component/text/Text.kt | 8 +- .../kit/feature/auth/navigation/AuthGraph.kt | 20 +- .../feature/auth/navigation/AuthNavigation.kt | 21 -- .../kit/feature/auth/view/LoginScreen.kt | 3 +- .../feature/auth/viewmodel/LoginViewModel.kt | 10 +- .../demo/navigation/DatabaseNavigation.kt | 21 -- .../kit/feature/demo/navigation/DemoGraph.kt | 55 ++-- .../demo/navigation/LocalStorageNavigation.kt | 21 -- .../navigation/NavigationResultNavigation.kt | 21 -- .../NavigationWithArgsNavigation.kt | 21 -- .../demo/navigation/NetworkDemoNavigation.kt | 21 -- .../navigation/NetworkListDemoNavigation.kt | 21 -- .../navigation/NetworkRequestNavigation.kt | 21 -- .../navigation/StateManagementNavigation.kt | 21 -- .../kit/feature/demo/view/DatabaseScreen.kt | 3 +- .../feature/demo/view/LocalStorageScreen.kt | 7 +- .../demo/view/NavigationResultScreen.kt | 3 +- .../demo/view/NavigationWithArgsScreen.kt | 12 +- .../feature/demo/view/NetworkDemoScreen.kt | 3 +- .../demo/view/NetworkListDemoScreen.kt | 3 +- .../feature/demo/view/NetworkRequestScreen.kt | 3 +- .../demo/view/StateManagementScreen.kt | 3 +- .../demo/viewmodel/DatabaseViewModel.kt | 8 +- .../demo/viewmodel/LocalStorageViewModel.kt | 10 +- .../viewmodel/NavigationResultViewModel.kt | 16 +- .../viewmodel/NavigationWithArgsViewModel.kt | 47 ++-- .../demo/viewmodel/NetworkDemoViewModel.kt | 8 +- .../viewmodel/NetworkListDemoViewModel.kt | 8 - .../demo/viewmodel/NetworkRequestViewModel.kt | 11 +- .../viewmodel/StateManagementViewModel.kt | 8 +- .../kit/feature/main/component/DemoCard.kt | 4 +- .../kit/feature/main/data/DemoCardData.kt | 33 ++- .../kit/feature/main/model/DemoCardInfo.kt | 11 +- .../kit/feature/main/navigation/MainGraph.kt | 21 +- .../feature/main/navigation/MainNavigation.kt | 26 -- .../kit/feature/main/view/CoreDemoScreen.kt | 2 +- .../joker/kit/feature/main/view/MainScreen.kt | 22 +- .../feature/main/view/NavigationDemoScreen.kt | 15 +- .../main/viewmodel/CoreDemoViewModel.kt | 35 +-- .../feature/main/viewmodel/MainViewModel.kt | 11 +- .../main/viewmodel/NavigationDemoViewModel.kt | 60 +++-- .../kit/feature/user/navigation/UserGraph.kt | 20 +- .../feature/user/navigation/UserNavigation.kt | 21 -- .../kit/feature/user/view/UserInfoScreen.kt | 5 +- .../user/viewmodel/UserInfoViewModel.kt | 10 +- .../com/joker/kit/navigation/AppNavHost.kt | 82 ------ .../com/joker/kit/navigation/AppNavigator.kt | 176 ------------- .../joker/kit/navigation/NavigationEvent.kt | 54 ---- .../joker/kit/navigation/RefreshResultKey.kt | 23 -- .../joker/kit/navigation/RouteInterceptor.kt | 74 ------ .../extension/NavigationResultExt.kt | 42 --- .../joker/kit/navigation/routes/AuthRoutes.kt | 16 -- .../joker/kit/navigation/routes/DemoRoutes.kt | 42 --- .../joker/kit/navigation/routes/UserRoutes.kt | 16 -- .../res/drawable/ic_launcher_foreground.xml | 59 ++--- .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 4 +- app/src/main/res/values-en/strings.xml | 2 +- gradle.properties | 3 +- gradle/gradle-daemon-jvm.properties | 13 + gradle/libs.versions.toml | 22 +- 95 files changed, 1546 insertions(+), 1359 deletions(-) create mode 100644 app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/BackStackNavigationController.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationCommand.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationController.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationOptions.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationParam.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationResult.kt rename app/src/main/java/com/joker/kit/{ => core}/navigation/NavigationResultKey.kt (80%) create mode 100644 app/src/main/java/com/joker/kit/core/navigation/NavigationService.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/RefreshResultKey.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/RouteInterceptor.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/auth/AuthRoutes.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/demo/DemoNavigator.kt rename app/src/main/java/com/joker/kit/{navigation/results => core/navigation/demo}/DemoResultKey.kt (62%) create mode 100644 app/src/main/java/com/joker/kit/core/navigation/demo/DemoRoutes.kt rename app/src/main/java/com/joker/kit/{navigation/routes => core/navigation/main}/MainRoutes.kt (55%) create mode 100644 app/src/main/java/com/joker/kit/core/navigation/user/UserNavigator.kt create mode 100644 app/src/main/java/com/joker/kit/core/navigation/user/UserRoutes.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/auth/navigation/AuthNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/DatabaseNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/LocalStorageNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationResultNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationWithArgsNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkDemoNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkListDemoNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkRequestNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/demo/navigation/StateManagementNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/main/navigation/MainNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/feature/user/navigation/UserNavigation.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/AppNavHost.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/AppNavigator.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/NavigationEvent.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/RefreshResultKey.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/RouteInterceptor.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/extension/NavigationResultExt.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/routes/AuthRoutes.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/routes/DemoRoutes.kt delete mode 100644 app/src/main/java/com/joker/kit/navigation/routes/UserRoutes.kt create mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5911f3f..f3b1a8b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -150,7 +150,9 @@ dependencies { implementation(libs.coil.compose) // 导航组件 - implementation(libs.navigation.compose) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) + implementation(libs.androidx.lifecycle.viewmodel.navigation3) // 序列化 implementation(libs.kotlinx.serialization.json) @@ -178,7 +180,7 @@ dependencies { // 依赖注入 (Hilt + Navigation) implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) - implementation(libs.hilt.navigation.compose) + implementation(libs.hilt.lifecycle.viewmodel.compose) androidTestImplementation(libs.hilt.android.testing) kspAndroidTest(libs.hilt.android.compiler) @@ -203,4 +205,4 @@ dependencies { androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/joker/kit/Application.kt b/app/src/main/java/com/joker/kit/Application.kt index bcfaf3b..9bdcf40 100644 --- a/app/src/main/java/com/joker/kit/Application.kt +++ b/app/src/main/java/com/joker/kit/Application.kt @@ -5,7 +5,6 @@ import android.content.res.Configuration import com.joker.kit.core.state.UserState import com.joker.kit.core.util.storage.MMKVUtils import com.joker.kit.core.util.toast.ToastUtils -import com.joker.kit.BuildConfig import dagger.hilt.android.HiltAndroidApp import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/com/joker/kit/MainActivity.kt b/app/src/main/java/com/joker/kit/MainActivity.kt index eb93344..1bf8494 100644 --- a/app/src/main/java/com/joker/kit/MainActivity.kt +++ b/app/src/main/java/com/joker/kit/MainActivity.kt @@ -6,8 +6,8 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.joker.kit.core.designsystem.theme.AppTheme -import com.joker.kit.navigation.AppNavHost -import com.joker.kit.navigation.AppNavigator +import com.joker.kit.core.navigation.AppNavHost +import com.joker.kit.core.navigation.AppNavigator import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject diff --git a/app/src/main/java/com/joker/kit/MainActivityViewModel.kt b/app/src/main/java/com/joker/kit/MainActivityViewModel.kt index b1e3143..64ef4ed 100644 --- a/app/src/main/java/com/joker/kit/MainActivityViewModel.kt +++ b/app/src/main/java/com/joker/kit/MainActivityViewModel.kt @@ -11,4 +11,4 @@ import javax.inject.Inject */ @HiltViewModel class MainActivityViewModel @Inject constructor( -) : ViewModel() {} \ No newline at end of file +) : ViewModel() \ No newline at end of file diff --git a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkListViewModel.kt b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkListViewModel.kt index b1d643d..97c1be7 100644 --- a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkListViewModel.kt +++ b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkListViewModel.kt @@ -1,19 +1,17 @@ package com.joker.kit.core.base.viewmodel -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope -import androidx.navigation.NavBackStackEntry import com.joker.kit.core.base.state.BaseNetWorkListUiState import com.joker.kit.core.base.state.LoadMoreState import com.joker.kit.core.model.network.NetworkPageData import com.joker.kit.core.model.network.NetworkResponse +import com.joker.kit.core.navigation.NavigationResultKey +import com.joker.kit.core.navigation.RefreshResult +import com.joker.kit.core.navigation.RefreshResultKey +import com.joker.kit.core.navigation.resultEvents import com.joker.kit.core.result.ResultHandler import com.joker.kit.core.result.asResult -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.NavigationResultKey -import com.joker.kit.navigation.RefreshResultKey +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -28,14 +26,16 @@ import kotlinx.coroutines.launch * 封装了常见的列表操作逻辑,简化子类实现 * * @param T 列表项数据类型 - * @param navigator 导航控制器 - * @param userState 用户状态 * @author Joker.X */ -abstract class BaseNetWorkListViewModel( - navigator: AppNavigator, - userState: UserState -) : BaseViewModel(navigator, userState) { +abstract class BaseNetWorkListViewModel : BaseViewModel() { + /** + * 刷新结果监听任务 + * + * 用于保证只注册一次刷新结果监听,避免重复 collect 导致重复刷新和内存浪费。 + * 当该任务不为 null 时,表示当前 ViewModel 已经建立监听。 + */ + private var refreshObserveJob: Job? = null /** * 当前页码 @@ -274,34 +274,26 @@ abstract class BaseNetWorkListViewModel( /** * 视图层调用此方法,监听页面刷新信号(基于 NavigationResultKey)。 * - * @param backStackEntry 当前页面的 NavBackStackEntry * @param key 刷新结果的类型安全 Key,默认使用全局的 [RefreshResultKey] * * 用法:在 Composable 中调用 * ```kotlin - * val backStackEntry = navController.currentBackStackEntry - * LaunchedEffect(backStackEntry) { - * viewModel.observeRefreshState(backStackEntry) - * } + * viewModel.observeRefreshState() * ``` * * 只需调用一次,自动去重和解绑,无内存泄漏。 - * 语义等价于旧方案中的 "refresh" 布尔标记。 + * 当 [RefreshResult.refresh] 为 true 时触发刷新。 */ fun observeRefreshState( - backStackEntry: NavBackStackEntry?, - key: NavigationResultKey = RefreshResultKey + key: NavigationResultKey = RefreshResultKey ) { - if (backStackEntry == null) return - val owner: LifecycleOwner = backStackEntry - backStackEntry.savedStateHandle - .getLiveData(key.key) - .observe(owner, Observer { value -> - if (value) { + if (refreshObserveJob != null) return + refreshObserveJob = viewModelScope.launch { + resultEvents(key).collect { refreshResult -> + if (refreshResult.refresh == true) { onRefresh() - // 只刷新一次 - backStackEntry.savedStateHandle[key.key] = false } - }) + } + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkViewModel.kt b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkViewModel.kt index 5f046c1..933ee37 100644 --- a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkViewModel.kt +++ b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkViewModel.kt @@ -1,18 +1,15 @@ package com.joker.kit.core.base.viewmodel -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.Observer -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import androidx.navigation.NavBackStackEntry import com.joker.kit.core.base.state.BaseNetWorkUiState import com.joker.kit.core.model.network.NetworkResponse +import com.joker.kit.core.navigation.NavigationResultKey +import com.joker.kit.core.navigation.RefreshResult +import com.joker.kit.core.navigation.RefreshResultKey +import com.joker.kit.core.navigation.resultEvents import com.joker.kit.core.result.ResultHandler import com.joker.kit.core.result.asResult -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.NavigationResultKey -import com.joker.kit.navigation.RefreshResultKey +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,16 +24,16 @@ import kotlinx.coroutines.launch * 支持自动从SavedStateHandle获取路由参数ID * * @param T 数据类型 - * @param navigator 导航控制器 - * @param userState 用户状态 - * @param savedStateHandle 保存状态句柄,用于获取路由参数 * @author Joker.X */ -abstract class BaseNetWorkViewModel( - navigator: AppNavigator, - userState: UserState, - protected val savedStateHandle: SavedStateHandle? = null, -) : BaseViewModel(navigator, userState) { +abstract class BaseNetWorkViewModel : BaseViewModel() { + /** + * 刷新结果监听任务 + * + * 用于保证只注册一次刷新结果监听,避免重复 collect 导致重复请求。 + * 当该任务不为 null 时,表示当前 ViewModel 已经建立监听。 + */ + private var refreshObserveJob: Job? = null /** * 通用网络请求UI状态 @@ -176,34 +173,26 @@ abstract class BaseNetWorkViewModel( /** * 视图层调用此方法,监听页面刷新信号(基于 NavigationResultKey)。 * - * @param backStackEntry 当前页面的 NavBackStackEntry * @param key 刷新结果的类型安全 Key,默认使用全局的 [RefreshResultKey] * * 用法:在 Composable 中调用 * ```kotlin - * val backStackEntry = navController.currentBackStackEntry - * LaunchedEffect(backStackEntry) { - * viewModel.observeRefreshState(backStackEntry) - * } + * viewModel.observeRefreshState() * ``` * * 只需调用一次,自动去重和解绑,无内存泄漏。 - * 语义等价于旧方案中的 "refresh" 布尔标记。 + * 当 [RefreshResult.refresh] 为 true 时触发刷新。 */ fun observeRefreshState( - backStackEntry: NavBackStackEntry?, - key: NavigationResultKey = RefreshResultKey + key: NavigationResultKey = RefreshResultKey ) { - if (backStackEntry == null) return - val owner: LifecycleOwner = backStackEntry - backStackEntry.savedStateHandle - .getLiveData(key.key) - .observe(owner, Observer { value -> - if (value) { + if (refreshObserveJob != null) return + refreshObserveJob = viewModelScope.launch { + resultEvents(key).collect { refreshResult -> + if (refreshResult.refresh == true) { executeRequest() - // 只刷新一次 - backStackEntry.savedStateHandle[key.key] = false } - }) + } + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseViewModel.kt b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseViewModel.kt index b7b63cb..30cbffb 100644 --- a/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/joker/kit/core/base/viewmodel/BaseViewModel.kt @@ -1,193 +1,10 @@ package com.joker.kit.core.base.viewmodel import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.NavOptions -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.NavigationResultKey -import com.joker.kit.navigation.RouteInterceptor -import kotlinx.coroutines.launch /** - * 基础ViewModel(类型安全版本) + * 基础 ViewModel * - * 提供所有ViewModel通用的功能: - * 1. 类型安全的导航 - * 2. 路由拦截(登录检查) - * 3. 类型安全的结果返回 - * - * 使用示例: - * ```kotlin - * class MyViewModel @Inject constructor( - * navigator: AppNavigator, - * userState: UserState - * ) : BaseViewModel(navigator, userState) { - * fun onItemClick(id: Long) { - * navigate(GoodsRoutes.Detail(goodsId = id)) - * } - * - * fun onSuccess() { - * navigateBack(RefreshResult) - * } - * } - * ``` - * - * @param navigator 导航控制器 - * @param userState 应用状态 - * @param routeInterceptor 路由拦截器 * @author Joker.X */ -abstract class BaseViewModel( - protected val navigator: AppNavigator, - protected val userState: UserState, - protected val routeInterceptor: RouteInterceptor = RouteInterceptor() -) : ViewModel() { - - // ==================== 基础导航方法 ==================== - - /** - * 导航到指定路由(类型安全) - * 自动处理登录拦截逻辑 - * - * @param route 目标路由对象(必须是 @Serializable) - * @param navOptions 导航选项(可选) - * - * 使用示例: - * ```kotlin - * // 简单导航 - * navigate(MainRoutes.Home) - * - * // 带参数导航 - * navigate(GoodsRoutes.Detail(goodsId = 123)) - * - * // 带 NavOptions - * navigate(UserRoutes.Profile, navOptions) - * ``` - * - * @author Joker.X - */ - fun navigate(route: Any, navOptions: NavOptions? = null) { - viewModelScope.launch { - val targetRoute = checkRouteInterception(route) - navigator.navigateTo(targetRoute, navOptions) - } - } - - /** - * 导航到指定路由并关闭当前页面 - * 自动处理登录拦截逻辑 - * - * @param route 目标路由对象 - * @param currentRoute 当前页面路由对象,将被关闭 - * - * 使用示例: - * ```kotlin - * navigateAndCloseCurrent( - * route = MainRoutes.Home, - * currentRoute = AuthRoutes.Login - * ) - * ``` - * - * @author Joker.X - */ - fun navigateAndCloseCurrent(route: Any, currentRoute: Any) { - viewModelScope.launch { - val targetRoute = checkRouteInterception(route) - val navOptions = NavOptions.Builder() - .setPopUpTo( - route = currentRoute, - inclusive = true, // 设为true表示当前页面也会被弹出 - saveState = false // 不保存状态 - ) - .build() - navigator.navigateTo(targetRoute, navOptions) - } - } - - // ==================== 返回导航方法 ==================== - - /** - * 返回上一页 - * - * 使用示例: - * ```kotlin - * navigateBack() - * ``` - * - * @author Joker.X - */ - fun navigateBack() { - viewModelScope.launch { - navigator.navigateBack() - } - } - - /** - * 返回上一页并携带类型安全的结果(使用 NavigationResultKey) - * - * 这是 V3.2 版本的最终方案,实现了端到端的类型安全。 - * - * @param key 类型安全的结果 Key - * @param result 要传递的结果对象 - * - * 使用示例: - * ```kotlin - * // 1. 定义返回结果数据类型 - * @Serializable - * data class Address(val id: Long, val fullAddress: String) - * - * // 2. 定义 ResultKey - * object SelectAddressResultKey : NavigationResultKey
- * - * // 3. 返回时携带结果 - * popBackStackWithResult(SelectAddressResultKey, address) - * ``` - * - * @author Joker.X - */ - fun popBackStackWithResult(key: NavigationResultKey, result: T) { - viewModelScope.launch { - navigator.popBackStackWithResult(key, result) - } - } - - /** - * 返回到指定路由 - * - * @param route 目标路由对象 - * @param inclusive 是否包含目标路由本身 - * - * 使用示例: - * ```kotlin - * // 返回到主页并保留主页 - * navigateBackTo(MainRoutes.Main, inclusive = false) - * ``` - * - * @author Joker.X - */ - fun navigateBackTo(route: Any, inclusive: Boolean = false) { - viewModelScope.launch { - navigator.navigateBackTo(route, inclusive) - } - } - - // ==================== 内部方法 ==================== - - /** - * 检查路由是否需要登录拦截(类型安全) - * - * @param route 目标路由对象 - * @return 如果需要拦截返回登录页面路由,否则返回原路由 - * @author Joker.X - */ - private fun checkRouteInterception(route: Any): Any { - return if (routeInterceptor.requiresLogin(route) && !userState.isLoggedIn.value) { - // 需要登录但未登录,跳转到登录页面 - routeInterceptor.getLoginRoute() - } else { - // 不需要登录或已登录,正常跳转 - route - } - } -} +abstract class BaseViewModel : ViewModel() diff --git a/app/src/main/java/com/joker/kit/core/data/repository/UserInfoRepository.kt b/app/src/main/java/com/joker/kit/core/data/repository/UserInfoRepository.kt index 6de0542..fa5fd54 100644 --- a/app/src/main/java/com/joker/kit/core/data/repository/UserInfoRepository.kt +++ b/app/src/main/java/com/joker/kit/core/data/repository/UserInfoRepository.kt @@ -1,8 +1,8 @@ package com.joker.kit.core.data.repository -import com.joker.kit.core.network.datasource.userinfo.UserInfoNetworkDataSource import com.joker.kit.core.model.entity.User import com.joker.kit.core.model.network.NetworkResponse +import com.joker.kit.core.network.datasource.userinfo.UserInfoNetworkDataSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/java/com/joker/kit/core/datastore/datasource/userinfo/UserInfoStoreDataSourceImpl.kt b/app/src/main/java/com/joker/kit/core/datastore/datasource/userinfo/UserInfoStoreDataSourceImpl.kt index c9af6c6..240118b 100644 --- a/app/src/main/java/com/joker/kit/core/datastore/datasource/userinfo/UserInfoStoreDataSourceImpl.kt +++ b/app/src/main/java/com/joker/kit/core/datastore/datasource/userinfo/UserInfoStoreDataSourceImpl.kt @@ -57,7 +57,7 @@ class UserInfoStoreDataSourceImpl @Inject constructor() : UserInfoStoreDataSourc * @author Joker.X */ override suspend fun updateUserInfo(updates: Map) { - val currentUser = getUserInfo() ?: return + getUserInfo() ?: return val userJson = MMKVUtils.getString(KEY_USER_INFO, "") if (userJson.isEmpty()) return diff --git a/app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt b/app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt new file mode 100644 index 0000000..bf726ee --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt @@ -0,0 +1,126 @@ +package com.joker.kit.core.navigation + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.IntOffset +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.ui.NavDisplay +import com.joker.kit.core.navigation.main.MainRoutes +import com.joker.kit.feature.auth.navigation.authGraph +import com.joker.kit.feature.demo.navigation.demoGraph +import com.joker.kit.feature.main.navigation.mainGraph +import com.joker.kit.feature.user.navigation.userGraph + +/** + * 页面切换动画时长(毫秒) + */ +private const val NAV_ANIMATION_DURATION = 300 + +/** + * 页面切换动画规范 + */ +private val NAV_ANIMATION_SPEC: FiniteAnimationSpec = + tween(durationMillis = NAV_ANIMATION_DURATION) + +/** + * 应用导航宿主 + * + * @param navigator 导航管理器 + * @param modifier 修饰符 + * @author Joker.X + */ +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun AppNavHost( + navigator: AppNavigator, + modifier: Modifier = Modifier, +) { + // 创建应用级回退栈,首个页面固定为主页面。 + val backStack = rememberNavBackStack(MainRoutes.Main) + // 基于当前回退栈构建导航控制器,供 AppNavigator 分发命令时使用。 + val navigationController = rememberBackStackNavigationController(backStack, navigator) + + // 在组合生命周期内绑定/解绑导航控制器,确保导航命令总是指向当前有效宿主。 + DisposableEffect(navigationController) { + // 绑定到 AppNavigator,接收全局导航命令。 + navigator.attachController(navigationController) + // 绑定到全局导航服务,支持业务层直接调用 navigate(...)。 + NavigationService.bind(navigator) + onDispose { + // 宿主销毁时先解绑导航服务,避免持有失效导航器引用。 + NavigationService.unbind(navigator) + // 最后从 AppNavigator 注销控制器,防止后续命令误发到旧宿主。 + navigator.detachController(navigationController) + } + } + + SharedTransitionLayout { + NavDisplay( + backStack = backStack, + modifier = modifier, + onBack = { navigationController.navigateBack() }, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ), + transitionSpec = { createForwardTransition() }, + popTransitionSpec = { createBackwardTransition() }, + predictivePopTransitionSpec = { createBackwardTransition() }, + entryProvider = appEntryProvider(), + ) + } +} + +/** + * 创建前进导航动画(右入左出) + * + * @return 前进导航动画 + * @author Joker.X + */ +private fun createForwardTransition() = slideInHorizontally( + initialOffsetX = { it }, + animationSpec = NAV_ANIMATION_SPEC, +) togetherWith slideOutHorizontally( + targetOffsetX = { -it }, + animationSpec = NAV_ANIMATION_SPEC, +) + +/** + * 创建返回导航动画(左入右出) + * + * @return 返回导航动画 + * @author Joker.X + */ +private fun createBackwardTransition() = slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = NAV_ANIMATION_SPEC, +) togetherWith slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = NAV_ANIMATION_SPEC, +) + +/** + * 构建应用级路由注册器 + * + * 按模块聚合 graph,避免全部 entry 混在同一个函数中。 + * + * @return 应用级 EntryProvider + * @author Joker.X + */ +private fun appEntryProvider() = entryProvider { + mainGraph() + demoGraph() + authGraph() + userGraph() +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt b/app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt new file mode 100644 index 0000000..f4107b2 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt @@ -0,0 +1,192 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.state.UserState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +/** + * 导航管理器 + * + * 提供给 ViewModel 的统一导航入口: + * 1. ViewModel 直接调用导航 API + * 2. AppNavHost 注册 BackStack 控制器 + * 3. 导航结果通过 resultEvents 分发 + * + * @author Joker.X + */ +@Singleton +class AppNavigator @Inject constructor( + private val userState: UserState, +) { + /** + * 导航控制器访问锁 + */ + private val lock = Any() + + /** + * 当前活跃的导航控制器 + */ + private var controller: NavigationController? = null + + /** + * 控制器未注册时缓存的导航命令队列 + */ + private val pendingCommands = ArrayDeque() + + /** + * 页面结果事件流 + */ + private val _resultEvents = MutableSharedFlow(extraBufferCapacity = 32) + + /** + * 路由拦截器 + * + * 在统一导航入口做登录拦截,避免不同调用入口出现行为不一致。 + */ + private val routeInterceptor: RouteInterceptor = RouteInterceptor() + + /** + * 注册导航控制器 + * + * @author Joker.X + */ + internal fun attachController(navigationController: NavigationController) { + synchronized(lock) { + controller = navigationController + while (pendingCommands.isNotEmpty()) { + pendingCommands.removeFirst().execute(navigationController) + } + } + } + + /** + * 注销导航控制器 + * + * @author Joker.X + */ + internal fun detachController(navigationController: NavigationController) { + synchronized(lock) { + if (controller === navigationController) { + controller = null + } + } + } + + /** + * 导航到指定路由 + * + * @param route 类型安全路由对象 + * @param navOptions 导航选项(可选) + * @author Joker.X + */ + fun navigateTo(route: NavKey, navOptions: NavigationOptions? = null) { + val targetRoute = resolveTargetRoute(route) + executeOrEnqueue(NavigationCommand.NavigateTo(targetRoute, navOptions)) + } + + /** + * 返回上一页 + * + * @author Joker.X + */ + fun navigateBack() { + executeOrEnqueue(NavigationCommand.NavigateUp) + } + + /** + * 返回上一页并携带类型安全结果 + * + * @param key 类型安全结果 Key + * @param result 返回结果 + * @author Joker.X + */ + fun popBackStackWithResult(key: NavigationResultKey, result: T) { + executeOrEnqueue(NavigationCommand.PopBackStackWithResult(key, result)) + } + + /** + * 返回到指定路由 + * + * @param route 目标路由对象 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ + fun navigateBackTo(route: NavKey, inclusive: Boolean = false) { + executeOrEnqueue(NavigationCommand.NavigateBackTo(route, inclusive)) + } + + /** + * 监听某个 ResultKey 对应的结果流 + * + * @param key 类型安全结果 Key + * @return 强类型结果流 + * @author Joker.X + */ + fun resultEvents(key: NavigationResultKey): Flow { + return _resultEvents + .filter { it.key == key.key } + .map { key.deserialize(it.rawValue) } + } + + /** + * 分发回传结果事件 + * + * @param key 类型安全结果 Key + * @param result 返回结果 + * @author Joker.X + */ + internal fun dispatchResult(key: NavigationResultKey, result: T) { + val rawValue = key.serialize(result) + _resultEvents.tryEmit(ResultEvent(key = key.key, rawValue = rawValue)) + } + + /** + * 执行导航命令(若控制器未注册则先缓存) + * + * @author Joker.X + */ + private fun executeOrEnqueue(command: NavigationCommand) { + synchronized(lock) { + val currentController = controller + if (currentController != null) { + command.execute(currentController) + } else { + pendingCommands.addLast(command) + } + } + } + + /** + * 解析最终跳转路由 + * + * 当目标路由需要登录且当前未登录时,返回登录页面路由。 + * + * @param route 原始目标路由 + * @return 实际执行跳转的路由 + * @author Joker.X + */ + private fun resolveTargetRoute(route: NavKey): NavKey { + return if (routeInterceptor.requiresLogin(route) && !userState.isLoggedIn.value) { + routeInterceptor.getLoginRoute() + } else { + route + } + } +} + +/** + * 导航回传结果事件 + * + * @param key 结果 Key + * @param rawValue 序列化后的结果值 + * @author Joker.X + */ +private data class ResultEvent( + val key: String, + val rawValue: Any, +) diff --git a/app/src/main/java/com/joker/kit/core/navigation/BackStackNavigationController.kt b/app/src/main/java/com/joker/kit/core/navigation/BackStackNavigationController.kt new file mode 100644 index 0000000..1e8f17c --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/BackStackNavigationController.kt @@ -0,0 +1,114 @@ +package com.joker.kit.core.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey + +/** + * 创建 BackStack 导航控制器 + * + * @param backStack 回退栈 + * @param navigator 导航管理器 + * @return BackStack 导航控制器 + * @author Joker.X + */ +@Composable +internal fun rememberBackStackNavigationController( + backStack: NavBackStack, + navigator: AppNavigator, +): NavigationController { + return remember(backStack, navigator) { + BackStackNavigationController(backStack = backStack, navigator = navigator) + } +} + +/** + * 基于 BackStack 的导航控制器实现 + * + * @author Joker.X + */ +private class BackStackNavigationController( + /** + * 回退栈 + */ + private val backStack: NavBackStack, + /** + * 导航管理器 + */ + private val navigator: AppNavigator, +) : NavigationController { + + /** + * 导航到目标页面 + * + * @param route 目标路由 + * @param navOptions 导航选项 + * @author Joker.X + */ + override fun navigateTo(route: NavKey, navOptions: NavigationOptions?) { + val popUpToRoute = navOptions?.popUpToRoute + if (popUpToRoute != null) { + backStack.popUpTo(route = popUpToRoute, inclusive = navOptions.inclusive) + } + backStack.add(route) + } + + /** + * 返回上一页 + * + * @author Joker.X + */ + override fun navigateBack() { + if (backStack.size > 1) { + backStack.removeLastOrNull() + } + } + + /** + * 返回到指定页面 + * + * @param route 目标路由 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ + override fun navigateBackTo(route: NavKey, inclusive: Boolean) { + backStack.popUpTo(route = route, inclusive = inclusive) + } + + /** + * 回退并携带结果 + * + * @param key 结果 Key + * @param result 回传结果 + * @author Joker.X + */ + override fun popBackStackWithResult(key: NavigationResultKey, result: T) { + navigator.dispatchResult(key = key, result = result) + navigateBack() + } +} + +/** + * BackStack 按路由弹栈 + * + * @param route 目标路由 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ +private fun NavBackStack.popUpTo(route: NavKey, inclusive: Boolean) { + val targetIndex = indexOfLast { it == route } + if (targetIndex == -1) return + + val removeFromIndex = if (inclusive) targetIndex else targetIndex + 1 + if (removeFromIndex >= size) return + + if (removeFromIndex == 0) { + if (size > 1) { + subList(1, size).clear() + } + return + } + + subList(removeFromIndex, size).clear() +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationCommand.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationCommand.kt new file mode 100644 index 0000000..fc574a3 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationCommand.kt @@ -0,0 +1,101 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey + +/** + * 导航命令 + * + * @author Joker.X + */ +internal sealed interface NavigationCommand { + /** + * 执行导航命令 + * + * @param controller 导航控制器 + * @author Joker.X + */ + fun execute(controller: NavigationController) + + /** + * 导航到指定路由命令 + * + * @property route 目标路由 + * @property navOptions 导航选项 + * @author Joker.X + */ + data class NavigateTo( + val route: NavKey, + val navOptions: NavigationOptions?, + ) : NavigationCommand { + /** + * 执行命令 + * + * @param controller 导航控制器 + * @author Joker.X + */ + override fun execute(controller: NavigationController) { + controller.navigateTo(route, navOptions) + } + } + + /** + * 返回上一页命令 + * + * @author Joker.X + */ + data object NavigateUp : NavigationCommand { + /** + * 执行命令 + * + * @param controller 导航控制器 + * @author Joker.X + */ + override fun execute(controller: NavigationController) { + controller.navigateBack() + } + } + + /** + * 回退到指定路由命令 + * + * @property route 目标路由 + * @property inclusive 是否包含目标路由 + * @author Joker.X + */ + data class NavigateBackTo( + val route: NavKey, + val inclusive: Boolean, + ) : NavigationCommand { + /** + * 执行命令 + * + * @param controller 导航控制器 + * @author Joker.X + */ + override fun execute(controller: NavigationController) { + controller.navigateBackTo(route, inclusive) + } + } + + /** + * 回退并回传结果命令 + * + * @property key 结果 Key + * @property result 结果值 + * @author Joker.X + */ + data class PopBackStackWithResult( + val key: NavigationResultKey, + val result: T, + ) : NavigationCommand { + /** + * 执行命令 + * + * @param controller 导航控制器 + * @author Joker.X + */ + override fun execute(controller: NavigationController) { + controller.popBackStackWithResult(key, result) + } + } +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationController.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationController.kt new file mode 100644 index 0000000..3cbd70c --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationController.kt @@ -0,0 +1,46 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey + +/** + * 导航控制器接口 + * + * 由 AppNavHost 提供 BackStack 实现。 + * + * @author Joker.X + */ +internal interface NavigationController { + /** + * 导航到目标路由 + * + * @param route 目标路由 + * @param navOptions 导航选项 + * @author Joker.X + */ + fun navigateTo(route: NavKey, navOptions: NavigationOptions?) + + /** + * 返回上一页 + * + * @author Joker.X + */ + fun navigateBack() + + /** + * 回退到指定路由 + * + * @param route 目标路由 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ + fun navigateBackTo(route: NavKey, inclusive: Boolean) + + /** + * 回退并携带结果 + * + * @param key 结果 Key + * @param result 结果值 + * @author Joker.X + */ + fun popBackStackWithResult(key: NavigationResultKey, result: T) +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationOptions.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationOptions.kt new file mode 100644 index 0000000..bada1fd --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationOptions.kt @@ -0,0 +1,15 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey + +/** + * 导航选项 + * + * @param popUpToRoute 回退栈弹出到的目标路由 + * @param inclusive 是否包含目标路由本身 + * @author Joker.X + */ +data class NavigationOptions( + val popUpToRoute: NavKey? = null, + val inclusive: Boolean = false, +) diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationParam.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationParam.kt new file mode 100644 index 0000000..27653dd --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationParam.kt @@ -0,0 +1,23 @@ +package com.joker.kit.core.navigation + +/** + * 公共导航参数定义 + * + * 用于沉淀跨模块可复用的导航参数模型,避免在各业务模块重复声明。 + * + * @author Joker.X + */ +/** + * 通用 ID 参数 + * + * 适用于仅需要传递单个 ID 的页面跳转场景。 + * + * @property id 通用业务 ID + * @author Joker.X + */ +data class IdParam( + /** + * 通用业务 ID + */ + val id: Long, +) diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationResult.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationResult.kt new file mode 100644 index 0000000..3a30eec --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationResult.kt @@ -0,0 +1,17 @@ +package com.joker.kit.core.navigation + +/** + * 公共导航返回结果定义 + * + * 用于沉淀跨模块可复用的页面返回结果模型。 + * + * @author Joker.X + */ +data class RefreshResult( + /** + * 是否需要刷新 + * + * true 表示需要刷新,false 或 null 表示不刷新。 + */ + val refresh: Boolean? = null, +) diff --git a/app/src/main/java/com/joker/kit/navigation/NavigationResultKey.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationResultKey.kt similarity index 80% rename from app/src/main/java/com/joker/kit/navigation/NavigationResultKey.kt rename to app/src/main/java/com/joker/kit/core/navigation/NavigationResultKey.kt index b2c8d61..5de18fb 100644 --- a/app/src/main/java/com/joker/kit/navigation/NavigationResultKey.kt +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationResultKey.kt @@ -1,4 +1,4 @@ -package com.joker.kit.navigation +package com.joker.kit.core.navigation /** * 导航返回结果的类型安全 Key。 @@ -17,7 +17,7 @@ package com.joker.kit.navigation * * 使用示例(接收结果): * ```kotlin - * navController.collectResult(SelectAddressResultKey) { address -> + * navigator.resultEvents(SelectAddressResultKey).collect { address -> * viewModel.onAddressSelected(address) * } * ``` @@ -27,7 +27,7 @@ package com.joker.kit.navigation */ interface NavigationResultKey { /** - * 底层用于 SavedStateHandle 存储的字符串 key。 + * 底层用于结果分发的字符串 key。 * * 默认实现使用 Key 对象自身的完全限定类名,保证全局唯一且无需手写字符串。 */ @@ -35,7 +35,7 @@ interface NavigationResultKey { get() = this::class.java.name /** - * 将结果对象序列化为 SavedStateHandle 可接受的底层类型。 + * 将结果对象序列化为可分发的底层类型。 * * 默认实现为透传(即直接存储原始对象),适用于 Boolean、Int、String 等基础类型。 * 复杂类型可以在具体的 Key 中重写此方法,例如序列化为 JSON 字符串。 @@ -43,7 +43,7 @@ interface NavigationResultKey { fun serialize(value: T): Any = value as Any /** - * 从 SavedStateHandle 中还原结果对象。 + * 从结果分发流中还原结果对象。 * * 默认实现为简单强转,复杂类型的 Key 需要重写以配合 [serialize]。 */ diff --git a/app/src/main/java/com/joker/kit/core/navigation/NavigationService.kt b/app/src/main/java/com/joker/kit/core/navigation/NavigationService.kt new file mode 100644 index 0000000..9fa0a52 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/NavigationService.kt @@ -0,0 +1,241 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey +import kotlinx.coroutines.flow.Flow + +/** + * 全局导航服务 + * + * 统一维护当前可用导航器,并对外提供通用导航能力。 + * 业务层只需调用本文件提供的简写函数,无需关注底层绑定细节。 + * + * @author Joker.X + */ +object NavigationService { + /** + * 当前导航器实例 + */ + @Volatile + private var navigator: AppNavigator? = null + + /** + * 绑定导航器 + * + * @param appNavigator 待绑定的导航器 + * @author Joker.X + */ + fun bind(appNavigator: AppNavigator) { + navigator = appNavigator + } + + /** + * 解绑导航器 + * + * @param appNavigator 待解绑的导航器 + * @author Joker.X + */ + fun unbind(appNavigator: AppNavigator) { + if (navigator === appNavigator) { + navigator = null + } + } + + /** + * 获取当前导航器 + * + * @return 当前导航器实例 + * @throws IllegalStateException 当导航器未绑定时抛出异常 + * @author Joker.X + */ + fun requireNavigator(): AppNavigator { + return navigator ?: error("AppNavigator is not bound") + } + + /** + * 跳转到目标路由 + * + * @param route 目标路由 + * @param navOptions 导航选项 + * @author Joker.X + */ + fun navigate(route: NavKey, navOptions: NavigationOptions? = null) { + requireNavigator().navigateTo(route = route, navOptions = navOptions) + } + + /** + * 跳转到目标路由并关闭当前页面 + * + * 常用于“当前页操作成功后进入下一个页面,并且不允许再返回当前页”的场景。 + * + * @param route 目标路由 + * @param currentRoute 当前路由(将被关闭) + * @author Joker.X + */ + fun navigateAndCloseCurrent(route: NavKey, currentRoute: NavKey) { + val navOptions = NavigationOptions( + popUpToRoute = currentRoute, + inclusive = true, + ) + requireNavigator().navigateTo(route = route, navOptions = navOptions) + } + + /** + * 跳转到目标路由并按条件清理回退栈 + * + * @param route 目标路由 + * @param popUpToRoute 回退栈清理到的目标路由 + * @param inclusive 是否包含 [popUpToRoute] + * @author Joker.X + */ + fun navigateWithPopUpTo(route: NavKey, popUpToRoute: NavKey, inclusive: Boolean = false) { + val navOptions = NavigationOptions( + popUpToRoute = popUpToRoute, + inclusive = inclusive, + ) + requireNavigator().navigateTo(route = route, navOptions = navOptions) + } + + /** + * 返回上一页 + * + * @author Joker.X + */ + fun navigateBack() { + requireNavigator().navigateBack() + } + + /** + * 返回到指定路由 + * + * @param route 目标路由 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ + fun navigateBackTo(route: NavKey, inclusive: Boolean = false) { + requireNavigator().navigateBackTo(route = route, inclusive = inclusive) + } + + /** + * 返回上一页并携带结果 + * + * @param key 结果 Key + * @param result 返回结果 + * @author Joker.X + */ + fun popBackStackWithResult(key: NavigationResultKey, result: T) { + requireNavigator().popBackStackWithResult(key = key, result = result) + } + + /** + * 返回上一页并携带结果(语义化别名) + * + * @param key 结果 Key + * @param result 返回结果 + * @author Joker.X + */ + fun navigateBackWithResult(key: NavigationResultKey, result: T) { + popBackStackWithResult(key = key, result = result) + } + + /** + * 监听指定结果 Key 的结果流 + * + * @param key 结果 Key + * @return 对应结果流 + * @author Joker.X + */ + fun resultEvents(key: NavigationResultKey): Flow { + return requireNavigator().resultEvents(key) + } +} + +/** + * 跳转到目标路由 + * + * @param route 目标路由 + * @param navOptions 导航选项 + * @author Joker.X + */ +fun navigate(route: NavKey, navOptions: NavigationOptions? = null) { + NavigationService.navigate(route = route, navOptions = navOptions) +} + +/** + * 跳转到目标路由并关闭当前页面 + * + * @param route 目标路由 + * @param currentRoute 当前路由(将被关闭) + * @author Joker.X + */ +fun navigateAndCloseCurrent(route: NavKey, currentRoute: NavKey) { + NavigationService.navigateAndCloseCurrent(route = route, currentRoute = currentRoute) +} + +/** + * 跳转到目标路由并按条件清理回退栈 + * + * @param route 目标路由 + * @param popUpToRoute 回退栈清理到的目标路由 + * @param inclusive 是否包含 [popUpToRoute] + * @author Joker.X + */ +fun navigateWithPopUpTo(route: NavKey, popUpToRoute: NavKey, inclusive: Boolean = false) { + NavigationService.navigateWithPopUpTo( + route = route, + popUpToRoute = popUpToRoute, + inclusive = inclusive, + ) +} + +/** + * 返回上一页 + * + * @author Joker.X + */ +fun navigateBack() { + NavigationService.navigateBack() +} + +/** + * 返回到指定路由 + * + * @param route 目标路由 + * @param inclusive 是否包含目标路由 + * @author Joker.X + */ +fun navigateBackTo(route: NavKey, inclusive: Boolean = false) { + NavigationService.navigateBackTo(route = route, inclusive = inclusive) +} + +/** + * 返回上一页并携带结果 + * + * @param key 结果 Key + * @param result 返回结果 + * @author Joker.X + */ +fun popBackStackWithResult(key: NavigationResultKey, result: T) { + NavigationService.popBackStackWithResult(key = key, result = result) +} + +/** + * 返回上一页并携带结果(语义化别名) + * + * @param key 结果 Key + * @param result 返回结果 + * @author Joker.X + */ +fun navigateBackWithResult(key: NavigationResultKey, result: T) { + NavigationService.navigateBackWithResult(key = key, result = result) +} + +/** + * 监听指定结果 Key 的结果流 + * + * @param key 结果 Key + * @return 对应结果流 + * @author Joker.X + */ +fun resultEvents(key: NavigationResultKey): Flow { + return NavigationService.resultEvents(key) +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/RefreshResultKey.kt b/app/src/main/java/com/joker/kit/core/navigation/RefreshResultKey.kt new file mode 100644 index 0000000..5e7100f --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/RefreshResultKey.kt @@ -0,0 +1,23 @@ +package com.joker.kit.core.navigation + +/** + * 通用的页面刷新结果 Key。 + * + * 使用公共的 [RefreshResult] 作为返回类型: + * - refresh = true 表示上一个页面需要刷新数据 + * - refresh = false 或 null 表示不刷新 + * + * 示例: + * ```kotlin + * // 子页面:操作成功后返回并通知上一个页面刷新 + * popBackStackWithResult(RefreshResultKey, RefreshResult(refresh = true)) + * + * // 上一个页面(ViewModel): + * fun observeRefresh() { + * observeRefreshState(RefreshResultKey) + * } + * ``` + * + * @author Joker.X + */ +object RefreshResultKey : NavigationResultKey diff --git a/app/src/main/java/com/joker/kit/core/navigation/RouteInterceptor.kt b/app/src/main/java/com/joker/kit/core/navigation/RouteInterceptor.kt new file mode 100644 index 0000000..26f6dc8 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/RouteInterceptor.kt @@ -0,0 +1,48 @@ +package com.joker.kit.core.navigation + +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.navigation.auth.AuthRoutes +import com.joker.kit.core.navigation.user.UserRoutes +import kotlin.reflect.KClass + +/** + * 路由拦截器 + * + * 负责管理需要登录的页面配置和路由拦截逻辑 + * 使用类型安全的方式处理路由拦截 + * + * @author Joker.X + */ +class RouteInterceptor { + + /** + * 需要登录的路由类型集合 + * + * 在这里统一声明所有需要登录才能访问的页面类型。 + * + * @author Joker.X + */ + private val loginRequiredRouteTypes: Set> = setOf( + UserRoutes.Info::class + ) + + /** + * 检查指定路由对象是否需要登录 + * + * @param route 要检查的路由对象(类型安全) + * @return true表示需要登录,false表示不需要登录 + * @author Joker.X + */ + fun requiresLogin(route: NavKey): Boolean { + val routeClass = route::class + return loginRequiredRouteTypes.contains(routeClass) + } + + /** + * 获取登录页面路由对象 + * + * @return 登录页面的路由对象 + * @author Joker.X + */ + fun getLoginRoute(): NavKey = AuthRoutes.Login +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/auth/AuthRoutes.kt b/app/src/main/java/com/joker/kit/core/navigation/auth/AuthRoutes.kt new file mode 100644 index 0000000..e5d58fe --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/auth/AuthRoutes.kt @@ -0,0 +1,20 @@ +package com.joker.kit.core.navigation.auth + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +/** + * 认证模块路由 + * + * @author Joker.X + */ +object AuthRoutes { + + /** + * 登录页路由 + * + * @author Joker.X + */ + @Serializable + data object Login : NavKey +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/demo/DemoNavigator.kt b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoNavigator.kt new file mode 100644 index 0000000..7599e18 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoNavigator.kt @@ -0,0 +1,87 @@ +package com.joker.kit.core.navigation.demo + +import com.joker.kit.core.navigation.navigate + +/** + * Demo 模块导航封装 + * + * 统一管理 Demo 模块页面跳转,避免在多个 ViewModel 中 + * 编写重复的中转方法。 + * + * @author Joker.X + */ +object DemoNavigator { + + /** + * 跳转到网络状态示例页 + * + * @author Joker.X + */ + fun toNetworkDemo() { + navigate(DemoRoutes.NetworkDemo) + } + + /** + * 跳转到网络列表示例页 + * + * @author Joker.X + */ + fun toNetworkListDemo() { + navigate(DemoRoutes.NetworkListDemo) + } + + /** + * 跳转到数据库示例页 + * + * @author Joker.X + */ + fun toDatabase() { + navigate(DemoRoutes.Database) + } + + /** + * 跳转到本地存储示例页 + * + * @author Joker.X + */ + fun toLocalStorage() { + navigate(DemoRoutes.LocalStorage) + } + + /** + * 跳转到状态管理示例页 + * + * @author Joker.X + */ + fun toStateManagement() { + navigate(DemoRoutes.StateManagement) + } + + /** + * 跳转到网络请求示例页 + * + * @author Joker.X + */ + fun toNetworkRequest() { + navigate(DemoRoutes.NetworkRequest) + } + + /** + * 跳转到带参示例页 + * + * @param goodsId 商品 ID + * @author Joker.X + */ + fun toNavigationWithArgs(goodsId: Long = 0) { + navigate(DemoRoutes.NavigationWithArgs(goodsId = goodsId)) + } + + /** + * 跳转到结果回传示例页 + * + * @author Joker.X + */ + fun toNavigationResult() { + navigate(DemoRoutes.NavigationResult) + } +} diff --git a/app/src/main/java/com/joker/kit/navigation/results/DemoResultKey.kt b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoResultKey.kt similarity index 62% rename from app/src/main/java/com/joker/kit/navigation/results/DemoResultKey.kt rename to app/src/main/java/com/joker/kit/core/navigation/demo/DemoResultKey.kt index b64a7cc..55cf72b 100644 --- a/app/src/main/java/com/joker/kit/navigation/results/DemoResultKey.kt +++ b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoResultKey.kt @@ -1,11 +1,11 @@ -package com.joker.kit.navigation.results +package com.joker.kit.core.navigation.demo -import com.joker.kit.navigation.NavigationResultKey +import com.joker.kit.core.navigation.NavigationResultKey import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json /** - * Demo 结果回传示例:返回 DemoResult 数据类 + * Demo 结果回传 Key * * @author Joker.X */ @@ -13,8 +13,8 @@ object DemoResultKey : NavigationResultKey { /** * 序列化结果 * - * @param value 待序列化的结果对象 - * @return 序列化后的字符串 + * @param value 待序列化对象 + * @return 序列化字符串 * @author Joker.X */ override fun serialize(value: DemoResult): Any = Json.encodeToString(value) @@ -22,8 +22,8 @@ object DemoResultKey : NavigationResultKey { /** * 反序列化结果 * - * @param raw 原始保存的数据 - * @return 解析后的结果对象 + * @param raw 原始值 + * @return 反序列化后的结果 * @author Joker.X */ override fun deserialize(raw: Any): DemoResult = Json.decodeFromString(raw as String) @@ -32,12 +32,12 @@ object DemoResultKey : NavigationResultKey { /** * Demo 结果数据 * - * @param id 结果标识 + * @param id 结果 ID * @param message 结果信息 * @author Joker.X */ @Serializable data class DemoResult( val id: Long, - val message: String + val message: String, ) diff --git a/app/src/main/java/com/joker/kit/core/navigation/demo/DemoRoutes.kt b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoRoutes.kt new file mode 100644 index 0000000..17e0669 --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/demo/DemoRoutes.kt @@ -0,0 +1,78 @@ +package com.joker.kit.core.navigation.demo + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +/** + * Demo 模块路由 + * + * @author Joker.X + */ +object DemoRoutes { + /** + * Network Demo 示例页路由 + * + * @author Joker.X + */ + @Serializable + data object NetworkDemo : NavKey + + /** + * Network List Demo 示例页路由 + * + * @author Joker.X + */ + @Serializable + data object NetworkListDemo : NavKey + + /** + * 数据库示例页路由 + * + * @author Joker.X + */ + @Serializable + data object Database : NavKey + + /** + * 本地存储示例页路由 + * + * @author Joker.X + */ + @Serializable + data object LocalStorage : NavKey + + /** + * 状态管理示例页路由 + * + * @author Joker.X + */ + @Serializable + data object StateManagement : NavKey + + /** + * 通用网络请求示例页路由 + * + * @author Joker.X + */ + @Serializable + data object NetworkRequest : NavKey + + /** + * 带参跳转示例页路由 + * + * @param goodsId 商品 ID + * @author Joker.X + */ + @Serializable + data class NavigationWithArgs( + val goodsId: Long, + ) : NavKey + + /** + * 结果回传示例页路由 + * + * @author Joker.X + */ + @Serializable + data object NavigationResult : NavKey +} diff --git a/app/src/main/java/com/joker/kit/navigation/routes/MainRoutes.kt b/app/src/main/java/com/joker/kit/core/navigation/main/MainRoutes.kt similarity index 55% rename from app/src/main/java/com/joker/kit/navigation/routes/MainRoutes.kt rename to app/src/main/java/com/joker/kit/core/navigation/main/MainRoutes.kt index 8b55143..e3c93ac 100644 --- a/app/src/main/java/com/joker/kit/navigation/routes/MainRoutes.kt +++ b/app/src/main/java/com/joker/kit/core/navigation/main/MainRoutes.kt @@ -1,5 +1,6 @@ -package com.joker.kit.navigation.routes +package com.joker.kit.core.navigation.main +import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable /** @@ -8,13 +9,12 @@ import kotlinx.serialization.Serializable * @author Joker.X */ object MainRoutes { + /** - * 主框架路由 - * - * 应用的主框架,包含底部导航栏 + * 主框架页路由 * * @author Joker.X */ @Serializable - data object Main + data object Main : NavKey } diff --git a/app/src/main/java/com/joker/kit/core/navigation/user/UserNavigator.kt b/app/src/main/java/com/joker/kit/core/navigation/user/UserNavigator.kt new file mode 100644 index 0000000..c76034a --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/user/UserNavigator.kt @@ -0,0 +1,20 @@ +package com.joker.kit.core.navigation.user + +import com.joker.kit.core.navigation.navigate + +/** + * 用户模块导航封装 + * + * @author Joker.X + */ +object UserNavigator { + + /** + * 跳转到用户信息页 + * + * @author Joker.X + */ + fun toUserInfo() { + navigate(UserRoutes.Info) + } +} diff --git a/app/src/main/java/com/joker/kit/core/navigation/user/UserRoutes.kt b/app/src/main/java/com/joker/kit/core/navigation/user/UserRoutes.kt new file mode 100644 index 0000000..558086e --- /dev/null +++ b/app/src/main/java/com/joker/kit/core/navigation/user/UserRoutes.kt @@ -0,0 +1,20 @@ +package com.joker.kit.core.navigation.user + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +/** + * 用户模块路由 + * + * @author Joker.X + */ +object UserRoutes { + + /** + * 用户信息页路由 + * + * @author Joker.X + */ + @Serializable + data object Info : NavKey +} diff --git a/app/src/main/java/com/joker/kit/core/network/di/NetworkModule.kt b/app/src/main/java/com/joker/kit/core/network/di/NetworkModule.kt index 6fa73e4..385a907 100644 --- a/app/src/main/java/com/joker/kit/core/network/di/NetworkModule.kt +++ b/app/src/main/java/com/joker/kit/core/network/di/NetworkModule.kt @@ -3,8 +3,8 @@ package com.joker.kit.core.network.di import android.content.Context import com.chuckerteam.chucker.api.ChuckerInterceptor import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import com.joker.kit.core.network.interceptor.AuthInterceptor import com.joker.kit.BuildConfig +import com.joker.kit.core.network.interceptor.AuthInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/app/src/main/java/com/joker/kit/core/network/interceptor/AuthInterceptor.kt b/app/src/main/java/com/joker/kit/core/network/interceptor/AuthInterceptor.kt index 770cfbb..c9e7d93 100644 --- a/app/src/main/java/com/joker/kit/core/network/interceptor/AuthInterceptor.kt +++ b/app/src/main/java/com/joker/kit/core/network/interceptor/AuthInterceptor.kt @@ -6,7 +6,6 @@ import okhttp3.Interceptor import okhttp3.Response import javax.inject.Inject import javax.inject.Singleton -import kotlin.text.isNotBlank /** * 认证拦截器 - 添加授权头信息 diff --git a/app/src/main/java/com/joker/kit/core/state/DemoCounterState.kt b/app/src/main/java/com/joker/kit/core/state/DemoCounterState.kt index bc6038f..7d575ec 100644 --- a/app/src/main/java/com/joker/kit/core/state/DemoCounterState.kt +++ b/app/src/main/java/com/joker/kit/core/state/DemoCounterState.kt @@ -1,13 +1,13 @@ package com.joker.kit.core.state import com.joker.kit.core.state.di.ApplicationScope -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton /** * Demo 计数器状态 diff --git a/app/src/main/java/com/joker/kit/core/ui/component/empty/Empty.kt b/app/src/main/java/com/joker/kit/core/ui/component/empty/Empty.kt index a6daa48..323abff 100644 --- a/app/src/main/java/com/joker/kit/core/ui/component/empty/Empty.kt +++ b/app/src/main/java/com/joker/kit/core/ui/component/empty/Empty.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton diff --git a/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkListView.kt b/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkListView.kt index 9e3ad97..f1537c7 100644 --- a/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkListView.kt +++ b/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkListView.kt @@ -10,10 +10,10 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.joker.kit.core.base.state.BaseNetWorkListUiState import com.joker.kit.core.ui.component.empty.EmptyData import com.joker.kit.core.ui.component.empty.EmptyNetwork import com.joker.kit.core.ui.component.loading.PageLoading -import com.joker.kit.core.base.state.BaseNetWorkListUiState /** * 基础网络列表视图组件 diff --git a/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkView.kt b/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkView.kt index 5592930..19c29d3 100644 --- a/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkView.kt +++ b/app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkView.kt @@ -10,9 +10,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.joker.kit.core.base.state.BaseNetWorkUiState import com.joker.kit.core.ui.component.empty.EmptyNetwork import com.joker.kit.core.ui.component.loading.PageLoading -import com.joker.kit.core.base.state.BaseNetWorkUiState /** * 基础网络视图组件,用于处理网络请求的三种状态:加载中、错误和成功 diff --git a/app/src/main/java/com/joker/kit/core/ui/component/text/Text.kt b/app/src/main/java/com/joker/kit/core/ui/component/text/Text.kt index 19ff8c9..4cb2155 100644 --- a/app/src/main/java/com/joker/kit/core/ui/component/text/Text.kt +++ b/app/src/main/java/com/joker/kit/core/ui/component/text/Text.kt @@ -244,7 +244,7 @@ fun AppText( } /** - * 通用文本组件 - AnnotatedString版本 + * 通用文本组件 - AnnotatedString 重载 * * @param text 富文本内容 * @param modifier 修饰符 @@ -369,7 +369,7 @@ fun AppText( } /** - * 基础文本组件 - 字符串版本 + * 基础文本组件 - 字符串重载 * * 该组件是对 Material3 Text 的轻量封装,主要用于设置ContentColor * @@ -411,7 +411,7 @@ private fun BasicText( } /** - * 基础文本组件 - AnnotatedString版本 + * 基础文本组件 - AnnotatedString 重载 * * 该组件是对 Material3 Text 的轻量封装,主要用于设置ContentColor * @@ -450,4 +450,4 @@ private fun BasicText( onTextLayout = onTextLayout ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthGraph.kt b/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthGraph.kt index 648aaf5..eb07d64 100644 --- a/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthGraph.kt +++ b/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthGraph.kt @@ -1,21 +1,17 @@ package com.joker.kit.feature.auth.navigation -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.navigation.auth.AuthRoutes +import com.joker.kit.feature.auth.view.LoginRoute /** * 认证模块导航图 * - * @param navController 导航控制器 - * @param sharedTransitionScope 共享转场作用域 * @author Joker.X */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.authGraph( - navController: NavHostController, - sharedTransitionScope: SharedTransitionScope -) { - loginScreen(sharedTransitionScope) +fun EntryProviderScope.authGraph() { + entry { + LoginRoute() + } } diff --git a/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthNavigation.kt b/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthNavigation.kt deleted file mode 100644 index 83e27ff..0000000 --- a/app/src/main/java/com/joker/kit/feature/auth/navigation/AuthNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.auth.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.auth.view.LoginRoute -import com.joker.kit.navigation.routes.AuthRoutes - -/** - * 注册登录页路由 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.loginScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - LoginRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/auth/view/LoginScreen.kt b/app/src/main/java/com/joker/kit/feature/auth/view/LoginScreen.kt index 8cc5a1a..97d99a6 100644 --- a/app/src/main/java/com/joker/kit/feature/auth/view/LoginScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/auth/view/LoginScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingLarge +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.auth.viewmodel.LoginViewModel @@ -27,7 +28,7 @@ internal fun LoginRoute( ) { LoginScreen( onLoginClick = viewModel::login, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/auth/viewmodel/LoginViewModel.kt b/app/src/main/java/com/joker/kit/feature/auth/viewmodel/LoginViewModel.kt index 38cd8b9..3156089 100644 --- a/app/src/main/java/com/joker/kit/feature/auth/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/auth/viewmodel/LoginViewModel.kt @@ -4,9 +4,9 @@ import androidx.lifecycle.viewModelScope import com.joker.kit.core.base.viewmodel.BaseViewModel import com.joker.kit.core.model.entity.Auth import com.joker.kit.core.model.entity.User +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.state.UserState import com.joker.kit.core.util.toast.ToastUtils -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -20,12 +20,8 @@ import javax.inject.Inject */ @HiltViewModel class LoginViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState -) : BaseViewModel( - navigator = navigator, - userState = userState -) { + private val userState: UserState +) : BaseViewModel() { /** * 模拟登录:构造假的 Auth/User,写入 UserState,演示路由拦截放行。 diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/DatabaseNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/DatabaseNavigation.kt deleted file mode 100644 index 5fffbb4..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/DatabaseNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.DatabaseRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 数据库示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.databaseScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - DatabaseRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/DemoGraph.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/DemoGraph.kt index 7331d22..6b6bb48 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/DemoGraph.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/navigation/DemoGraph.kt @@ -1,28 +1,45 @@ package com.joker.kit.feature.demo.navigation -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.navigation.demo.DemoRoutes +import com.joker.kit.feature.demo.view.DatabaseRoute +import com.joker.kit.feature.demo.view.LocalStorageRoute +import com.joker.kit.feature.demo.view.NavigationResultRoute +import com.joker.kit.feature.demo.view.NavigationWithArgsRoute +import com.joker.kit.feature.demo.view.NetworkDemoRoute +import com.joker.kit.feature.demo.view.NetworkListDemoRoute +import com.joker.kit.feature.demo.view.NetworkRequestRoute +import com.joker.kit.feature.demo.view.StateManagementRoute /** * Demo 模块导航图 * - * @param navController 导航控制器 - * @param sharedTransitionScope 共享转场作用域 * @author Joker.X */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.demoGraph( - navController: NavHostController, - sharedTransitionScope: SharedTransitionScope -) { - networkDemoScreen(sharedTransitionScope) - networkListDemoScreen(sharedTransitionScope) - databaseScreen(sharedTransitionScope) - localStorageScreen(sharedTransitionScope) - stateManagementScreen(sharedTransitionScope) - networkRequestScreen(sharedTransitionScope) - navigationWithArgsScreen(sharedTransitionScope) - navigationResultScreen(sharedTransitionScope) +fun EntryProviderScope.demoGraph() { + entry { + NetworkDemoRoute() + } + entry { + NetworkListDemoRoute() + } + entry { + DatabaseRoute() + } + entry { + LocalStorageRoute() + } + entry { + StateManagementRoute() + } + entry { + NetworkRequestRoute() + } + entry { key -> + NavigationWithArgsRoute(navKey = key) + } + entry { + NavigationResultRoute() + } } diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/LocalStorageNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/LocalStorageNavigation.kt deleted file mode 100644 index bcd31cd..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/LocalStorageNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.LocalStorageRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 本地存储示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.localStorageScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - LocalStorageRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationResultNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationResultNavigation.kt deleted file mode 100644 index 1739d80..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationResultNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.NavigationResultRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 结果回传示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.navigationResultScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - NavigationResultRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationWithArgsNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationWithArgsNavigation.kt deleted file mode 100644 index eb29677..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/NavigationWithArgsNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.NavigationWithArgsRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 带参跳转示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.navigationWithArgsScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - NavigationWithArgsRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkDemoNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkDemoNavigation.kt deleted file mode 100644 index 09a1afb..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkDemoNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.NetworkDemoRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * Network Demo 页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.networkDemoScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - NetworkDemoRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkListDemoNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkListDemoNavigation.kt deleted file mode 100644 index 6f9b5e2..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkListDemoNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.NetworkListDemoRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * Network List Demo 页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.networkListDemoScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - NetworkListDemoRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkRequestNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkRequestNavigation.kt deleted file mode 100644 index 3cb1127..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/NetworkRequestNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.NetworkRequestRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 网络请求示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.networkRequestScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - NetworkRequestRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/navigation/StateManagementNavigation.kt b/app/src/main/java/com/joker/kit/feature/demo/navigation/StateManagementNavigation.kt deleted file mode 100644 index e2f4ca3..0000000 --- a/app/src/main/java/com/joker/kit/feature/demo/navigation/StateManagementNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.demo.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.demo.view.StateManagementRoute -import com.joker.kit.navigation.routes.DemoRoutes - -/** - * 状态管理示例页面导航 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.stateManagementScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - StateManagementRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/DatabaseScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/DatabaseScreen.kt index f5cdfe5..6d0ef0d 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/DatabaseScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/DatabaseScreen.kt @@ -39,6 +39,7 @@ import com.joker.kit.core.designsystem.theme.SpacePaddingMedium import com.joker.kit.core.designsystem.theme.SpaceVerticalLarge import com.joker.kit.core.designsystem.theme.SpaceVerticalMedium import com.joker.kit.core.designsystem.theme.SpaceVerticalSmall +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.divider.Divider import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText @@ -75,7 +76,7 @@ internal fun DatabaseRoute( onAddClick = viewModel::addItem, onDeleteItem = viewModel::deleteItem, onClearAll = viewModel::clearAll, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/LocalStorageScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/LocalStorageScreen.kt index 79a872f..96757e7 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/LocalStorageScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/LocalStorageScreen.kt @@ -11,8 +11,8 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -32,6 +32,7 @@ import com.joker.kit.core.designsystem.theme.SpacePaddingMedium import com.joker.kit.core.designsystem.theme.SpaceVerticalLarge import com.joker.kit.core.designsystem.theme.SpaceVerticalMedium import com.joker.kit.core.model.entity.User +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.core.ui.component.text.TextSize @@ -68,7 +69,7 @@ internal fun LocalStorageRoute( onSaveUser = viewModel::saveUser, onClearUser = viewModel::clearUser, onReloadUser = viewModel::loadUser, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } @@ -253,7 +254,7 @@ private fun UserCard( } } - Divider() + HorizontalDivider() val userText = user?.let { val name = it.nickName ?: "未设置昵称" diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/NavigationResultScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/NavigationResultScreen.kt index 0f29278..8fc0feb 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/NavigationResultScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/NavigationResultScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingLarge +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.demo.viewmodel.NavigationResultViewModel @@ -26,7 +27,7 @@ internal fun NavigationResultRoute( viewModel: NavigationResultViewModel = hiltViewModel() ) { NavigationResultScreen( - onBackClick = viewModel::navigateBack, + onBackClick = ::navigateBack, onSendResult = viewModel::sendResultAndBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/NavigationWithArgsScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/NavigationWithArgsScreen.kt index b15d641..a5ca28e 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/NavigationWithArgsScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/NavigationWithArgsScreen.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.joker.kit.core.designsystem.theme.AppTheme +import com.joker.kit.core.navigation.demo.DemoRoutes +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.demo.viewmodel.NavigationWithArgsViewModel @@ -12,16 +14,22 @@ import com.joker.kit.feature.demo.viewmodel.NavigationWithArgsViewModel /** * 带参跳转示例路由 * + * @param navKey 导航参数 * @param viewModel Hilt 注入的 NavigationWithArgsViewModel * @author Joker.X */ @Composable internal fun NavigationWithArgsRoute( - viewModel: NavigationWithArgsViewModel = hiltViewModel() + navKey: DemoRoutes.NavigationWithArgs, + viewModel: NavigationWithArgsViewModel = hiltViewModel( + creationCallback = { factory -> + factory.create(navKey) + } + ) ) { NavigationWithArgsScreen( goodsId = viewModel.goodsId, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkDemoScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkDemoScreen.kt index 9dd011a..4acc428 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkDemoScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkDemoScreen.kt @@ -13,6 +13,7 @@ import com.joker.kit.core.base.state.BaseNetWorkUiState import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingMedium import com.joker.kit.core.model.entity.Goods +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.network.BaseNetWorkView import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText @@ -33,7 +34,7 @@ internal fun NetworkDemoRoute( NetworkDemoScreen( uiState = uiState, - onBackClick = viewModel::navigateBack, + onBackClick = ::navigateBack, onRetry = viewModel::retryRequest ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkListDemoScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkListDemoScreen.kt index 6007cb5..3ca302f 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkListDemoScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkListDemoScreen.kt @@ -15,6 +15,7 @@ import com.joker.kit.core.base.state.LoadMoreState import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.ShapeMedium import com.joker.kit.core.model.entity.Goods +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.network.BaseNetWorkListView import com.joker.kit.core.ui.component.refresh.RefreshLayout import com.joker.kit.core.ui.component.scaffold.AppScaffold @@ -48,7 +49,7 @@ internal fun NetworkListDemoRoute( onRefresh = viewModel::onRefresh, onLoadMore = viewModel::onLoadMore, shouldTriggerLoadMore = viewModel::shouldTriggerLoadMore, - onBackClick = viewModel::navigateBack, + onBackClick = ::navigateBack, onRetry = viewModel::retryRequest, ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkRequestScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkRequestScreen.kt index f9aff39..c0341e6 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/NetworkRequestScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/NetworkRequestScreen.kt @@ -18,6 +18,7 @@ import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingLarge import com.joker.kit.core.designsystem.theme.SpacePaddingMedium import com.joker.kit.core.model.entity.Goods +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.demo.viewmodel.NetworkRequestViewModel @@ -37,7 +38,7 @@ internal fun NetworkRequestRoute( NetworkRequestScreen( goods = goods, - onBackClick = viewModel::navigateBack, + onBackClick = ::navigateBack, onRequestClick = viewModel::onRequestClick ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/view/StateManagementScreen.kt b/app/src/main/java/com/joker/kit/feature/demo/view/StateManagementScreen.kt index d3fe415..947a1ae 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/view/StateManagementScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/view/StateManagementScreen.kt @@ -27,6 +27,7 @@ import com.joker.kit.core.designsystem.theme.SpacePaddingLarge import com.joker.kit.core.designsystem.theme.SpacePaddingMedium import com.joker.kit.core.designsystem.theme.SpaceVerticalLarge import com.joker.kit.core.designsystem.theme.SpaceVerticalMedium +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.core.ui.component.text.TextSize @@ -51,7 +52,7 @@ internal fun StateManagementRoute( onIncrease = viewModel::increase, onDecrease = viewModel::decrease, onReset = viewModel::reset, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/DatabaseViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/DatabaseViewModel.kt index a1f82ee..134896c 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/DatabaseViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/DatabaseViewModel.kt @@ -4,8 +4,6 @@ import androidx.lifecycle.viewModelScope import com.joker.kit.core.base.viewmodel.BaseViewModel import com.joker.kit.core.data.repository.DemoRepository import com.joker.kit.core.database.entity.DemoEntity -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -18,17 +16,13 @@ import javax.inject.Inject /** * 数据库示例页 ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态管理 * @param demoRepository Demo 仓库,封装 DemoDataSource 的增删改查 * @author Joker.X */ @HiltViewModel class DatabaseViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val demoRepository: DemoRepository -) : BaseViewModel(navigator, userState) { +) : BaseViewModel() { /** 标题输入 */ private val _title = MutableStateFlow("") diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/LocalStorageViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/LocalStorageViewModel.kt index f00ae52..9776886 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/LocalStorageViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/LocalStorageViewModel.kt @@ -4,31 +4,25 @@ import androidx.lifecycle.viewModelScope import com.joker.kit.core.base.viewmodel.BaseViewModel import com.joker.kit.core.data.repository.UserInfoStoreRepository import com.joker.kit.core.model.entity.User -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject /** * 本地存储示例页 ViewModel * * 通过本地仓库 (UserInfoStoreRepository) 演示“用户信息” 的保存 / 读取 / 清除。 * - * @param navigator 导航管理器 - * @param userState 用户状态管理 * @param userInfoStoreRepository 用户信息本地存储仓库 * @author Joker.X */ @HiltViewModel class LocalStorageViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val userInfoStoreRepository: UserInfoStoreRepository -) : BaseViewModel(navigator, userState) { +) : BaseViewModel() { /** 用户 id 输入 */ private val _userId = MutableStateFlow("1") diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationResultViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationResultViewModel.kt index 8750c5b..9e5c8b1 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationResultViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationResultViewModel.kt @@ -1,28 +1,20 @@ package com.joker.kit.feature.demo.viewmodel import com.joker.kit.core.base.viewmodel.BaseViewModel -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.results.DemoResult -import com.joker.kit.navigation.results.DemoResultKey +import com.joker.kit.core.navigation.demo.DemoResult +import com.joker.kit.core.navigation.demo.DemoResultKey +import com.joker.kit.core.navigation.popBackStackWithResult import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject /** * 结果回传示例页 ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态 * @author Joker.X */ @HiltViewModel class NavigationResultViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState -) : BaseViewModel( - navigator = navigator, - userState = userState -) { +) : BaseViewModel() { /** * 回传结果并返回上一页 * diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationWithArgsViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationWithArgsViewModel.kt index f4ab0cc..c3a087a 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationWithArgsViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NavigationWithArgsViewModel.kt @@ -1,44 +1,43 @@ package com.joker.kit.feature.demo.viewmodel -import androidx.lifecycle.SavedStateHandle -import androidx.navigation.toRoute import com.joker.kit.core.base.viewmodel.BaseViewModel -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.routes.DemoRoutes +import com.joker.kit.core.navigation.demo.DemoRoutes +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject /** * 带参跳转示例页 ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态 - * @param savedStateHandle 路由参数存储 + * @param navKey 导航参数 * @author Joker.X */ -@HiltViewModel -class NavigationWithArgsViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, - savedStateHandle: SavedStateHandle -) : BaseViewModel( - navigator = navigator, - userState = userState -) { +@HiltViewModel(assistedFactory = NavigationWithArgsViewModel.Factory::class) +class NavigationWithArgsViewModel @AssistedInject constructor( + @Assisted private val navKey: DemoRoutes.NavigationWithArgs +) : BaseViewModel() { /** - * 路由参数 + * 当前商品 ID(用于请求商品详情) * - * @return 路由解析结果 * @author Joker.X */ - private val route = savedStateHandle.toRoute() + val goodsId: Long = navKey.goodsId /** - * 商品ID + * Assisted Factory * - * @return 传递的商品 ID * @author Joker.X */ - val goodsId: Long = route.goodsId + @AssistedFactory + interface Factory { + /** + * 创建 ViewModel 实例 + * + * @param navKey 导航参数 + * @return ViewModel 实例 + * @author Joker.X + */ + fun create(navKey: DemoRoutes.NavigationWithArgs): NavigationWithArgsViewModel + } } diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkDemoViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkDemoViewModel.kt index b6a03be..9ce70f8 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkDemoViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkDemoViewModel.kt @@ -4,8 +4,6 @@ import com.joker.kit.core.base.viewmodel.BaseNetWorkViewModel import com.joker.kit.core.data.repository.GoodsRepository import com.joker.kit.core.model.entity.Goods import com.joker.kit.core.model.network.NetworkResponse -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -13,17 +11,13 @@ import javax.inject.Inject /** * 网络状态 Demo 页面 ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态管理 * @param goodsRepository 商品数据仓库 * @author Joker.X */ @HiltViewModel class NetworkDemoViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val goodsRepository: GoodsRepository -) : BaseNetWorkViewModel(navigator, userState) { +) : BaseNetWorkViewModel() { init { super.executeRequest() diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkListDemoViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkListDemoViewModel.kt index 30a627a..92160af 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkListDemoViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkListDemoViewModel.kt @@ -6,8 +6,6 @@ import com.joker.kit.core.model.entity.Goods import com.joker.kit.core.model.network.NetworkPageData import com.joker.kit.core.model.network.NetworkResponse import com.joker.kit.core.model.request.GoodsSearchRequest -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -15,19 +13,13 @@ import javax.inject.Inject /** * Network List Demo 示例页 ViewModel * - * @param navigator 导航器 - * @param userState 用户状态管理 * @param goodsRepository 商品数据仓库 * @author Joker.X */ @HiltViewModel class NetworkListDemoViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val goodsRepository: GoodsRepository ) : BaseNetWorkListViewModel( - navigator = navigator, - userState = userState ) { init { diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkRequestViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkRequestViewModel.kt index 237c17c..73db077 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkRequestViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/NetworkRequestViewModel.kt @@ -6,8 +6,6 @@ import com.joker.kit.core.data.repository.GoodsRepository import com.joker.kit.core.model.entity.Goods import com.joker.kit.core.result.ResultHandler import com.joker.kit.core.result.asResult -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -17,20 +15,13 @@ import javax.inject.Inject /** * 网络请求示例页 ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态 * @param goodsRepository 商品仓库 * @author Joker.X */ @HiltViewModel class NetworkRequestViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val goodsRepository: GoodsRepository -) : BaseViewModel( - navigator = navigator, - userState = userState -) { +) : BaseViewModel() { /** * 商品信息 diff --git a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/StateManagementViewModel.kt b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/StateManagementViewModel.kt index 52097c6..4dae98c 100644 --- a/app/src/main/java/com/joker/kit/feature/demo/viewmodel/StateManagementViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/demo/viewmodel/StateManagementViewModel.kt @@ -2,8 +2,6 @@ package com.joker.kit.feature.demo.viewmodel import com.joker.kit.core.base.viewmodel.BaseViewModel import com.joker.kit.core.state.DemoCounterState -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -11,17 +9,13 @@ import javax.inject.Inject /** * 状态管理示例页 ViewModel * - * @param navigator 应用导航器 - * @param userState 全局用户状态 * @param counterState 计数器状态 * @author Joker.X */ @HiltViewModel class StateManagementViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, private val counterState: DemoCounterState -) : BaseViewModel(navigator, userState) { +) : BaseViewModel() { /** * 对外暴露的计数器 StateFlow diff --git a/app/src/main/java/com/joker/kit/feature/main/component/DemoCard.kt b/app/src/main/java/com/joker/kit/feature/main/component/DemoCard.kt index d693385..38d63e6 100644 --- a/app/src/main/java/com/joker/kit/feature/main/component/DemoCard.kt +++ b/app/src/main/java/com/joker/kit/feature/main/component/DemoCard.kt @@ -1,11 +1,11 @@ package com.joker.kit.feature.main.component +import androidx.compose.foundation.clickable import androidx.compose.material3.ListItem import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.foundation.clickable import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.ShapeMedium import com.joker.kit.core.ui.component.text.AppText @@ -53,7 +53,7 @@ private fun DemoCardPreview() { info = DemoCardInfo( title = "示例组件", description = "预览展示 Demo 卡片默认样式。", - route = null + navigateAction = null ) ) } diff --git a/app/src/main/java/com/joker/kit/feature/main/data/DemoCardData.kt b/app/src/main/java/com/joker/kit/feature/main/data/DemoCardData.kt index dd6ac50..4dc0af6 100644 --- a/app/src/main/java/com/joker/kit/feature/main/data/DemoCardData.kt +++ b/app/src/main/java/com/joker/kit/feature/main/data/DemoCardData.kt @@ -1,8 +1,8 @@ package com.joker.kit.feature.main.data +import com.joker.kit.core.navigation.demo.DemoNavigator +import com.joker.kit.core.navigation.user.UserNavigator import com.joker.kit.feature.main.model.DemoCardInfo -import com.joker.kit.navigation.routes.DemoRoutes -import com.joker.kit.navigation.routes.UserRoutes /** * Demo 卡片静态数据源 @@ -10,55 +10,64 @@ import com.joker.kit.navigation.routes.UserRoutes * @author Joker.X */ object DemoCardData { - + /** + * Core 页签下的演示卡片 + * + * @author Joker.X + */ val coreCards: List = listOf( DemoCardInfo( title = "Network Demo", description = "网络状态切换,包含加载、错误、重试等流程。", - route = DemoRoutes.NetworkDemo + navigateAction = { DemoNavigator.toNetworkDemo() } ), DemoCardInfo( title = "Network List Demo", description = "下拉刷新与分页加载的统一列表模板,内置空状态与重试。", - route = DemoRoutes.NetworkListDemo + navigateAction = { DemoNavigator.toNetworkListDemo() } ), DemoCardInfo( title = "数据库", description = "Room 的增删改查示例,含简单的列表展示与数据观察。", - route = DemoRoutes.Database + navigateAction = { DemoNavigator.toDatabase() } ), DemoCardInfo( title = "本地存储", description = "DataStore / MMKV 的写入与清除示例,演示单值增删改查。", - route = DemoRoutes.LocalStorage + navigateAction = { DemoNavigator.toLocalStorage() } ), DemoCardInfo( title = "状态管理", description = "全局 DemoCounterState 计数器共享示例,展示跨页面 StateFlow 同步。", - route = DemoRoutes.StateManagement + navigateAction = { DemoNavigator.toStateManagement() } ), DemoCardInfo( title = "网络请求", description = "结合 ResultHandler 的通用接口请求、加载状态与错误提示。", - route = DemoRoutes.NetworkRequest + navigateAction = { DemoNavigator.toNetworkRequest() } ) ) + /** + * Navigation 页签下的演示卡片 + * + * @author Joker.X + */ val navigationCards: List = listOf( DemoCardInfo( title = "带参跳转", description = "类型安全路由参数,包含必填/可选参数与目标页接收方式。", - route = DemoRoutes.NavigationWithArgs(123) + navigateAction = { DemoNavigator.toNavigationWithArgs(goodsId = 123) } ), DemoCardInfo( title = "结果回传", description = "NavigationResultKey 返回数据,包含刷新信号与数据实体回传。", - route = DemoRoutes.NavigationResult + navigateAction = { DemoNavigator.toNavigationResult() } ), DemoCardInfo( title = "导航拦截", description = "登录拦截流程:未登录跳登录页,登录成功后才能进入用户详情。", - route = UserRoutes.Info + navigateAction = { UserNavigator.toUserInfo() } ) ) } diff --git a/app/src/main/java/com/joker/kit/feature/main/model/DemoCardInfo.kt b/app/src/main/java/com/joker/kit/feature/main/model/DemoCardInfo.kt index 1735abb..68d5a9c 100644 --- a/app/src/main/java/com/joker/kit/feature/main/model/DemoCardInfo.kt +++ b/app/src/main/java/com/joker/kit/feature/main/model/DemoCardInfo.kt @@ -1,15 +1,22 @@ package com.joker.kit.feature.main.model +/** + * Demo 卡片导航动作 + * + * @author Joker.X + */ +typealias DemoCardNavigateAction = () -> Unit + /** * Demo 卡片信息 * * @param title 标题 * @param description 描述内容 - * @param route 跳转路由 + * @param navigateAction 跳转动作 * @author Joker.X */ data class DemoCardInfo( val title: String, val description: String, - val route: Any? = null + val navigateAction: DemoCardNavigateAction? = null ) diff --git a/app/src/main/java/com/joker/kit/feature/main/navigation/MainGraph.kt b/app/src/main/java/com/joker/kit/feature/main/navigation/MainGraph.kt index 51544c5..14d589c 100644 --- a/app/src/main/java/com/joker/kit/feature/main/navigation/MainGraph.kt +++ b/app/src/main/java/com/joker/kit/feature/main/navigation/MainGraph.kt @@ -1,22 +1,17 @@ package com.joker.kit.feature.main.navigation -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.navigation.main.MainRoutes +import com.joker.kit.feature.main.view.MainRoute /** * 主模块导航图 * - * @param navController 导航控制器 - * @param sharedTransitionScope 共享转场作用域 * @author Joker.X */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.mainGraph( - navController: NavHostController, - sharedTransitionScope: SharedTransitionScope -) { - // 只调用页面级导航函数,不包含其他逻辑 - mainScreen(navController, sharedTransitionScope) +fun EntryProviderScope.mainGraph() { + entry { + MainRoute() + } } diff --git a/app/src/main/java/com/joker/kit/feature/main/navigation/MainNavigation.kt b/app/src/main/java/com/joker/kit/feature/main/navigation/MainNavigation.kt deleted file mode 100644 index a171a0d..0000000 --- a/app/src/main/java/com/joker/kit/feature/main/navigation/MainNavigation.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.joker.kit.feature.main.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import androidx.navigation.compose.composable -import com.joker.kit.feature.main.view.MainRoute -import com.joker.kit.navigation.routes.MainRoutes - -/** - * 注册主页面路由 - * - * @param navController NavHostController - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.mainScreen( - navController: NavHostController, - sharedTransitionScope: SharedTransitionScope -) { - composable { - MainRoute(navController = navController) - } -} diff --git a/app/src/main/java/com/joker/kit/feature/main/view/CoreDemoScreen.kt b/app/src/main/java/com/joker/kit/feature/main/view/CoreDemoScreen.kt index 389b511..0c511a1 100644 --- a/app/src/main/java/com/joker/kit/feature/main/view/CoreDemoScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/main/view/CoreDemoScreen.kt @@ -40,7 +40,7 @@ internal fun CoreDemoRoute( CoreDemoScreen( cards = cards, counter = count, - onCardClick = viewModel::onCardClick + onCardClick = { info -> info.navigateAction?.invoke() } ) } diff --git a/app/src/main/java/com/joker/kit/feature/main/view/MainScreen.kt b/app/src/main/java/com/joker/kit/feature/main/view/MainScreen.kt index 94b24ab..49d1fbb 100644 --- a/app/src/main/java/com/joker/kit/feature/main/view/MainScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/main/view/MainScreen.kt @@ -19,12 +19,10 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.NavController import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.core.ui.component.text.TextSize @@ -40,14 +38,12 @@ import com.joker.kit.feature.main.viewmodel.MainViewModel */ @Composable internal fun MainRoute( - viewModel: MainViewModel = hiltViewModel(), - navController: NavController + viewModel: MainViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState() MainScreen( uiState = uiState, - onTabSelected = viewModel::selectTab, - navController = navController + onTabSelected = viewModel::selectTab ) } @@ -62,13 +58,11 @@ internal fun MainRoute( @Composable internal fun MainScreen( uiState: MainUiState = MainUiState(), - onTabSelected: (MainTab) -> Unit, - navController: NavController = NavController(LocalContext.current) + onTabSelected: (MainTab) -> Unit ) { MainScreenContent( uiState = uiState, - onTabSelected = onTabSelected, - navController = navController + onTabSelected = onTabSelected ) } @@ -77,15 +71,13 @@ internal fun MainScreen( * * @param uiState UI 状态 * @param onTabSelected Tab 切换回调 - * @param navController 导航控制器 * @author Joker.X */ @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable private fun MainScreenContent( uiState: MainUiState, - onTabSelected: (MainTab) -> Unit, - navController: NavController + onTabSelected: (MainTab) -> Unit ) { val pagerState = rememberPagerState(pageCount = { MainTab.allTabs.size }) val currentPage = pagerState.currentPage @@ -121,9 +113,7 @@ private fun MainScreenContent( ) { page -> when (MainTab.fromIndex(page)) { MainTab.Core -> CoreDemoRoute() - MainTab.Navigation -> NavigationDemoRoute( - navController = navController - ) + MainTab.Navigation -> NavigationDemoRoute() } } } diff --git a/app/src/main/java/com/joker/kit/feature/main/view/NavigationDemoScreen.kt b/app/src/main/java/com/joker/kit/feature/main/view/NavigationDemoScreen.kt index b5ee083..ba31360 100644 --- a/app/src/main/java/com/joker/kit/feature/main/view/NavigationDemoScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/main/view/NavigationDemoScreen.kt @@ -16,30 +16,25 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.NavController import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingLarge import com.joker.kit.core.designsystem.theme.SpaceVerticalMedium +import com.joker.kit.core.navigation.demo.DemoResult import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.main.component.DemoCard import com.joker.kit.feature.main.data.DemoCardData import com.joker.kit.feature.main.model.DemoCardInfo import com.joker.kit.feature.main.viewmodel.NavigationDemoViewModel -import com.joker.kit.navigation.extension.observeResult -import com.joker.kit.navigation.results.DemoResult -import com.joker.kit.navigation.results.DemoResultKey /** * Navigation Demo 路由 * * @param viewModel Navigation Demo ViewModel - * @param navController 用于监听结果的 NavController * @author Joker.X */ @Composable internal fun NavigationDemoRoute( - viewModel: NavigationDemoViewModel = hiltViewModel(), - navController: NavController + viewModel: NavigationDemoViewModel = hiltViewModel() ) { val cards by viewModel.cards.collectAsState() val isLoggedIn by viewModel.isLoggedIn.collectAsState() @@ -49,12 +44,8 @@ internal fun NavigationDemoRoute( cards = cards, isLoggedIn = isLoggedIn, demoResult = demoResult, - onCardClick = viewModel::onCardClick + onCardClick = { info -> info.navigateAction?.invoke() } ) - - navController.observeResult(DemoResultKey) { result -> - viewModel.onResultReceived(result) - } } /** diff --git a/app/src/main/java/com/joker/kit/feature/main/viewmodel/CoreDemoViewModel.kt b/app/src/main/java/com/joker/kit/feature/main/viewmodel/CoreDemoViewModel.kt index 6047abe..d4ae204 100644 --- a/app/src/main/java/com/joker/kit/feature/main/viewmodel/CoreDemoViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/main/viewmodel/CoreDemoViewModel.kt @@ -1,11 +1,9 @@ package com.joker.kit.feature.main.viewmodel -import com.joker.kit.core.base.viewmodel.BaseViewModel +import androidx.lifecycle.ViewModel import com.joker.kit.core.state.DemoCounterState -import com.joker.kit.core.state.UserState import com.joker.kit.feature.main.data.DemoCardData import com.joker.kit.feature.main.model.DemoCardInfo -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -15,22 +13,25 @@ import javax.inject.Inject /** * Core Demo ViewModel * - * @param navigator 导航管理器 - * @param userState 用户状态 * @param counterState 计数器状态 * @author Joker.X */ @HiltViewModel class CoreDemoViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, counterState: DemoCounterState -) : BaseViewModel( - navigator = navigator, - userState = userState -) { - +) : ViewModel() { + /** + * Demo 卡片源数据 + * + * @author Joker.X + */ private val _cards = MutableStateFlow(DemoCardData.coreCards) + + /** + * Demo 卡片状态流 + * + * @author Joker.X + */ val cards: StateFlow> = _cards.asStateFlow() /** @@ -40,14 +41,4 @@ class CoreDemoViewModel @Inject constructor( * @author Joker.X */ val count: StateFlow = counterState.count - - /** - * 处理卡片点击 - * - * @param info 被点击的卡片信息 - * @author Joker.X - */ - fun onCardClick(info: DemoCardInfo) { - info.route?.let { navigate(it) } - } } diff --git a/app/src/main/java/com/joker/kit/feature/main/viewmodel/MainViewModel.kt b/app/src/main/java/com/joker/kit/feature/main/viewmodel/MainViewModel.kt index 20faa4d..24e80a8 100644 --- a/app/src/main/java/com/joker/kit/feature/main/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/main/viewmodel/MainViewModel.kt @@ -1,8 +1,6 @@ package com.joker.kit.feature.main.viewmodel import com.joker.kit.core.base.viewmodel.BaseViewModel -import com.joker.kit.core.state.UserState -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -12,18 +10,11 @@ import javax.inject.Inject /** * 主界面 ViewModel * - * @param navigator 导航控制器 - * @param userState 全局用户状态 * @author Joker.X */ @HiltViewModel class MainViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState -) : BaseViewModel( - navigator = navigator, - userState = userState -) { +) : BaseViewModel() { private val _uiState = MutableStateFlow(MainUiState()) val uiState: StateFlow = _uiState.asStateFlow() diff --git a/app/src/main/java/com/joker/kit/feature/main/viewmodel/NavigationDemoViewModel.kt b/app/src/main/java/com/joker/kit/feature/main/viewmodel/NavigationDemoViewModel.kt index fdf4e44..ec47fa6 100644 --- a/app/src/main/java/com/joker/kit/feature/main/viewmodel/NavigationDemoViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/main/viewmodel/NavigationDemoViewModel.kt @@ -1,56 +1,63 @@ package com.joker.kit.feature.main.viewmodel +import androidx.lifecycle.viewModelScope import com.joker.kit.core.base.viewmodel.BaseViewModel +import com.joker.kit.core.navigation.demo.DemoResult +import com.joker.kit.core.navigation.demo.DemoResultKey +import com.joker.kit.core.navigation.resultEvents import com.joker.kit.core.state.UserState import com.joker.kit.feature.main.data.DemoCardData import com.joker.kit.feature.main.model.DemoCardInfo -import com.joker.kit.navigation.AppNavigator -import com.joker.kit.navigation.results.DemoResult import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import javax.inject.Inject /** * Navigation Demo ViewModel * - * @param navigator 导航管理器 * @param userState 用户状态 * @author Joker.X */ @HiltViewModel class NavigationDemoViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState, -) : BaseViewModel( - navigator = navigator, - userState = userState -) { + private val userState: UserState, +) : BaseViewModel() { + /** + * 初始化时监听结果回传 + * + * @author Joker.X + */ + init { + observeDemoResult() + } + /** + * Navigation 卡片源数据 + */ private val _cards = MutableStateFlow(DemoCardData.navigationCards) + + /** + * Navigation 卡片状态流 + */ val cards: StateFlow> = _cards.asStateFlow() /** * 全局登录状态 - * - * @return 登录状态流 - * @author Joker.X */ val isLoggedIn: StateFlow = userState.isLoggedIn + /** + * Demo 结果状态源 + */ private val _demoResult = MutableStateFlow(null) - val demoResult: StateFlow = _demoResult.asStateFlow() /** - * 处理卡片点击 - * - * @param info 卡片信息 - * @author Joker.X + * Demo 结果状态流 */ - fun onCardClick(info: DemoCardInfo) { - info.route?.let { navigate(it) } - } + val demoResult: StateFlow = _demoResult.asStateFlow() /** * 处理回传结果 @@ -61,4 +68,17 @@ class NavigationDemoViewModel @Inject constructor( fun onResultReceived(result: DemoResult) { _demoResult.value = result } + + /** + * 监听结果回传事件 + * + * @author Joker.X + */ + private fun observeDemoResult() { + viewModelScope.launch { + resultEvents(DemoResultKey).collect { result -> + onResultReceived(result) + } + } + } } diff --git a/app/src/main/java/com/joker/kit/feature/user/navigation/UserGraph.kt b/app/src/main/java/com/joker/kit/feature/user/navigation/UserGraph.kt index e539884..37b3402 100644 --- a/app/src/main/java/com/joker/kit/feature/user/navigation/UserGraph.kt +++ b/app/src/main/java/com/joker/kit/feature/user/navigation/UserGraph.kt @@ -1,21 +1,17 @@ package com.joker.kit.feature.user.navigation -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import com.joker.kit.core.navigation.user.UserRoutes +import com.joker.kit.feature.user.view.UserInfoRoute /** * 用户模块导航图 * - * @param navController 导航控制器 - * @param sharedTransitionScope 共享转场作用域 * @author Joker.X */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.userGraph( - navController: NavHostController, - sharedTransitionScope: SharedTransitionScope -) { - userInfoScreen(sharedTransitionScope) +fun EntryProviderScope.userGraph() { + entry { + UserInfoRoute() + } } diff --git a/app/src/main/java/com/joker/kit/feature/user/navigation/UserNavigation.kt b/app/src/main/java/com/joker/kit/feature/user/navigation/UserNavigation.kt deleted file mode 100644 index d54031d..0000000 --- a/app/src/main/java/com/joker/kit/feature/user/navigation/UserNavigation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.joker.kit.feature.user.navigation - -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.joker.kit.feature.user.view.UserInfoRoute -import com.joker.kit.navigation.routes.UserRoutes - -/** - * 注册用户信息页路由 - * - * @param sharedTransitionScope 共享转场作用域 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -fun NavGraphBuilder.userInfoScreen(sharedTransitionScope: SharedTransitionScope) { - composable { - UserInfoRoute() - } -} diff --git a/app/src/main/java/com/joker/kit/feature/user/view/UserInfoScreen.kt b/app/src/main/java/com/joker/kit/feature/user/view/UserInfoScreen.kt index 151c07a..eee69e8 100644 --- a/app/src/main/java/com/joker/kit/feature/user/view/UserInfoScreen.kt +++ b/app/src/main/java/com/joker/kit/feature/user/view/UserInfoScreen.kt @@ -5,14 +5,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.joker.kit.core.designsystem.theme.AppTheme import com.joker.kit.core.designsystem.theme.SpacePaddingLarge +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.ui.component.scaffold.AppScaffold import com.joker.kit.core.ui.component.text.AppText import com.joker.kit.feature.user.viewmodel.UserInfoViewModel @@ -29,7 +28,7 @@ internal fun UserInfoRoute( ) { UserInfoScreen( onLogoutClick = viewModel::logout, - onBackClick = viewModel::navigateBack + onBackClick = ::navigateBack ) } diff --git a/app/src/main/java/com/joker/kit/feature/user/viewmodel/UserInfoViewModel.kt b/app/src/main/java/com/joker/kit/feature/user/viewmodel/UserInfoViewModel.kt index a13ce87..407b653 100644 --- a/app/src/main/java/com/joker/kit/feature/user/viewmodel/UserInfoViewModel.kt +++ b/app/src/main/java/com/joker/kit/feature/user/viewmodel/UserInfoViewModel.kt @@ -2,9 +2,9 @@ package com.joker.kit.feature.user.viewmodel import androidx.lifecycle.viewModelScope import com.joker.kit.core.base.viewmodel.BaseViewModel +import com.joker.kit.core.navigation.navigateBack import com.joker.kit.core.state.UserState import com.joker.kit.core.util.toast.ToastUtils -import com.joker.kit.navigation.AppNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -18,12 +18,8 @@ import javax.inject.Inject */ @HiltViewModel class UserInfoViewModel @Inject constructor( - navigator: AppNavigator, - userState: UserState -) : BaseViewModel( - navigator = navigator, - userState = userState -) { + private val userState: UserState +) : BaseViewModel() { /** * 一键退出登录(本地清空) diff --git a/app/src/main/java/com/joker/kit/navigation/AppNavHost.kt b/app/src/main/java/com/joker/kit/navigation/AppNavHost.kt deleted file mode 100644 index c32d164..0000000 --- a/app/src/main/java/com/joker/kit/navigation/AppNavHost.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.joker.kit.navigation - -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.core.tween -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import com.joker.kit.feature.auth.navigation.authGraph -import com.joker.kit.feature.demo.navigation.demoGraph -import com.joker.kit.feature.main.navigation.mainGraph -import com.joker.kit.feature.user.navigation.userGraph -import com.joker.kit.navigation.routes.MainRoutes -import kotlinx.coroutines.flow.collectLatest - -/** - * 应用导航宿主 - * 配置整个应用的导航图和动画 - * - * @param navigator 导航管理器 - * @param modifier 修饰符 - * @author Joker.X - */ -@OptIn(ExperimentalSharedTransitionApi::class) -@Composable -fun AppNavHost( - navigator: AppNavigator, - modifier: Modifier = Modifier -) { - val navController = rememberNavController() - - // 监听导航事件 - LaunchedEffect(navController) { - navigator.navigationEvents.collectLatest { event -> - navController.handleNavigationEvent(event) - } - } - - SharedTransitionLayout { - NavHost( - navController = navController, - startDestination = MainRoutes.Main, - modifier = modifier, - // 页面进入动画 - enterTransition = { - slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = tween(300) - ) - }, - // 页面退出动画 - exitTransition = { - slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = tween(300) - ) - }, - // 返回时页面进入动画 - popEnterTransition = { - slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = tween(300) - ) - }, - // 返回时页面退出动画 - popExitTransition = { - slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = tween(300) - ) - } - ) { - mainGraph(navController, this@SharedTransitionLayout) - demoGraph(navController, this@SharedTransitionLayout) - authGraph(navController, this@SharedTransitionLayout) - userGraph(navController, this@SharedTransitionLayout) - } - } -} diff --git a/app/src/main/java/com/joker/kit/navigation/AppNavigator.kt b/app/src/main/java/com/joker/kit/navigation/AppNavigator.kt deleted file mode 100644 index ac80b75..0000000 --- a/app/src/main/java/com/joker/kit/navigation/AppNavigator.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.joker.kit.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavOptions -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import javax.inject.Inject -import javax.inject.Singleton - -/** - * 导航管理器 - * - * 负责处理应用内所有的导航请求,采用事件驱动模式: - * 1. ViewModel 通过 AppNavigator 发送导航事件 - * 2. AppNavHost 监听事件并通过 NavController 执行导航 - * 3. 实现了 ViewModel 与 NavController 的解耦 - * - * 优势: - * - ViewModel 无需持有 NavController 引用 - * - 支持类型安全的路由导航 - * - 便于单元测试 - * - 统一管理导航逻辑 - * - * 使用示例: - * ```kotlin - * // 在 ViewModel 中 - * class MyViewModel @Inject constructor( - * private val navigator: AppNavigator - * ) : ViewModel() { - * fun navigateToDetail() { - * viewModelScope.launch { - * navigator.navigateTo(GoodsRoutes.Detail(id = 123)) - * } - * } - * } - * ``` - * - * @author Joker.X - */ -@Singleton -class AppNavigator @Inject constructor() { - private val _navigationEvents = MutableSharedFlow() - val navigationEvents: SharedFlow = _navigationEvents.asSharedFlow() - - /** - * 导航到指定路由 - * - * @param route 类型安全的路由对象(必须是 @Serializable) - * @param navOptions 导航选项(可选) - * - * 使用示例: - * ```kotlin - * // 简单导航 - * navigateTo(MainRoutes.Home) - * - * // 带参数导航 - * navigateTo(GoodsRoutes.Detail(goodsId = 123)) - * - * // 带 NavOptions - * navigateTo(UserRoutes.Profile, navOptions) - * ``` - * - * @author Joker.X - */ - suspend fun navigateTo(route: Any, navOptions: NavOptions? = null) { - _navigationEvents.emit(NavigationEvent.NavigateTo(route, navOptions)) - } - - /** - * 返回上一页(简单返回,无结果) - * - * 使用示例: - * ```kotlin - * navigateBack() - * ``` - * - * @author Joker.X - */ - suspend fun navigateBack() { - _navigationEvents.emit(NavigationEvent.NavigateUp) - } - - /** - * 返回上一页并携带类型安全的结果(使用 NavigationResultKey) - * - * 这是 V3.2 版本的最终方案,实现了端到端的类型安全 - * - * @param key 类型安全的结果 Key - * @param result 要传递的结果对象 - * - * 使用示例: - * ```kotlin - * // 1. 定义返回结果数据类型 - * @Serializable - * data class Address(val id: Long, val fullAddress: String) - * - * // 2. 定义 ResultKey - * object SelectAddressResultKey : NavigationResultKey
- * - * // 3. 在发送方使用 - * popBackStackWithResult(SelectAddressResultKey, address) - * ``` - * - * 在接收方使用: - * ```kotlin - * navController.collectResult(SelectAddressResultKey) { address -> - * // address 是强类型的 Address 对象,绝对类型安全 - * viewModel.updateAddress(address) - * } - * ``` - * - * @author Joker.X - */ - suspend fun popBackStackWithResult(key: NavigationResultKey, result: T) { - _navigationEvents.emit(NavigationEvent.PopBackStackWithResult(key, result)) - } - - /** - * 返回到指定路由 - * - * @param route 目标路由对象(必须是 @Serializable) - * @param inclusive 是否包含目标路由本身(true: 目标也会被弹出,false: 保留目标) - * - * 使用示例: - * ```kotlin - * // 返回到主页并保留主页 - * navigateBackTo(MainRoutes.Main, inclusive = false) - * - * // 返回到登录页并移除登录页(重新加载登录页) - * navigateBackTo(AuthRoutes.Login, inclusive = true) - * ``` - * - * @author Joker.X - */ - suspend fun navigateBackTo(route: Any, inclusive: Boolean = false) { - _navigationEvents.emit(NavigationEvent.NavigateBackTo(route, inclusive)) - } -} - -/** - * 处理导航事件的 NavController 扩展函数 - * - * 将导航事件转换为实际的导航操作 - * - * @param event 导航事件 - * @author Joker.X - */ -fun NavController.handleNavigationEvent(event: NavigationEvent) { - when (event) { - is NavigationEvent.NavigateTo -> { - // 使用类型安全的 navigate 方法 - this.navigate(event.route, event.navOptions) - } - - is NavigationEvent.NavigateUp -> { - // 简单返回,不携带结果 - this.popBackStack() - } - - is NavigationEvent.PopBackStackWithResult<*> -> { - // 使用 NavigationResultKey 的类型安全结果回传 - // 通过 key.serialize 将结果转换为 SavedStateHandle 支持的类型 - @Suppress("UNCHECKED_CAST") - val key = event.key as NavigationResultKey - val rawValue = key.serialize(event.result) - previousBackStackEntry?.savedStateHandle?.set(key.key, rawValue) - this.popBackStack() - } - - is NavigationEvent.NavigateBackTo -> { - // 弹出回退栈到指定路由 - this.popBackStack(event.route, event.inclusive) - } - } -} diff --git a/app/src/main/java/com/joker/kit/navigation/NavigationEvent.kt b/app/src/main/java/com/joker/kit/navigation/NavigationEvent.kt deleted file mode 100644 index 5e82e87..0000000 --- a/app/src/main/java/com/joker/kit/navigation/NavigationEvent.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.joker.kit.navigation - -import androidx.navigation.NavOptions - -/** - * 导航事件 - * 定义所有可能的导航操作类型 - * - * @author Joker.X - */ -sealed class NavigationEvent { - /** - * 导航到指定路由 - * - * @param route 类型安全的路由对象(必须是 @Serializable) - * @param navOptions 导航选项 - * @author Joker.X - */ - data class NavigateTo( - val route: Any, - val navOptions: NavOptions? = null - ) : NavigationEvent() - - /** - * 返回上一页(简单返回,无结果) - * - * @author Joker.X - */ - data object NavigateUp : NavigationEvent() - - /** - * 返回上一页并携带类型安全的结果(使用 NavigationResultKey) - * - * @param key 类型安全的结果 Key - * @param result 返回结果 - * @author Joker.X - */ - data class PopBackStackWithResult( - val key: NavigationResultKey, - val result: T - ) : NavigationEvent() - - /** - * 返回到指定路由 - * - * @param route 类型安全的路由对象(必须是 @Serializable) - * @param inclusive 是否包含目标路由本身 - * @author Joker.X - */ - data class NavigateBackTo( - val route: Any, - val inclusive: Boolean = false - ) : NavigationEvent() -} diff --git a/app/src/main/java/com/joker/kit/navigation/RefreshResultKey.kt b/app/src/main/java/com/joker/kit/navigation/RefreshResultKey.kt deleted file mode 100644 index 9531608..0000000 --- a/app/src/main/java/com/joker/kit/navigation/RefreshResultKey.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.joker.kit.navigation - -/** - * 通用的页面刷新结果 Key。 - * - * 语义等价于以前的 "refresh" 布尔标记: - * - true 表示上一个页面需要刷新数据 - * - false 或 null 表示不刷新 - * - * 示例: - * ```kotlin - * // 子页面:操作成功后返回并通知上一个页面刷新 - * popBackStackWithResult(RefreshResultKey, true) - * - * // 上一个页面(ViewModel): - * fun observeRefresh(backStackEntry: NavBackStackEntry?) { - * observeRefreshState(backStackEntry, RefreshResultKey) - * } - * ``` - * - * @author Joker.X - */ -object RefreshResultKey : NavigationResultKey diff --git a/app/src/main/java/com/joker/kit/navigation/RouteInterceptor.kt b/app/src/main/java/com/joker/kit/navigation/RouteInterceptor.kt deleted file mode 100644 index 20bc9cd..0000000 --- a/app/src/main/java/com/joker/kit/navigation/RouteInterceptor.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.joker.kit.navigation - -import com.joker.kit.navigation.routes.AuthRoutes -import com.joker.kit.navigation.routes.UserRoutes -import kotlin.reflect.KClass - -/** - * 路由拦截器(类型安全版本) - * - * 负责管理需要登录的页面配置和路由拦截逻辑 - * 使用类型安全的方式处理路由拦截 - * - * @author Joker.X - */ -class RouteInterceptor { - - /** - * 需要登录的路由类型集合 - * 在这里配置所有需要登录才能访问的页面类型 - */ - private val loginRequiredRouteTypes: MutableSet> = mutableSetOf( - UserRoutes.Info::class - ) - - /** - * 检查指定路由对象是否需要登录 - * - * @param route 要检查的路由对象(类型安全) - * @return true表示需要登录,false表示不需要登录 - * @author Joker.X - */ - fun requiresLogin(route: Any): Boolean { - val routeClass = route::class - return loginRequiredRouteTypes.contains(routeClass) - } - - /** - * 获取登录页面路由对象 - * - * @return 登录页面的路由对象 - * @author Joker.X - */ - fun getLoginRoute(): Any = AuthRoutes.Login - - /** - * 添加需要登录的路由类型 - * - * @param routeClass 需要登录的路由类型 - * @author Joker.X - */ - fun addLoginRequiredRoute(routeClass: KClass<*>) { - loginRequiredRouteTypes.add(routeClass) - } - - /** - * 移除需要登录的路由类型 - * - * @param routeClass 不再需要登录的路由类型 - * @author Joker.X - */ - fun removeLoginRequiredRoute(routeClass: KClass<*>) { - loginRequiredRouteTypes.remove(routeClass) - } - - /** - * 获取所有需要登录的路由类型 - * - * @return 需要登录的路由类型集合 - * @author Joker.X - */ - fun getLoginRequiredRoutes(): Set> { - return loginRequiredRouteTypes.toSet() - } -} diff --git a/app/src/main/java/com/joker/kit/navigation/extension/NavigationResultExt.kt b/app/src/main/java/com/joker/kit/navigation/extension/NavigationResultExt.kt deleted file mode 100644 index f663497..0000000 --- a/app/src/main/java/com/joker/kit/navigation/extension/NavigationResultExt.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.joker.kit.navigation.extension - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.navigation.NavController -import com.joker.kit.navigation.NavigationResultKey - -/** - * 监听返回结果扩展 - * - * @param key 结果键,定义序列化/反序列化规则 - * @param onResult 结果回调 - * @param T 结果数据类型 - * @author Joker.X - */ -@Composable -fun NavController.observeResult( - key: NavigationResultKey, - onResult: (T) -> Unit -) { - val backStackEntry = currentBackStackEntry ?: return - val savedStateHandle = backStackEntry.savedStateHandle - - DisposableEffect(backStackEntry, key) { - val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_RESUME) { - val raw = savedStateHandle.get(key.key) - if (raw != null) { - val result = key.deserialize(raw) - onResult(result) - savedStateHandle.remove(key.key) - } - } - } - backStackEntry.lifecycle.addObserver(observer) - onDispose { - backStackEntry.lifecycle.removeObserver(observer) - } - } -} diff --git a/app/src/main/java/com/joker/kit/navigation/routes/AuthRoutes.kt b/app/src/main/java/com/joker/kit/navigation/routes/AuthRoutes.kt deleted file mode 100644 index bbd4698..0000000 --- a/app/src/main/java/com/joker/kit/navigation/routes/AuthRoutes.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.joker.kit.navigation.routes - -import kotlinx.serialization.Serializable - -/** - * 用户认证相关路由 - * 这里只提供一个示例登录路由 - */ -object AuthRoutes { - - /** - * 登录页 - */ - @Serializable - data object Login -} diff --git a/app/src/main/java/com/joker/kit/navigation/routes/DemoRoutes.kt b/app/src/main/java/com/joker/kit/navigation/routes/DemoRoutes.kt deleted file mode 100644 index 6551130..0000000 --- a/app/src/main/java/com/joker/kit/navigation/routes/DemoRoutes.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.joker.kit.navigation.routes - -import kotlinx.serialization.Serializable - -/** - * Demo 模块路由 - */ -object DemoRoutes { - /** Network Demo 示例页 */ - @Serializable - data object NetworkDemo - - /** Network List Demo 示例页 */ - @Serializable - data object NetworkListDemo - - /** 数据库示例页 */ - @Serializable - data object Database - - /** 本地存储示例页 */ - @Serializable - data object LocalStorage - - /** 状态管理示例页 */ - @Serializable - data object StateManagement - - /** 通用网络请求示例页 */ - @Serializable - data object NetworkRequest - - /** 带参跳转示例页 */ - @Serializable - data class NavigationWithArgs( - val goodsId: Long - ) - - /** 结果回传示例页 */ - @Serializable - data object NavigationResult -} diff --git a/app/src/main/java/com/joker/kit/navigation/routes/UserRoutes.kt b/app/src/main/java/com/joker/kit/navigation/routes/UserRoutes.kt deleted file mode 100644 index 62fb2be..0000000 --- a/app/src/main/java/com/joker/kit/navigation/routes/UserRoutes.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.joker.kit.navigation.routes - -import kotlinx.serialization.Serializable - -/** - * 用户相关路由 - */ -object UserRoutes { - - /** - * 用户信息页 - * 登录后才能访问的用户信息页 - */ - @Serializable - data object Info -} diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index ae3637a..a29c315 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,33 +3,34 @@ android:height="108dp" android:viewportWidth="1024" android:viewportHeight="1024"> - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 7353dbd..ac94b34 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 7353dbd..ac94b34 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index da6868a..3196137 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1,6 +1,6 @@ AndroidProject-Compose - + Go Shopping No Data diff --git a/gradle.properties b/gradle.properties index 20e2a01..a068140 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,9 +15,10 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +android.enableJetifier=false # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..3a9aa65 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,13 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/23adb857f3cb3cbe28750bc7faa7abc0/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/056dc25d3b9d168ede8b94d3d2f99942/redirect +toolchainVendor=JETBRAINS +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 237b318..a1a1fa3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # 构建工具版本 # Android Gradle Plugin: https://developer.android.com/studio/releases/gradle-plugin -agp = "9.0.0" +agp = "9.0.1" # Kotlin 编译器: https://kotlinlang.org/docs/releases.html kotlin = "2.3.10" @@ -9,7 +9,7 @@ kotlin = "2.3.10" # AndroidX Core KTX: https://developer.android.com/jetpack/androidx/releases/core coreKtx = "1.17.0" # AndroidX Activity Compose: https://developer.android.com/jetpack/androidx/releases/activity -activityCompose = "1.12.3" +activityCompose = "1.12.4" # AndroidX Lifecycle Runtime KTX: https://developer.android.com/jetpack/androidx/releases/lifecycle lifecycleRuntimeKtx = "2.10.0" @@ -24,11 +24,13 @@ espressoCore = "3.7.0" # Jetpack Compose 相关版本 # Compose BOM: https://developer.android.com/jetpack/compose/bom # 说明: BOM 统一管理所有 Compose 库版本,确保兼容性 -composeBom = "2026.01.01" +composeBom = "2026.02.00" # 导航相关版本 -# Navigation Compose: https://developer.android.com/jetpack/androidx/releases/navigation -navigationCompose = "2.9.7" +# AndroidX Navigation3: https://developer.android.com/jetpack/androidx/releases/navigation3 +navigation3 = "1.0.1" +# AndroidX Lifecycle: https://developer.android.com/jetpack/androidx/releases/lifecycle +lifecycleViewModelNavigation3 = "2.10.0" # 序列化相关版本 # Kotlinx Serialization JSON: https://github.com/Kotlin/kotlinx.serialization @@ -56,8 +58,8 @@ timber = "5.0.1" # GitHub: https://github.com/google/dagger # 说明: 基于 Dagger 的 Android 依赖注入库 hilt = "2.59.1" -# Hilt Navigation Compose: https://developer.android.com/jetpack/androidx/releases/hilt -hiltNavigationCompose = "1.3.0" +# Hilt Lifecycle ViewModel Compose: https://developer.android.com/jetpack/androidx/releases/hilt +hiltLifecycleViewModelCompose = "1.3.0" # KSP (Kotlin Symbol Processing): https://github.com/google/ksp # 说明: Kotlin 注解处理器,用于代码生成 ksp = "2.3.4" @@ -115,7 +117,9 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } # 导航 -navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +androidx-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" } +androidx-navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" } +androidx-lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewModelNavigation3" } # 序列化 kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } @@ -134,7 +138,7 @@ ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devto hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } -hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +hilt-lifecycle-viewmodel-compose = { module = "androidx.hilt:hilt-lifecycle-viewmodel-compose", version.ref = "hiltLifecycleViewModelCompose" } # 图片加载 coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }