auth to server-V91
This commit is contained in:
parent
321be95ff0
commit
fae89ee041
2 changed files with 200 additions and 9 deletions
|
|
@ -0,0 +1,178 @@
|
||||||
|
package com.example.login_v3.auth.SwitchAccount
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.foundation.lazy.items // 確保匯入這個
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.ExitToApp
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.example.login_v3.auth.login.AuthViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AccountSwitchScreen(
|
||||||
|
viewModel: AuthViewModel = hiltViewModel<AuthViewModel>(),
|
||||||
|
onAddAccountClick: () -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
// 觀察 ViewModel 中的狀態
|
||||||
|
val allUsers by viewModel.allUserIds.collectAsState()
|
||||||
|
val currentUserId by viewModel.currentUserId.collectAsState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text("切換帳號") },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.Default.ArrowBack, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "已登入的帳號",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 帳號清單
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
// 這裡加上明確的 items(items = ...)
|
||||||
|
items(items = allUsers.toList()) { userId ->
|
||||||
|
val isCurrent = (userId == currentUserId) // 這裡解決 == 類型問題
|
||||||
|
|
||||||
|
AccountItem(
|
||||||
|
userId = userId,
|
||||||
|
isCurrent = isCurrent,
|
||||||
|
onSelect = { viewModel.switchAccount(userId) },
|
||||||
|
onLogout = { viewModel.logout(userId) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 新增帳號按鈕
|
||||||
|
Button(
|
||||||
|
onClick = onAddAccountClick,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("登入另一個帳號")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccountItem(
|
||||||
|
userId: String,
|
||||||
|
isCurrent: Boolean,
|
||||||
|
onSelect: () -> Unit,
|
||||||
|
onLogout: () -> Unit
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onSelect() },
|
||||||
|
border = if (isCurrent) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null,
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = if (isCurrent)
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
// 模擬頭像
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
modifier = Modifier.size(40.dp)
|
||||||
|
) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
Text(userId.take(1).uppercase(), color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(text = userId, style = MaterialTheme.typography.bodyLarge)
|
||||||
|
if (isCurrent) {
|
||||||
|
Text(
|
||||||
|
text = "使用中",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出小按鈕
|
||||||
|
IconButton(onClick = onLogout) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ExitToApp,
|
||||||
|
contentDescription = "登出",
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,10 +7,13 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.example.login_v3.data.repository.basic.TokenManager
|
import com.example.login_v3.data.repository.basic.TokenManager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -36,25 +39,30 @@ class AppViewModel @Inject constructor(
|
||||||
private val tokenManager: TokenManager
|
private val tokenManager: TokenManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _currentScreen = MutableStateFlow<AppScreen>(AppScreen.Loading) // 增加一個 Loading 狀態
|
private val _currentScreen = MutableStateFlow<AppScreen>(AppScreen.Loading)
|
||||||
val currentScreen: StateFlow<AppScreen> = _currentScreen.asStateFlow()
|
val currentScreen: StateFlow<AppScreen> = _currentScreen.asStateFlow()
|
||||||
|
|
||||||
// 暴露 Token 給其他需要的 Screen 使用
|
// 修改:監聽當前活躍帳號的 Token
|
||||||
val userToken: StateFlow<String?> = tokenManager.accessToken
|
val userToken: StateFlow<String?> = tokenManager.currentAccessToken
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
||||||
|
|
||||||
|
// 修改:監聽「是否至少有一個帳號登入中」
|
||||||
|
private val hasAccountLoggedIn: Flow<Boolean> = tokenManager.allUserIds.map { it.isNotEmpty() }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkLoginStatus()
|
checkLoginStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkLoginStatus() {
|
private fun checkLoginStatus() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// 讀取 DataStore 中的 isLogin 狀態
|
// 修改:根據「帳號清單是否為空」來決定初始頁面
|
||||||
tokenManager.isLogin.collect { loggedIn ->
|
hasAccountLoggedIn.collect { hasAccount ->
|
||||||
if (loggedIn) {
|
if (hasAccount) {
|
||||||
|
// 只要清單不為空,就進入主頁面
|
||||||
_currentScreen.value = AppScreen.ScreensTab
|
_currentScreen.value = AppScreen.ScreensTab
|
||||||
} else {
|
} else {
|
||||||
_currentScreen.value = AppScreen.PreReg // 或 AppScreen.Register
|
// 如果清單空了(所有人都登出了),回首頁/註冊頁
|
||||||
|
_currentScreen.value = AppScreen.PreReg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,10 +72,15 @@ class AppViewModel @Inject constructor(
|
||||||
_currentScreen.value = screen
|
_currentScreen.value = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改:登出當前帳號
|
||||||
fun onLogout() {
|
fun onLogout() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
tokenManager.clearAuthData()
|
// 先獲取目前的 ID
|
||||||
// 清除後,checkLoginStatus 的 collect 會自動觸發切換回 PreReg
|
val currentId = tokenManager.currentUserId.first()
|
||||||
|
currentId?.let {
|
||||||
|
tokenManager.logout(it)
|
||||||
|
}
|
||||||
|
// logout 完後,hasAccountLoggedIn 的 collect 會自動觸發頁面跳轉邏輯
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue