From fae89ee04186ba7195db350c1928f3a3ace6d241 Mon Sep 17 00:00:00 2001 From: Eric85710 Date: Tue, 12 May 2026 16:15:35 +0800 Subject: [PATCH] auth to server-V91 --- .../auth/SwitchAccount/Switch_Account_Page.kt | 178 ++++++++++++++++++ .../login_v3/navigation/MainViewModel.kt | 31 ++- 2 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/example/login_v3/auth/SwitchAccount/Switch_Account_Page.kt diff --git a/app/src/main/java/com/example/login_v3/auth/SwitchAccount/Switch_Account_Page.kt b/app/src/main/java/com/example/login_v3/auth/SwitchAccount/Switch_Account_Page.kt new file mode 100644 index 0000000..27c9700 --- /dev/null +++ b/app/src/main/java/com/example/login_v3/auth/SwitchAccount/Switch_Account_Page.kt @@ -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(), + 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 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/navigation/MainViewModel.kt b/app/src/main/java/com/example/login_v3/navigation/MainViewModel.kt index b6eea7d..3b4dd6b 100644 --- a/app/src/main/java/com/example/login_v3/navigation/MainViewModel.kt +++ b/app/src/main/java/com/example/login_v3/navigation/MainViewModel.kt @@ -7,10 +7,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.login_v3.data.repository.basic.TokenManager import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -36,25 +39,30 @@ class AppViewModel @Inject constructor( private val tokenManager: TokenManager ) : ViewModel() { - private val _currentScreen = MutableStateFlow(AppScreen.Loading) // 增加一個 Loading 狀態 + private val _currentScreen = MutableStateFlow(AppScreen.Loading) val currentScreen: StateFlow = _currentScreen.asStateFlow() - // 暴露 Token 給其他需要的 Screen 使用 - val userToken: StateFlow = tokenManager.accessToken + // 修改:監聽當前活躍帳號的 Token + val userToken: StateFlow = tokenManager.currentAccessToken .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null) + // 修改:監聽「是否至少有一個帳號登入中」 + private val hasAccountLoggedIn: Flow = tokenManager.allUserIds.map { it.isNotEmpty() } + init { checkLoginStatus() } private fun checkLoginStatus() { viewModelScope.launch { - // 讀取 DataStore 中的 isLogin 狀態 - tokenManager.isLogin.collect { loggedIn -> - if (loggedIn) { + // 修改:根據「帳號清單是否為空」來決定初始頁面 + hasAccountLoggedIn.collect { hasAccount -> + if (hasAccount) { + // 只要清單不為空,就進入主頁面 _currentScreen.value = AppScreen.ScreensTab } else { - _currentScreen.value = AppScreen.PreReg // 或 AppScreen.Register + // 如果清單空了(所有人都登出了),回首頁/註冊頁 + _currentScreen.value = AppScreen.PreReg } } } @@ -64,10 +72,15 @@ class AppViewModel @Inject constructor( _currentScreen.value = screen } + // 修改:登出當前帳號 fun onLogout() { viewModelScope.launch { - tokenManager.clearAuthData() - // 清除後,checkLoginStatus 的 collect 會自動觸發切換回 PreReg + // 先獲取目前的 ID + val currentId = tokenManager.currentUserId.first() + currentId?.let { + tokenManager.logout(it) + } + // logout 完後,hasAccountLoggedIn 的 collect 會自動觸發頁面跳轉邏輯 } } }