diff --git a/app/src/main/java/com/mingyuan/android_client/AppNavHost.kt b/app/src/main/java/com/mingyuan/android_client/AppNavHost.kt index 864a9c8..81ea34b 100644 --- a/app/src/main/java/com/mingyuan/android_client/AppNavHost.kt +++ b/app/src/main/java/com/mingyuan/android_client/AppNavHost.kt @@ -96,7 +96,9 @@ fun AppNavHost() { ) { PlanSubmitScreen(onBack = { navController.popBackStack() }) } - composable(PROFILE_ROUTE) { ProfileScreen() } + composable(PROFILE_ROUTE) { + ProfileScreen(onLoggedOut = { navigateToTab(HOME_ROUTE) }) + } } } } diff --git a/core/data/src/main/kotlin/com/mingyuan/android_client/core/data/repository/FakeProfileRepository.kt b/core/data/src/main/kotlin/com/mingyuan/android_client/core/data/repository/FakeProfileRepository.kt index 242d24a..21b6218 100644 --- a/core/data/src/main/kotlin/com/mingyuan/android_client/core/data/repository/FakeProfileRepository.kt +++ b/core/data/src/main/kotlin/com/mingyuan/android_client/core/data/repository/FakeProfileRepository.kt @@ -21,19 +21,6 @@ class FakeProfileRepository @Inject constructor() : ProfileRepository { 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( displayName = "张三", name = "张三", diff --git a/core/domain/src/main/kotlin/com/mingyuan/android_client/core/domain/repository/ProfileRepository.kt b/core/domain/src/main/kotlin/com/mingyuan/android_client/core/domain/repository/ProfileRepository.kt index d31427a..e2a5e5f 100644 --- a/core/domain/src/main/kotlin/com/mingyuan/android_client/core/domain/repository/ProfileRepository.kt +++ b/core/domain/src/main/kotlin/com/mingyuan/android_client/core/domain/repository/ProfileRepository.kt @@ -5,6 +5,4 @@ import kotlinx.coroutines.flow.Flow interface ProfileRepository { fun observeProfile(): Flow - - suspend fun deleteAccount() } diff --git a/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileScreen.kt b/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileScreen.kt index 3a9bb87..8a1fc6f 100644 --- a/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileScreen.kt +++ b/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.ErrorState 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.SectionCard import com.mingyuan.android_client.core.designsystem.component.SkeletonBox @@ -53,14 +55,20 @@ const val PROFILE_ROUTE = "profile" @Composable fun ProfileScreen( + onLoggedOut: () -> Unit, modifier: Modifier = Modifier, viewModel: ProfileViewModel = hiltViewModel(), ) { 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) { is ProfileUiState.Loading -> ProfileSkeleton(modifier) + is ProfileUiState.LoggedOut -> LoggedOutState(onLogin = viewModel::login, modifier = modifier) is ProfileUiState.Error -> ErrorState( message = state.message, onRetry = {}, @@ -69,28 +77,28 @@ fun ProfileScreen( is ProfileUiState.Success -> ProfileContent( profile = state.profile, - onDelete = { showDeleteDialog = true }, + onLogout = { showLogoutDialog = true }, modifier = modifier, ) } - if (showDeleteDialog) { + if (showLogoutDialog) { AlertDialog( - onDismissRequest = { showDeleteDialog = false }, - title = { Text("注销账号") }, - text = { Text("确定要注销账号吗?此操作不可恢复。") }, + onDismissRequest = { showLogoutDialog = false }, + title = { Text("退出登录") }, + text = { Text("确定要退出登录吗?退出后需要重新登录。") }, confirmButton = { TextButton( onClick = { - showDeleteDialog = false - viewModel.deleteAccount() + showLogoutDialog = false + viewModel.logout() }, ) { Text("确定", color = Danger) } }, dismissButton = { - TextButton(onClick = { showDeleteDialog = false }) { Text("取消") } + TextButton(onClick = { showLogoutDialog = false }) { Text("取消") } }, ) } @@ -99,7 +107,7 @@ fun ProfileScreen( @Composable private fun ProfileContent( profile: UserProfile, - onDelete: () -> Unit, + onLogout: () -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( @@ -114,8 +122,8 @@ private fun ProfileContent( } item { MingyuanButton( - text = "注销账号", - onClick = onDelete, + text = "退出登录", + onClick = onLogout, variant = ButtonVariant.DANGER, fullWidth = true, ) diff --git a/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileViewModel.kt b/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileViewModel.kt index f226b3a..87c299c 100644 --- a/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileViewModel.kt +++ b/feature/profile/src/main/kotlin/com/mingyuan/android_client/feature/profile/ProfileViewModel.kt @@ -3,11 +3,16 @@ package com.mingyuan.android_client.feature.profile import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope 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 dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -17,27 +22,46 @@ import javax.inject.Inject sealed interface ProfileUiState { data object Loading : ProfileUiState data class Success(val profile: UserProfile) : ProfileUiState + data object LoggedOut : ProfileUiState data class Error(val message: String) : ProfileUiState } @HiltViewModel class ProfileViewModel @Inject constructor( private val profileRepository: ProfileRepository, + private val sessionRepository: SessionRepository, ) : ViewModel() { - val uiState: StateFlow = profileRepository.observeProfile() - .map(ProfileUiState::Success) - .onStart { emit(ProfileUiState.Loading) } + val uiState: StateFlow = sessionRepository.isLoggedIn + .flatMapLatest { loggedIn -> + if (loggedIn) { + profileRepository.observeProfile() + .map(ProfileUiState::Success) + .onStart { emit(ProfileUiState.Loading) } + } else { + flowOf(ProfileUiState.LoggedOut) + } + } .catch { emit(ProfileUiState.Error(it.message ?: "加载失败,请稍后再试")) } .stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), + started = SharingStarted.Eagerly, initialValue = ProfileUiState.Loading, ) - fun deleteAccount() { + private val _logoutEvents = MutableSharedFlow() + val logoutEvents: SharedFlow = _logoutEvents + + fun login() { viewModelScope.launch { - profileRepository.deleteAccount() + sessionRepository.login() + } + } + + fun logout() { + viewModelScope.launch { + sessionRepository.logout() + _logoutEvents.emit(Unit) } } }