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
@Composable
fun LifeechoApp(initialLoggedIn: Boolean = false) {
var isLoggedIn by rememberSaveable { mutableStateOf(initialLoggedIn) }
// 使用 remember非 rememberSaveable避免进程恢复时使用过时的登录状态
// 登录状态始终以 TokenManager 为准
var isLoggedIn by remember { mutableStateOf(initialLoggedIn) }
val navController = rememberNavController()
var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.CHAT) }
@@ -151,20 +153,25 @@ fun LifeechoApp(initialLoggedIn: Boolean = false) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
// 同步登录状态变化,并在登录状态恢复时自动导航到主界面
// 实时同步 TokenManager 的登录状态,并处理导航
androidx.compose.runtime.LaunchedEffect(TokenManager.isLoggedIn) {
val newLoggedIn = TokenManager.isLoggedIn
val wasLoggedOut = !isLoggedIn
val previousLoggedIn = isLoggedIn
isLoggedIn = newLoggedIn
// 如果从未登录变为已登录,且当前在登录页,自动跳转到主界面
if (newLoggedIn && wasLoggedOut) {
val currentRoute = navController.currentDestination?.route
if (currentRoute == Screen.Login.route || currentRoute == null) {
if (newLoggedIn && !previousLoggedIn) {
// 从未登录变为已登录:跳转到主界面
val route = navController.currentDestination?.route
if (route == Screen.Login.route || route == Screen.NicknameSetup.route || route == null) {
navController.navigate(Screen.ConversationList.route) {
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 ->
// 兼容旧数据:将旧名称"回忆录助手"识别为默认助手
val isAssistant = conversation.title == null || conversation.title == "回忆录助手"
// 兼容旧数据:将"岁月知己"和旧名称"回忆录助手"识别为默认助手
val isAssistant = conversation.title == null || conversation.title == "岁月知己" || conversation.title == "回忆录助手"
val displayTitle = if (isAssistant) "岁月知己" else conversation.title!!
val dto = ConversationListItemDto(
id = conversation.id,

View File

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

View File

@@ -75,7 +75,7 @@ fun NicknameSetupScreen(
// 欢迎图标
Icon(
imageVector = AppIcons.PersonAdd,
contentDescription = "设置昵称",
contentDescription = "怎么称呼你呢?",
modifier = Modifier.size(80.dp),
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() {
viewModelScope.launch {
val accessToken = TokenManager.getAccessToken()
val refreshToken = TokenManager.getRefreshToken()
val hasToken = !accessToken.isNullOrBlank() || !refreshToken.isNullOrBlank()
// 只要有缓存的token就认为已登录
_isLoggedIn.value = hasToken
if (!hasToken) return@launch
// 后台尝试获取用户信息best effort不影响登录状态
if (!accessToken.isNullOrBlank()) {
// 有access_token尝试获取用户信息验证token有效性
val success = loadUserInfo(accessToken)
if (success) {
_isLoggedIn.value = true
} else {
// access_token可能过期尝试用refresh_token刷新
if (!refreshToken.isNullOrBlank()) {
tryRefreshAndRestore(refreshToken)
} else {
// 没有refresh_token需要重新登录
TokenManager.clearTokens()
_isLoggedIn.value = false
}
if (!success && !refreshToken.isNullOrBlank()) {
// access_token可能过期尝试用refresh_token静默刷新
tryRefreshTokenSilently(refreshToken)
}
} else if (!refreshToken.isNullOrBlank()) {
// 没有access_token但有refresh_token尝试刷新
tryRefreshAndRestore(refreshToken)
} else {
// 完全没有token
_isLoggedIn.value = false
// 有refresh_token尝试静默刷新
tryRefreshTokenSilently(refreshToken)
}
}
}
/**
* 尝试使用refresh_token刷新access_token并恢复登录
* 静默刷新token后台执行不影响登录状态
* 只在刷新成功时更新token失败时不清除现有token
* token失效的处理交给AuthInterceptor在实际API调用时处理
*/
private suspend fun tryRefreshAndRestore(refreshToken: String) {
val refreshResult = authService.refreshToken(refreshToken)
refreshResult.fold(
onSuccess = { tokenResponse ->
// 刷新成功保存新token
val userId = TokenManager.getUserId() ?: ""
TokenManager.saveTokens(
tokenResponse.access_token,
tokenResponse.refresh_token,
userId
)
// 用新token获取用户信息
val success = loadUserInfo(tokenResponse.access_token)
_isLoggedIn.value = true
},
onFailure = {
// 刷新失败了,需要重新登录
TokenManager.clearTokens()
_isLoggedIn.value = false
}
)
private suspend fun tryRefreshTokenSilently(refreshToken: String) {
try {
val refreshResult = authService.refreshToken(refreshToken)
refreshResult.fold(
onSuccess = { tokenResponse ->
// 刷新成功保存新token
val userId = TokenManager.getUserId() ?: ""
TokenManager.saveTokens(
tokenResponse.access_token,
tokenResponse.refresh_token,
userId
)
// 用新token获取用户信息
loadUserInfo(tokenResponse.access_token)
},
onFailure = {
// 静默刷新失败不清除token
// 后续API调用时AuthInterceptor会再次尝试刷新
}
)
} catch (_: Exception) {
// 网络异常等,忽略,保持现有登录状态
}
}
/**