feat: 「我的」页退出登录/登录接入会话状态
This commit is contained in:
@@ -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) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = "张三",
|
||||||
|
|||||||
@@ -5,6 +5,4 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
interface ProfileRepository {
|
interface ProfileRepository {
|
||||||
fun observeProfile(): Flow<UserProfile>
|
fun observeProfile(): Flow<UserProfile>
|
||||||
|
|
||||||
suspend fun deleteAccount()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user