feat: 「我的」页退出登录/登录接入会话状态

This commit is contained in:
AndyChen1024
2026-09-09 22:16:25 +08:00
parent c41c04ff23
commit 03410d7ea2
5 changed files with 53 additions and 34 deletions

View File

@@ -96,7 +96,9 @@ fun AppNavHost() {
) { ) {
PlanSubmitScreen(onBack = { navController.popBackStack() }) PlanSubmitScreen(onBack = { navController.popBackStack() })
} }
composable(PROFILE_ROUTE) { ProfileScreen() } composable(PROFILE_ROUTE) {
ProfileScreen(onLoggedOut = { navigateToTab(HOME_ROUTE) })
}
} }
} }
} }

View File

@@ -21,19 +21,6 @@ class FakeProfileRepository @Inject constructor() : ProfileRepository {
emitAll(profile) emitAll(profile)
} }
override suspend fun deleteAccount() {
delay(300)
profile.value = seedProfile().copy(
displayName = "游客",
name = "",
studentId = "",
phone = null,
group = null,
avatarUrl = null,
classGroups = emptyList(),
)
}
private fun seedProfile() = UserProfile( private fun seedProfile() = UserProfile(
displayName = "张三", displayName = "张三",
name = "张三", name = "张三",

View File

@@ -5,6 +5,4 @@ import kotlinx.coroutines.flow.Flow
interface ProfileRepository { interface ProfileRepository {
fun observeProfile(): Flow<UserProfile> fun observeProfile(): Flow<UserProfile>
suspend fun deleteAccount()
} }

View File

@@ -21,6 +21,7 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -36,6 +37,7 @@ import com.mingyuan.android_client.core.designsystem.component.Avatar
import com.mingyuan.android_client.core.designsystem.component.ButtonVariant import com.mingyuan.android_client.core.designsystem.component.ButtonVariant
import com.mingyuan.android_client.core.designsystem.component.ErrorState import com.mingyuan.android_client.core.designsystem.component.ErrorState
import com.mingyuan.android_client.core.designsystem.component.LabeledRow import com.mingyuan.android_client.core.designsystem.component.LabeledRow
import com.mingyuan.android_client.core.designsystem.component.LoggedOutState
import com.mingyuan.android_client.core.designsystem.component.MingyuanButton import com.mingyuan.android_client.core.designsystem.component.MingyuanButton
import com.mingyuan.android_client.core.designsystem.component.SectionCard import com.mingyuan.android_client.core.designsystem.component.SectionCard
import com.mingyuan.android_client.core.designsystem.component.SkeletonBox import com.mingyuan.android_client.core.designsystem.component.SkeletonBox
@@ -53,14 +55,20 @@ const val PROFILE_ROUTE = "profile"
@Composable @Composable
fun ProfileScreen( fun ProfileScreen(
onLoggedOut: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: ProfileViewModel = hiltViewModel(), viewModel: ProfileViewModel = hiltViewModel(),
) { ) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
var showDeleteDialog by remember { mutableStateOf(false) } var showLogoutDialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.logoutEvents.collect { onLoggedOut() }
}
when (val state = uiState) { when (val state = uiState) {
is ProfileUiState.Loading -> ProfileSkeleton(modifier) is ProfileUiState.Loading -> ProfileSkeleton(modifier)
is ProfileUiState.LoggedOut -> LoggedOutState(onLogin = viewModel::login, modifier = modifier)
is ProfileUiState.Error -> ErrorState( is ProfileUiState.Error -> ErrorState(
message = state.message, message = state.message,
onRetry = {}, onRetry = {},
@@ -69,28 +77,28 @@ fun ProfileScreen(
is ProfileUiState.Success -> ProfileContent( is ProfileUiState.Success -> ProfileContent(
profile = state.profile, profile = state.profile,
onDelete = { showDeleteDialog = true }, onLogout = { showLogoutDialog = true },
modifier = modifier, modifier = modifier,
) )
} }
if (showDeleteDialog) { if (showLogoutDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteDialog = false }, onDismissRequest = { showLogoutDialog = false },
title = { Text("注销账号") }, title = { Text("退出登录") },
text = { Text("确定要注销账号吗?此操作不可恢复。") }, text = { Text("确定要退出登录吗?退出后需要重新登录。") },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
showDeleteDialog = false showLogoutDialog = false
viewModel.deleteAccount() viewModel.logout()
}, },
) { ) {
Text("确定", color = Danger) Text("确定", color = Danger)
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteDialog = false }) { Text("取消") } TextButton(onClick = { showLogoutDialog = false }) { Text("取消") }
}, },
) )
} }
@@ -99,7 +107,7 @@ fun ProfileScreen(
@Composable @Composable
private fun ProfileContent( private fun ProfileContent(
profile: UserProfile, profile: UserProfile,
onDelete: () -> Unit, onLogout: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
LazyColumn( LazyColumn(
@@ -114,8 +122,8 @@ private fun ProfileContent(
} }
item { item {
MingyuanButton( MingyuanButton(
text = "注销账号", text = "退出登录",
onClick = onDelete, onClick = onLogout,
variant = ButtonVariant.DANGER, variant = ButtonVariant.DANGER,
fullWidth = true, fullWidth = true,
) )

View File

@@ -3,11 +3,16 @@ package com.mingyuan.android_client.feature.profile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mingyuan.android_client.core.domain.repository.ProfileRepository import com.mingyuan.android_client.core.domain.repository.ProfileRepository
import com.mingyuan.android_client.core.domain.repository.SessionRepository
import com.mingyuan.android_client.core.model.UserProfile import com.mingyuan.android_client.core.model.UserProfile
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@@ -17,27 +22,46 @@ import javax.inject.Inject
sealed interface ProfileUiState { sealed interface ProfileUiState {
data object Loading : ProfileUiState data object Loading : ProfileUiState
data class Success(val profile: UserProfile) : ProfileUiState data class Success(val profile: UserProfile) : ProfileUiState
data object LoggedOut : ProfileUiState
data class Error(val message: String) : ProfileUiState data class Error(val message: String) : ProfileUiState
} }
@HiltViewModel @HiltViewModel
class ProfileViewModel @Inject constructor( class ProfileViewModel @Inject constructor(
private val profileRepository: ProfileRepository, private val profileRepository: ProfileRepository,
private val sessionRepository: SessionRepository,
) : ViewModel() { ) : ViewModel() {
val uiState: StateFlow<ProfileUiState> = profileRepository.observeProfile() val uiState: StateFlow<ProfileUiState> = sessionRepository.isLoggedIn
.map<UserProfile, ProfileUiState>(ProfileUiState::Success) .flatMapLatest { loggedIn ->
.onStart { emit(ProfileUiState.Loading) } if (loggedIn) {
profileRepository.observeProfile()
.map<UserProfile, ProfileUiState>(ProfileUiState::Success)
.onStart { emit(ProfileUiState.Loading) }
} else {
flowOf(ProfileUiState.LoggedOut)
}
}
.catch { emit(ProfileUiState.Error(it.message ?: "加载失败,请稍后再试")) } .catch { emit(ProfileUiState.Error(it.message ?: "加载失败,请稍后再试")) }
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000), started = SharingStarted.Eagerly,
initialValue = ProfileUiState.Loading, initialValue = ProfileUiState.Loading,
) )
fun deleteAccount() { private val _logoutEvents = MutableSharedFlow<Unit>()
val logoutEvents: SharedFlow<Unit> = _logoutEvents
fun login() {
viewModelScope.launch { viewModelScope.launch {
profileRepository.deleteAccount() sessionRepository.login()
}
}
fun logout() {
viewModelScope.launch {
sessionRepository.logout()
_logoutEvents.emit(Unit)
} }
} }
} }