mirror of
https://github.com/Joker-x-dev/AndroidProject-Compose.git
synced 2026-02-20 02:38:08 +00:00
初步重构导航到 nav3
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ import javax.inject.Inject
|
||||
*/
|
||||
@HiltViewModel
|
||||
class MainActivityViewModel @Inject constructor(
|
||||
) : ViewModel() {}
|
||||
) : ViewModel()
|
||||
@@ -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<T : Any>(
|
||||
navigator: AppNavigator,
|
||||
userState: UserState
|
||||
) : BaseViewModel(navigator, userState) {
|
||||
abstract class BaseNetWorkListViewModel<T : Any> : BaseViewModel() {
|
||||
/**
|
||||
* 刷新结果监听任务
|
||||
*
|
||||
* 用于保证只注册一次刷新结果监听,避免重复 collect 导致重复刷新和内存浪费。
|
||||
* 当该任务不为 null 时,表示当前 ViewModel 已经建立监听。
|
||||
*/
|
||||
private var refreshObserveJob: Job? = null
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
@@ -274,34 +274,26 @@ abstract class BaseNetWorkListViewModel<T : Any>(
|
||||
/**
|
||||
* 视图层调用此方法,监听页面刷新信号(基于 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<Boolean> = RefreshResultKey
|
||||
key: NavigationResultKey<RefreshResult> = RefreshResultKey
|
||||
) {
|
||||
if (backStackEntry == null) return
|
||||
val owner: LifecycleOwner = backStackEntry
|
||||
backStackEntry.savedStateHandle
|
||||
.getLiveData<Boolean>(key.key)
|
||||
.observe(owner, Observer<Boolean> { value ->
|
||||
if (value) {
|
||||
if (refreshObserveJob != null) return
|
||||
refreshObserveJob = viewModelScope.launch {
|
||||
resultEvents(key).collect { refreshResult ->
|
||||
if (refreshResult.refresh == true) {
|
||||
onRefresh()
|
||||
// 只刷新一次
|
||||
backStackEntry.savedStateHandle[key.key] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>(
|
||||
navigator: AppNavigator,
|
||||
userState: UserState,
|
||||
protected val savedStateHandle: SavedStateHandle? = null,
|
||||
) : BaseViewModel(navigator, userState) {
|
||||
abstract class BaseNetWorkViewModel<T> : BaseViewModel() {
|
||||
/**
|
||||
* 刷新结果监听任务
|
||||
*
|
||||
* 用于保证只注册一次刷新结果监听,避免重复 collect 导致重复请求。
|
||||
* 当该任务不为 null 时,表示当前 ViewModel 已经建立监听。
|
||||
*/
|
||||
private var refreshObserveJob: Job? = null
|
||||
|
||||
/**
|
||||
* 通用网络请求UI状态
|
||||
@@ -176,34 +173,26 @@ abstract class BaseNetWorkViewModel<T>(
|
||||
/**
|
||||
* 视图层调用此方法,监听页面刷新信号(基于 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<Boolean> = RefreshResultKey
|
||||
key: NavigationResultKey<RefreshResult> = RefreshResultKey
|
||||
) {
|
||||
if (backStackEntry == null) return
|
||||
val owner: LifecycleOwner = backStackEntry
|
||||
backStackEntry.savedStateHandle
|
||||
.getLiveData<Boolean>(key.key)
|
||||
.observe(owner, Observer<Boolean> { value ->
|
||||
if (value) {
|
||||
if (refreshObserveJob != null) return
|
||||
refreshObserveJob = viewModelScope.launch {
|
||||
resultEvents(key).collect { refreshResult ->
|
||||
if (refreshResult.refresh == true) {
|
||||
executeRequest()
|
||||
// 只刷新一次
|
||||
backStackEntry.savedStateHandle[key.key] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Address>
|
||||
*
|
||||
* // 3. 返回时携带结果
|
||||
* popBackStackWithResult(SelectAddressResultKey, address)
|
||||
* ```
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun <T> popBackStackWithResult(key: NavigationResultKey<T>, 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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,7 +57,7 @@ class UserInfoStoreDataSourceImpl @Inject constructor() : UserInfoStoreDataSourc
|
||||
* @author Joker.X
|
||||
*/
|
||||
override suspend fun updateUserInfo(updates: Map<String, Any?>) {
|
||||
val currentUser = getUserInfo() ?: return
|
||||
getUserInfo() ?: return
|
||||
val userJson = MMKVUtils.getString(KEY_USER_INFO, "")
|
||||
if (userJson.isEmpty()) return
|
||||
|
||||
|
||||
126
app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt
Normal file
126
app/src/main/java/com/joker/kit/core/navigation/AppNavHost.kt
Normal file
@@ -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<IntOffset> =
|
||||
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()
|
||||
}
|
||||
192
app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt
Normal file
192
app/src/main/java/com/joker/kit/core/navigation/AppNavigator.kt
Normal file
@@ -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<NavigationCommand>()
|
||||
|
||||
/**
|
||||
* 页面结果事件流
|
||||
*/
|
||||
private val _resultEvents = MutableSharedFlow<ResultEvent>(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 <T> popBackStackWithResult(key: NavigationResultKey<T>, 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 <T> resultEvents(key: NavigationResultKey<T>): Flow<T> {
|
||||
return _resultEvents
|
||||
.filter { it.key == key.key }
|
||||
.map { key.deserialize(it.rawValue) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 分发回传结果事件
|
||||
*
|
||||
* @param key 类型安全结果 Key
|
||||
* @param result 返回结果
|
||||
* @author Joker.X
|
||||
*/
|
||||
internal fun <T> dispatchResult(key: NavigationResultKey<T>, 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,
|
||||
)
|
||||
@@ -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<NavKey>,
|
||||
navigator: AppNavigator,
|
||||
): NavigationController {
|
||||
return remember(backStack, navigator) {
|
||||
BackStackNavigationController(backStack = backStack, navigator = navigator)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 BackStack 的导航控制器实现
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
private class BackStackNavigationController(
|
||||
/**
|
||||
* 回退栈
|
||||
*/
|
||||
private val backStack: NavBackStack<NavKey>,
|
||||
/**
|
||||
* 导航管理器
|
||||
*/
|
||||
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 <T> popBackStackWithResult(key: NavigationResultKey<T>, result: T) {
|
||||
navigator.dispatchResult(key = key, result = result)
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BackStack 按路由弹栈
|
||||
*
|
||||
* @param route 目标路由
|
||||
* @param inclusive 是否包含目标路由
|
||||
* @author Joker.X
|
||||
*/
|
||||
private fun NavBackStack<NavKey>.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()
|
||||
}
|
||||
@@ -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<T>(
|
||||
val key: NavigationResultKey<T>,
|
||||
val result: T,
|
||||
) : NavigationCommand {
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param controller 导航控制器
|
||||
* @author Joker.X
|
||||
*/
|
||||
override fun execute(controller: NavigationController) {
|
||||
controller.popBackStackWithResult(key, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <T> popBackStackWithResult(key: NavigationResultKey<T>, result: T)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.joker.kit.core.navigation
|
||||
|
||||
/**
|
||||
* 公共导航返回结果定义
|
||||
*
|
||||
* 用于沉淀跨模块可复用的页面返回结果模型。
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
data class RefreshResult(
|
||||
/**
|
||||
* 是否需要刷新
|
||||
*
|
||||
* true 表示需要刷新,false 或 null 表示不刷新。
|
||||
*/
|
||||
val refresh: Boolean? = null,
|
||||
)
|
||||
@@ -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<T> {
|
||||
/**
|
||||
* 底层用于 SavedStateHandle 存储的字符串 key。
|
||||
* 底层用于结果分发的字符串 key。
|
||||
*
|
||||
* 默认实现使用 Key 对象自身的完全限定类名,保证全局唯一且无需手写字符串。
|
||||
*/
|
||||
@@ -35,7 +35,7 @@ interface NavigationResultKey<T> {
|
||||
get() = this::class.java.name
|
||||
|
||||
/**
|
||||
* 将结果对象序列化为 SavedStateHandle 可接受的底层类型。
|
||||
* 将结果对象序列化为可分发的底层类型。
|
||||
*
|
||||
* 默认实现为透传(即直接存储原始对象),适用于 Boolean、Int、String 等基础类型。
|
||||
* 复杂类型可以在具体的 Key 中重写此方法,例如序列化为 JSON 字符串。
|
||||
@@ -43,7 +43,7 @@ interface NavigationResultKey<T> {
|
||||
fun serialize(value: T): Any = value as Any
|
||||
|
||||
/**
|
||||
* 从 SavedStateHandle 中还原结果对象。
|
||||
* 从结果分发流中还原结果对象。
|
||||
*
|
||||
* 默认实现为简单强转,复杂类型的 Key 需要重写以配合 [serialize]。
|
||||
*/
|
||||
@@ -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 <T> popBackStackWithResult(key: NavigationResultKey<T>, result: T) {
|
||||
requireNavigator().popBackStackWithResult(key = key, result = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页并携带结果(语义化别名)
|
||||
*
|
||||
* @param key 结果 Key
|
||||
* @param result 返回结果
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun <T> navigateBackWithResult(key: NavigationResultKey<T>, result: T) {
|
||||
popBackStackWithResult(key = key, result = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听指定结果 Key 的结果流
|
||||
*
|
||||
* @param key 结果 Key
|
||||
* @return 对应结果流
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun <T> resultEvents(key: NavigationResultKey<T>): Flow<T> {
|
||||
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 <T> popBackStackWithResult(key: NavigationResultKey<T>, result: T) {
|
||||
NavigationService.popBackStackWithResult(key = key, result = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页并携带结果(语义化别名)
|
||||
*
|
||||
* @param key 结果 Key
|
||||
* @param result 返回结果
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun <T> navigateBackWithResult(key: NavigationResultKey<T>, result: T) {
|
||||
NavigationService.navigateBackWithResult(key = key, result = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听指定结果 Key 的结果流
|
||||
*
|
||||
* @param key 结果 Key
|
||||
* @return 对应结果流
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun <T> resultEvents(key: NavigationResultKey<T>): Flow<T> {
|
||||
return NavigationService.resultEvents(key)
|
||||
}
|
||||
@@ -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<RefreshResult>
|
||||
@@ -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<KClass<out NavKey>> = 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<DemoResult> {
|
||||
/**
|
||||
* 序列化结果
|
||||
*
|
||||
* @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<DemoResult> {
|
||||
/**
|
||||
* 反序列化结果
|
||||
*
|
||||
* @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<DemoResult> {
|
||||
/**
|
||||
* Demo 结果数据
|
||||
*
|
||||
* @param id 结果标识
|
||||
* @param id 结果 ID
|
||||
* @param message 结果信息
|
||||
* @author Joker.X
|
||||
*/
|
||||
@Serializable
|
||||
data class DemoResult(
|
||||
val id: Long,
|
||||
val message: String
|
||||
val message: String,
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,6 @@ import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.text.isNotBlank
|
||||
|
||||
/**
|
||||
* 认证拦截器 - 添加授权头信息
|
||||
|
||||
@@ -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 计数器状态
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
* 基础网络列表视图组件
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
* 基础网络视图组件,用于处理网络请求的三种状态:加载中、错误和成功
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NavKey>.authGraph() {
|
||||
entry<AuthRoutes.Login> {
|
||||
LoginRoute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AuthRoutes.Login> {
|
||||
LoginRoute()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,演示路由拦截放行。
|
||||
|
||||
@@ -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<DemoRoutes.Database> {
|
||||
DatabaseRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<NavKey>.demoGraph() {
|
||||
entry<DemoRoutes.NetworkDemo> {
|
||||
NetworkDemoRoute()
|
||||
}
|
||||
entry<DemoRoutes.NetworkListDemo> {
|
||||
NetworkListDemoRoute()
|
||||
}
|
||||
entry<DemoRoutes.Database> {
|
||||
DatabaseRoute()
|
||||
}
|
||||
entry<DemoRoutes.LocalStorage> {
|
||||
LocalStorageRoute()
|
||||
}
|
||||
entry<DemoRoutes.StateManagement> {
|
||||
StateManagementRoute()
|
||||
}
|
||||
entry<DemoRoutes.NetworkRequest> {
|
||||
NetworkRequestRoute()
|
||||
}
|
||||
entry<DemoRoutes.NavigationWithArgs> { key ->
|
||||
NavigationWithArgsRoute(navKey = key)
|
||||
}
|
||||
entry<DemoRoutes.NavigationResult> {
|
||||
NavigationResultRoute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DemoRoutes.LocalStorage> {
|
||||
LocalStorageRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.NavigationResult> {
|
||||
NavigationResultRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.NavigationWithArgs> {
|
||||
NavigationWithArgsRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.NetworkDemo> {
|
||||
NetworkDemoRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.NetworkListDemo> {
|
||||
NetworkListDemoRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.NetworkRequest> {
|
||||
NetworkRequestRoute()
|
||||
}
|
||||
}
|
||||
@@ -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<DemoRoutes.StateManagement> {
|
||||
StateManagementRoute()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?: "未设置昵称"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<NavigationWithArgsViewModel, NavigationWithArgsViewModel.Factory>(
|
||||
creationCallback = { factory ->
|
||||
factory.create(navKey)
|
||||
}
|
||||
)
|
||||
) {
|
||||
NavigationWithArgsScreen(
|
||||
goodsId = viewModel.goodsId,
|
||||
onBackClick = viewModel::navigateBack
|
||||
onBackClick = ::navigateBack
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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("")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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() {
|
||||
/**
|
||||
* 回传结果并返回上一页
|
||||
*
|
||||
|
||||
@@ -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<DemoRoutes.NavigationWithArgs>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Goods>(navigator, userState) {
|
||||
) : BaseNetWorkViewModel<Goods>() {
|
||||
|
||||
init {
|
||||
super.executeRequest()
|
||||
|
||||
@@ -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<Goods>(
|
||||
navigator = navigator,
|
||||
userState = userState
|
||||
) {
|
||||
|
||||
init {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
/**
|
||||
* 商品信息
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<DemoCardInfo> = 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<DemoCardInfo> = 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() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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<NavKey>.mainGraph() {
|
||||
entry<MainRoutes.Main> {
|
||||
MainRoute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MainRoutes.Main> {
|
||||
MainRoute(navController = navController)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ internal fun CoreDemoRoute(
|
||||
CoreDemoScreen(
|
||||
cards = cards,
|
||||
counter = count,
|
||||
onCardClick = viewModel::onCardClick
|
||||
onCardClick = { info -> info.navigateAction?.invoke() }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<List<DemoCardInfo>> = _cards.asStateFlow()
|
||||
|
||||
/**
|
||||
@@ -40,14 +41,4 @@ class CoreDemoViewModel @Inject constructor(
|
||||
* @author Joker.X
|
||||
*/
|
||||
val count: StateFlow<Int> = counterState.count
|
||||
|
||||
/**
|
||||
* 处理卡片点击
|
||||
*
|
||||
* @param info 被点击的卡片信息
|
||||
* @author Joker.X
|
||||
*/
|
||||
fun onCardClick(info: DemoCardInfo) {
|
||||
info.route?.let { navigate(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MainUiState> = _uiState.asStateFlow()
|
||||
|
||||
@@ -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<List<DemoCardInfo>> = _cards.asStateFlow()
|
||||
|
||||
/**
|
||||
* 全局登录状态
|
||||
*
|
||||
* @return 登录状态流
|
||||
* @author Joker.X
|
||||
*/
|
||||
val isLoggedIn: StateFlow<Boolean> = userState.isLoggedIn
|
||||
|
||||
/**
|
||||
* Demo 结果状态源
|
||||
*/
|
||||
private val _demoResult = MutableStateFlow<DemoResult?>(null)
|
||||
val demoResult: StateFlow<DemoResult?> = _demoResult.asStateFlow()
|
||||
|
||||
/**
|
||||
* 处理卡片点击
|
||||
*
|
||||
* @param info 卡片信息
|
||||
* @author Joker.X
|
||||
* Demo 结果状态流
|
||||
*/
|
||||
fun onCardClick(info: DemoCardInfo) {
|
||||
info.route?.let { navigate(it) }
|
||||
}
|
||||
val demoResult: StateFlow<DemoResult?> = _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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NavKey>.userGraph() {
|
||||
entry<UserRoutes.Info> {
|
||||
UserInfoRoute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UserRoutes.Info> {
|
||||
UserInfoRoute()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
/**
|
||||
* 一键退出登录(本地清空)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<NavigationEvent>()
|
||||
val navigationEvents: SharedFlow<NavigationEvent> = _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<Address>
|
||||
*
|
||||
* // 3. 在发送方使用
|
||||
* popBackStackWithResult(SelectAddressResultKey, address)
|
||||
* ```
|
||||
*
|
||||
* 在接收方使用:
|
||||
* ```kotlin
|
||||
* navController.collectResult(SelectAddressResultKey) { address ->
|
||||
* // address 是强类型的 Address 对象,绝对类型安全
|
||||
* viewModel.updateAddress(address)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
suspend fun <T> popBackStackWithResult(key: NavigationResultKey<T>, 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<Any?>
|
||||
val rawValue = key.serialize(event.result)
|
||||
previousBackStackEntry?.savedStateHandle?.set(key.key, rawValue)
|
||||
this.popBackStack()
|
||||
}
|
||||
|
||||
is NavigationEvent.NavigateBackTo -> {
|
||||
// 弹出回退栈到指定路由
|
||||
this.popBackStack(event.route, event.inclusive)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T>(
|
||||
val key: NavigationResultKey<T>,
|
||||
val result: T
|
||||
) : NavigationEvent()
|
||||
|
||||
/**
|
||||
* 返回到指定路由
|
||||
*
|
||||
* @param route 类型安全的路由对象(必须是 @Serializable)
|
||||
* @param inclusive 是否包含目标路由本身
|
||||
* @author Joker.X
|
||||
*/
|
||||
data class NavigateBackTo(
|
||||
val route: Any,
|
||||
val inclusive: Boolean = false
|
||||
) : NavigationEvent()
|
||||
}
|
||||
@@ -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<Boolean>
|
||||
@@ -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<KClass<out Any>> = 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<KClass<*>> {
|
||||
return loginRequiredRouteTypes.toSet()
|
||||
}
|
||||
}
|
||||
@@ -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 <T> NavController.observeResult(
|
||||
key: NavigationResultKey<T>,
|
||||
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<Any?>(key.key)
|
||||
if (raw != null) {
|
||||
val result = key.deserialize(raw)
|
||||
onResult(result)
|
||||
savedStateHandle.remove<Any?>(key.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
backStackEntry.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
backStackEntry.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.joker.kit.navigation.routes
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 用户认证相关路由
|
||||
* 这里只提供一个示例登录路由
|
||||
*/
|
||||
object AuthRoutes {
|
||||
|
||||
/**
|
||||
* 登录页
|
||||
*/
|
||||
@Serializable
|
||||
data object Login
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.joker.kit.navigation.routes
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 用户相关路由
|
||||
*/
|
||||
object UserRoutes {
|
||||
|
||||
/**
|
||||
* 用户信息页
|
||||
* 登录后才能访问的用户信息页
|
||||
*/
|
||||
@Serializable
|
||||
data object Info
|
||||
}
|
||||
@@ -3,33 +3,34 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<group android:scaleX="0.6068966"
|
||||
android:scaleY="0.6068966"
|
||||
android:translateX="235.79463"
|
||||
android:translateY="204.1461">
|
||||
<path
|
||||
android:pathData="M373.8,202.3H189.6a67.8,67.8 0,0 0,-67.6 67.8,65.6 65.6,0 0,0 65.6,67.6h188.2l-2,-135.5z"
|
||||
android:fillColor="#7585FF"/>
|
||||
<path
|
||||
android:pathData="M793.5,805.2H189.4a67.8,67.8 0,0 1,-67.4 -67.6V269.7a67.8,67.8 0,0 0,72 68.1h501.1s98.5,-8.5 98.5,65.6v401.8z"
|
||||
android:fillColor="#465CFF"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M474.5,479.8a37.6,37.6 0,1 0,14.4 72.2,37.4 37.4,0 0,0 -14.4,-72.2z"/>
|
||||
<path
|
||||
android:pathData="M511.7,565.6a60.8,60.8 0,0 0,-34.8 -109.4h2a57.6,57.6 0,0 1,10.7 0L489.6,418.5a12,12 0,0 0,-5.7 -10.7,11.8 11.8,0 0,0 -12,0 12,12 0,0 0,-5.7 10.7v38.3a61.1,61.1 0,0 0,-26.7 109.4L328.1,804.3h72.6l51.4,-109.4a26.7,26.7 0,0 1,24.1 -15.1,26.3 26.3,0 0,1 23.9,15.1l53,109.4h73.5L511.7,565.6zM589.4,793.6a37.6,37.6 0,0 1,-26.5 -64.1,37.4 37.4,0 0,1 64.1,26.5 37.6,37.6 0,0 1,-37.6 37.6z"
|
||||
android:fillColor="#3870B2"/>
|
||||
<path
|
||||
android:pathData="M334,235.6h258.4v47.5H334v-47.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M454.4,479.8a37.4,37.4 0,1 1,-26.7 10.9,38.1 38.1,0 0,1 26.7,-10.9zM469.2,457.9L469.2,418.5a12,12 0,0 0,-5.7 -10.7,11.8 11.8,0 0,0 -12,0 12,12 0,0 0,-5.7 10.7v38.3a61.1,61.1 0,0 0,-26.7 109.4L279.3,867.1a33.3,33.3 0,0 0,2.4 33.5,33.7 33.7,0 0,0 30.4,14.2 33,33 0,0 0,27.1 -19.7l93.2,-200.2a26.9,26.9 0,0 1,24.1 -15.1,26.3 26.3,0 0,1 23.9,15.1l95.6,199.1a33,33 0,1 0,59.5 -28.9L491.6,565.6a60.6,60.6 0,0 0,21.9 -60.6,61.3 61.3,0 0,0 -43.8,-46.8"
|
||||
android:fillColor="#073042"/>
|
||||
<path
|
||||
android:pathData="M555,277.4a17.5,17.5 0,0 1,-12.7 -30,17.7 17.7,0 0,1 30.2,12.5 17.5,17.5 0,0 1,-17.5 17.5m-194.5,0a17.5,17.5 0,0 1,-12.5 -30,17.7 17.7,0 1,1 24.9,24.9 17.3,17.3 0,0 1,-12.5 5M561.4,171.3l35,-60.8a7.4,7.4 0,0 0,-12.7 -7.4l-35.4,61.7a220.6,220.6 0,0 0,-180.8 0l-35.7,-61.7a7.4,7.4 0,0 0,-6.3 -3.7,8.1 8.1,0 0,0 -6.3,3.7 7.4,7.4 0,0 0,0 7.4l35.2,60.8a208.3,208.3 0,0 0,-107.7 166.5h422.3A208.3,208.3 0,0 0,561.4 171.3"
|
||||
android:fillColor="#3DDC84"/>
|
||||
<path
|
||||
android:pathData="M732.9,467.6h-27.8a3.5,3.5 0,0 0,-3.5 3.5v438.7a3.5,3.5 0,0 0,3.3 3.5h28.7a60.8,60.8 0,0 0,60.6 -60.8V406.7a60.8,60.8 0,0 1,-61.3 60.8z"
|
||||
android:fillColor="#7585FF"/>
|
||||
</group>
|
||||
<group
|
||||
android:scaleX="0.6068966"
|
||||
android:scaleY="0.6068966"
|
||||
android:translateX="235.79463"
|
||||
android:translateY="204.1461">
|
||||
<path
|
||||
android:fillColor="#7585FF"
|
||||
android:pathData="M373.8,202.3H189.6a67.8,67.8 0,0 0,-67.6 67.8,65.6 65.6,0 0,0 65.6,67.6h188.2l-2,-135.5z" />
|
||||
<path
|
||||
android:fillColor="#465CFF"
|
||||
android:pathData="M793.5,805.2H189.4a67.8,67.8 0,0 1,-67.4 -67.6V269.7a67.8,67.8 0,0 0,72 68.1h501.1s98.5,-8.5 98.5,65.6v401.8z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M474.5,479.8a37.6,37.6 0,1 0,14.4 72.2,37.4 37.4,0 0,0 -14.4,-72.2z" />
|
||||
<path
|
||||
android:fillColor="#3870B2"
|
||||
android:pathData="M511.7,565.6a60.8,60.8 0,0 0,-34.8 -109.4h2a57.6,57.6 0,0 1,10.7 0L489.6,418.5a12,12 0,0 0,-5.7 -10.7,11.8 11.8,0 0,0 -12,0 12,12 0,0 0,-5.7 10.7v38.3a61.1,61.1 0,0 0,-26.7 109.4L328.1,804.3h72.6l51.4,-109.4a26.7,26.7 0,0 1,24.1 -15.1,26.3 26.3,0 0,1 23.9,15.1l53,109.4h73.5L511.7,565.6zM589.4,793.6a37.6,37.6 0,0 1,-26.5 -64.1,37.4 37.4,0 0,1 64.1,26.5 37.6,37.6 0,0 1,-37.6 37.6z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M334,235.6h258.4v47.5H334v-47.5z" />
|
||||
<path
|
||||
android:fillColor="#073042"
|
||||
android:pathData="M454.4,479.8a37.4,37.4 0,1 1,-26.7 10.9,38.1 38.1,0 0,1 26.7,-10.9zM469.2,457.9L469.2,418.5a12,12 0,0 0,-5.7 -10.7,11.8 11.8,0 0,0 -12,0 12,12 0,0 0,-5.7 10.7v38.3a61.1,61.1 0,0 0,-26.7 109.4L279.3,867.1a33.3,33.3 0,0 0,2.4 33.5,33.7 33.7,0 0,0 30.4,14.2 33,33 0,0 0,27.1 -19.7l93.2,-200.2a26.9,26.9 0,0 1,24.1 -15.1,26.3 26.3,0 0,1 23.9,15.1l95.6,199.1a33,33 0,1 0,59.5 -28.9L491.6,565.6a60.6,60.6 0,0 0,21.9 -60.6,61.3 61.3,0 0,0 -43.8,-46.8" />
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M555,277.4a17.5,17.5 0,0 1,-12.7 -30,17.7 17.7,0 0,1 30.2,12.5 17.5,17.5 0,0 1,-17.5 17.5m-194.5,0a17.5,17.5 0,0 1,-12.5 -30,17.7 17.7,0 1,1 24.9,24.9 17.3,17.3 0,0 1,-12.5 5M561.4,171.3l35,-60.8a7.4,7.4 0,0 0,-12.7 -7.4l-35.4,61.7a220.6,220.6 0,0 0,-180.8 0l-35.7,-61.7a7.4,7.4 0,0 0,-6.3 -3.7,8.1 8.1,0 0,0 -6.3,3.7 7.4,7.4 0,0 0,0 7.4l35.2,60.8a208.3,208.3 0,0 0,-107.7 166.5h422.3A208.3,208.3 0,0 0,561.4 171.3" />
|
||||
<path
|
||||
android:fillColor="#7585FF"
|
||||
android:pathData="M732.9,467.6h-27.8a3.5,3.5 0,0 0,-3.5 3.5v438.7a3.5,3.5 0,0 0,3.3 3.5h28.7a60.8,60.8 0,0 0,60.6 -60.8V406.7a60.8,60.8 0,0 1,-61.3 60.8z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">AndroidProject-Compose</string>
|
||||
|
||||
|
||||
<!-- Empty States -->
|
||||
<string name="empty_cart_btn">Go Shopping</string>
|
||||
<string name="empty_data">No Data</string>
|
||||
|
||||
Reference in New Issue
Block a user