auth to server-V95
This commit is contained in:
parent
538c018470
commit
d9f5efc680
3 changed files with 278 additions and 2 deletions
|
|
@ -0,0 +1,271 @@
|
||||||
|
package com.example.login_v3.auth.SwitchAccount
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
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.PaddingValues
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.AccountCircle
|
||||||
|
import androidx.compose.material.icons.rounded.Lock
|
||||||
|
import androidx.compose.material.icons.rounded.Visibility
|
||||||
|
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.BlurredEdgeTreatment
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Color.Companion.Red
|
||||||
|
import androidx.compose.ui.graphics.Color.Companion.Transparent
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.decode.SvgDecoder
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.example.login_v3.R
|
||||||
|
import com.example.login_v3.auth.login.AuthViewModel
|
||||||
|
import com.example.login_v3.auth.login.LoginUiState
|
||||||
|
import com.example.login_v3.ui.theme.light_orange
|
||||||
|
import com.example.login_v3.ui.theme.main_orange
|
||||||
|
import kotlin.text.ifEmpty
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AddAccountScreen(
|
||||||
|
paddingValues: PaddingValues,
|
||||||
|
viewModel: AuthViewModel = hiltViewModel()
|
||||||
|
){
|
||||||
|
|
||||||
|
val uiState by viewModel.loginState.collectAsStateWithLifecycle()
|
||||||
|
val isLoading = uiState is LoginUiState.Loading
|
||||||
|
var username by remember { mutableStateOf("") }
|
||||||
|
var password by remember { mutableStateOf("") }
|
||||||
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var usernameError by remember { mutableStateOf("") }
|
||||||
|
var passwordError by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//background
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFFDA7029),
|
||||||
|
Color(0xFF777777),
|
||||||
|
Color(0xFFB34800)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.padding(paddingValues),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
//title
|
||||||
|
Text(text = "Add Account",
|
||||||
|
fontSize = 32.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
//login Box
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(340.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 24.dp, end = 24.dp, top = 20.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
){
|
||||||
|
|
||||||
|
|
||||||
|
//glass effect
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.clip(RoundedCornerShape(16.dp)) // 🔥 一定要有
|
||||||
|
.blur(
|
||||||
|
radius = 10.dp,
|
||||||
|
edgeTreatment = BlurredEdgeTreatment.Unbounded
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
Color.White.copy(alpha = 0.18f)
|
||||||
|
)
|
||||||
|
.border( // ⭐ 玻璃邊框
|
||||||
|
width = 1.dp,
|
||||||
|
color = Color.White.copy(alpha = 0.35f),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = username,
|
||||||
|
onValueChange = { username = it},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
usernameError.ifEmpty { "username" },
|
||||||
|
color = if (usernameError.isNotEmpty()) Red else Color.Unspecified
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.AccountCircle,
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||||
|
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Transparent,
|
||||||
|
unfocusedIndicatorColor = Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
passwordError.ifEmpty { "Password" },
|
||||||
|
color = if (passwordError.isNotEmpty()) Red else Color.Unspecified
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.Lock, contentDescription = "")
|
||||||
|
},
|
||||||
|
|
||||||
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
|
||||||
|
trailingIcon = {
|
||||||
|
val image = if (passwordVisible) {
|
||||||
|
Icons.Rounded.Visibility
|
||||||
|
} else {
|
||||||
|
Icons.Rounded.VisibilityOff
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||||
|
Icon(imageVector = image, contentDescription = if (passwordVisible) "Hide password" else "Show password")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||||
|
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Transparent,
|
||||||
|
unfocusedIndicatorColor = Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
|
||||||
|
when (uiState) {
|
||||||
|
is LoginUiState.Success -> {
|
||||||
|
val user = (uiState as LoginUiState.Success).user
|
||||||
|
Text("歡迎,${user.display_name}")
|
||||||
|
}
|
||||||
|
is LoginUiState.Error -> {
|
||||||
|
Text(text = (uiState as LoginUiState.Error).message, color = Color.Red)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(color = Color.White)
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
onClick = {
|
||||||
|
usernameError = if(username.isBlank()) "Email is required" else ""
|
||||||
|
passwordError = if(password.isBlank()) "password is required" else ""
|
||||||
|
if (usernameError.isEmpty() && passwordError.isEmpty()){
|
||||||
|
viewModel.login(username, password)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = main_orange, // 背景顏色
|
||||||
|
contentColor = Color.White // 文字顏色
|
||||||
|
)
|
||||||
|
|
||||||
|
) {
|
||||||
|
Text( text = "Login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text( text = "forget password?",
|
||||||
|
color = light_orange,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
//init navigation
|
//init navigation
|
||||||
enum class AppScreen {
|
enum class AppScreen {
|
||||||
PreReg, Login, Register, Home, ScreensTab, Loading, AccountSwitch
|
PreReg, Login, Register, Home, ScreensTab, Loading, AccountSwitch, AddAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.login_v3.auth.login.LoginScreen
|
import com.example.login_v3.auth.login.LoginScreen
|
||||||
import com.example.login_v3.auth.Login_or_Reg_page
|
import com.example.login_v3.auth.Login_or_Reg_page
|
||||||
import com.example.login_v3.auth.SwitchAccount.AccountSwitchScreen
|
import com.example.login_v3.auth.SwitchAccount.AccountSwitchScreen
|
||||||
|
import com.example.login_v3.auth.SwitchAccount.AddAccountScreen
|
||||||
import com.example.login_v3.auth.reg.RegisterScreen
|
import com.example.login_v3.auth.reg.RegisterScreen
|
||||||
import com.example.login_v3.auth.reg.Register_Screen
|
import com.example.login_v3.auth.reg.Register_Screen
|
||||||
import com.example.login_v3.home.HomeScreen
|
import com.example.login_v3.home.HomeScreen
|
||||||
|
|
@ -45,6 +46,10 @@ fun AppNavGraph(
|
||||||
paddingValues = paddingValues
|
paddingValues = paddingValues
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AppScreen.AddAccount -> AddAccountScreen(
|
||||||
|
paddingValues = paddingValues
|
||||||
|
)
|
||||||
|
|
||||||
AppScreen.Register -> {
|
AppScreen.Register -> {
|
||||||
//back to pre_reg
|
//back to pre_reg
|
||||||
BackHandler {
|
BackHandler {
|
||||||
|
|
@ -69,7 +74,7 @@ fun AppNavGraph(
|
||||||
AppScreen.AccountSwitch -> {
|
AppScreen.AccountSwitch -> {
|
||||||
AccountSwitchScreen(
|
AccountSwitchScreen(
|
||||||
// 點擊「登入另一個帳號」就跳轉到 Login 頁面
|
// 點擊「登入另一個帳號」就跳轉到 Login 頁面
|
||||||
onAddAccountClick = { appViewModel.goTo(AppScreen.Login) },
|
onAddAccountClick = { appViewModel.goTo(AppScreen.AddAccount) },
|
||||||
// 點擊返回則回到原本的主分頁 (ScreensTab)
|
// 點擊返回則回到原本的主分頁 (ScreensTab)
|
||||||
onBack = { appViewModel.goTo(AppScreen.ScreensTab) }
|
onBack = { appViewModel.goTo(AppScreen.ScreensTab) }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue