fix: 优化登录状态管理与界面提示

- 在 MainActivity.kt 中调整登录状态管理逻辑,确保实时同步 TokenManager 的状态,并根据登录状态自动导航至主界面或登录页。
- 更新 NicknameSetupScreen.kt 中的图标描述文本,以提升用户体验。
- 在 AuthViewModel.kt 中重构 token 刷新逻辑,采用静默刷新策略,确保用户状态的稳定性而不影响当前登录状态。
This commit is contained in:
penghanyuan
2026-02-14 10:39:00 +01:00
parent 5b178b64d7
commit aa20ce3039
5 changed files with 60 additions and 52 deletions

View File

@@ -143,7 +143,9 @@ fun SystemUiController(
@PreviewScreenSizes @PreviewScreenSizes
@Composable @Composable
fun LifeechoApp(initialLoggedIn: Boolean = false) { fun LifeechoApp(initialLoggedIn: Boolean = false) {
var isLoggedIn by rememberSaveable { mutableStateOf(initialLoggedIn) } // 使用 remember非 rememberSaveable避免进程恢复时使用过时的登录状态
// 登录状态始终以 TokenManager 为准
var isLoggedIn by remember { mutableStateOf(initialLoggedIn) }
val navController = rememberNavController() val navController = rememberNavController()
var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.CHAT) } var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.CHAT) }
@@ -151,20 +153,25 @@ fun LifeechoApp(initialLoggedIn: Boolean = false) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
// 同步登录状态变化,并在登录状态恢复时自动导航到主界面 // 实时同步 TokenManager 的登录状态,并处理导航
androidx.compose.runtime.LaunchedEffect(TokenManager.isLoggedIn) { androidx.compose.runtime.LaunchedEffect(TokenManager.isLoggedIn) {
val newLoggedIn = TokenManager.isLoggedIn val newLoggedIn = TokenManager.isLoggedIn
val wasLoggedOut = !isLoggedIn val previousLoggedIn = isLoggedIn
isLoggedIn = newLoggedIn isLoggedIn = newLoggedIn
// 如果从未登录变为已登录,且当前在登录页,自动跳转到主界面 if (newLoggedIn && !previousLoggedIn) {
if (newLoggedIn && wasLoggedOut) { // 从未登录变为已登录:跳转到主界面
val currentRoute = navController.currentDestination?.route val route = navController.currentDestination?.route
if (currentRoute == Screen.Login.route || currentRoute == null) { if (route == Screen.Login.route || route == Screen.NicknameSetup.route || route == null) {
navController.navigate(Screen.ConversationList.route) { navController.navigate(Screen.ConversationList.route) {
popUpTo(0) { inclusive = true } popUpTo(0) { inclusive = true }
} }
} }
} else if (!newLoggedIn && previousLoggedIn) {
// 从已登录变为未登录如后台token刷新失败跳转到登录页
navController.navigate(Screen.Login.route) {
popUpTo(0) { inclusive = true }
}
} }
} }

View File

@@ -222,8 +222,8 @@ fun ConversationListScreen(
// 对话列表 // 对话列表
items(conversations, key = { it.id }) { conversation -> items(conversations, key = { it.id }) { conversation ->
// 兼容旧数据:将旧名称"回忆录助手"识别为默认助手 // 兼容旧数据:将"岁月知己"和旧名称"回忆录助手"识别为默认助手
val isAssistant = conversation.title == null || conversation.title == "回忆录助手" val isAssistant = conversation.title == null || conversation.title == "岁月知己" || conversation.title == "回忆录助手"
val displayTitle = if (isAssistant) "岁月知己" else conversation.title!! val displayTitle = if (isAssistant) "岁月知己" else conversation.title!!
val dto = ConversationListItemDto( val dto = ConversationListItemDto(
id = conversation.id, id = conversation.id,

View File

@@ -160,8 +160,7 @@ fun CreateMemoryScreen(
ChatHeader( ChatHeader(
title = "岁月知己", title = "岁月知己",
isOnline = connectionStatus == "已连接", isOnline = connectionStatus == "已连接",
onBackClick = { navController?.popBackStack() }, onBackClick = { navController?.popBackStack() }
onNewConversationClick = { viewModel.startConversation() }
) )
// WebSocket调试面板仅在开发模式下显示 // WebSocket调试面板仅在开发模式下显示

View File

@@ -75,7 +75,7 @@ fun NicknameSetupScreen(
// 欢迎图标 // 欢迎图标
Icon( Icon(
imageVector = AppIcons.PersonAdd, imageVector = AppIcons.PersonAdd,
contentDescription = "设置昵称", contentDescription = "怎么称呼你呢?",
modifier = Modifier.size(80.dp), modifier = Modifier.size(80.dp),
tint = LightPurple tint = LightPurple
) )

View File

@@ -54,62 +54,64 @@ class AuthViewModel(private val context: Context) : ViewModel() {
/** /**
* 检查认证状态 * 检查认证状态
* 启动时验证token有效性如果access_token过期则尝试用refresh_token刷新 *
* 策略只要本地有缓存的token就认为已登录直接进入主界面。
* 后台尝试验证和刷新token但不会因为网络错误而清除token。
* token的失效由AuthInterceptor在实际API调用时处理。
*/ */
fun checkAuthStatus() { fun checkAuthStatus() {
viewModelScope.launch { viewModelScope.launch {
val accessToken = TokenManager.getAccessToken() val accessToken = TokenManager.getAccessToken()
val refreshToken = TokenManager.getRefreshToken() val refreshToken = TokenManager.getRefreshToken()
val hasToken = !accessToken.isNullOrBlank() || !refreshToken.isNullOrBlank()
// 只要有缓存的token就认为已登录
_isLoggedIn.value = hasToken
if (!hasToken) return@launch
// 后台尝试获取用户信息best effort不影响登录状态
if (!accessToken.isNullOrBlank()) { if (!accessToken.isNullOrBlank()) {
// 有access_token尝试获取用户信息验证token有效性
val success = loadUserInfo(accessToken) val success = loadUserInfo(accessToken)
if (success) { if (!success && !refreshToken.isNullOrBlank()) {
_isLoggedIn.value = true // access_token可能过期尝试用refresh_token静默刷新
} else { tryRefreshTokenSilently(refreshToken)
// access_token可能过期尝试用refresh_token刷新
if (!refreshToken.isNullOrBlank()) {
tryRefreshAndRestore(refreshToken)
} else {
// 没有refresh_token需要重新登录
TokenManager.clearTokens()
_isLoggedIn.value = false
}
} }
} else if (!refreshToken.isNullOrBlank()) { } else if (!refreshToken.isNullOrBlank()) {
// 没有access_token但有refresh_token尝试刷新 // 有refresh_token尝试静默刷新
tryRefreshAndRestore(refreshToken) tryRefreshTokenSilently(refreshToken)
} else {
// 完全没有token
_isLoggedIn.value = false
} }
} }
} }
/** /**
* 尝试使用refresh_token刷新access_token并恢复登录 * 静默刷新token后台执行不影响登录状态
* 只在刷新成功时更新token失败时不清除现有token
* token失效的处理交给AuthInterceptor在实际API调用时处理
*/ */
private suspend fun tryRefreshAndRestore(refreshToken: String) { private suspend fun tryRefreshTokenSilently(refreshToken: String) {
val refreshResult = authService.refreshToken(refreshToken) try {
refreshResult.fold( val refreshResult = authService.refreshToken(refreshToken)
onSuccess = { tokenResponse -> refreshResult.fold(
// 刷新成功保存新token onSuccess = { tokenResponse ->
val userId = TokenManager.getUserId() ?: "" // 刷新成功保存新token
TokenManager.saveTokens( val userId = TokenManager.getUserId() ?: ""
tokenResponse.access_token, TokenManager.saveTokens(
tokenResponse.refresh_token, tokenResponse.access_token,
userId tokenResponse.refresh_token,
) userId
// 用新token获取用户信息 )
val success = loadUserInfo(tokenResponse.access_token) // 用新token获取用户信息
_isLoggedIn.value = true loadUserInfo(tokenResponse.access_token)
}, },
onFailure = { onFailure = {
// 刷新失败了,需要重新登录 // 静默刷新失败不清除token
TokenManager.clearTokens() // 后续API调用时AuthInterceptor会再次尝试刷新
_isLoggedIn.value = false }
} )
) } catch (_: Exception) {
// 网络异常等,忽略,保持现有登录状态
}
} }
/** /**