|
@@ -1,39 +1,89 @@
|
|
|
package com.zaojiao.app.core.auth.ui
|
|
|
|
|
|
+import android.os.Build
|
|
|
import android.os.Bundle
|
|
|
+import android.view.WindowManager
|
|
|
+import android.widget.Toast
|
|
|
import androidx.activity.ComponentActivity
|
|
|
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
|
|
+import androidx.activity.OnBackPressedCallback
|
|
|
import androidx.activity.compose.setContent
|
|
|
+import androidx.compose.foundation.Image
|
|
|
import androidx.compose.foundation.background
|
|
|
import androidx.compose.foundation.clickable
|
|
|
+import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
|
+import androidx.compose.foundation.layout.Arrangement
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
+import androidx.compose.foundation.layout.IntrinsicSize
|
|
|
+import androidx.compose.foundation.layout.Row
|
|
|
+import androidx.compose.foundation.layout.fillMaxHeight
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
+import androidx.compose.foundation.layout.height
|
|
|
+import androidx.compose.foundation.layout.navigationBarsPadding
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
+import androidx.compose.foundation.layout.size
|
|
|
import androidx.compose.foundation.layout.statusBarsPadding
|
|
|
-import androidx.compose.material.icons.Icons
|
|
|
-import androidx.compose.material.icons.filled.Close
|
|
|
-import androidx.compose.material3.Icon
|
|
|
+import androidx.compose.foundation.layout.width
|
|
|
+import androidx.compose.foundation.layout.wrapContentHeight
|
|
|
+import androidx.compose.foundation.layout.wrapContentSize
|
|
|
+import androidx.compose.foundation.layout.wrapContentWidth
|
|
|
+import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
+import androidx.compose.foundation.text.BasicTextField
|
|
|
+import androidx.compose.foundation.text.KeyboardOptions
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
+import androidx.compose.material3.Text
|
|
|
import androidx.compose.runtime.Composable
|
|
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
|
+import androidx.compose.runtime.collectAsState
|
|
|
import androidx.compose.runtime.compositionLocalOf
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
+import androidx.compose.runtime.remember
|
|
|
+import androidx.compose.runtime.rememberCoroutineScope
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
+import androidx.compose.ui.Alignment
|
|
|
import androidx.compose.ui.Modifier
|
|
|
+import androidx.compose.ui.draw.clip
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
+import androidx.compose.ui.res.painterResource
|
|
|
+import androidx.compose.ui.text.TextStyle
|
|
|
+import androidx.compose.ui.text.font.FontWeight
|
|
|
+import androidx.compose.ui.text.input.KeyboardType
|
|
|
+import androidx.compose.ui.unit.dp
|
|
|
+import androidx.compose.ui.unit.sp
|
|
|
+import androidx.core.view.WindowCompat
|
|
|
+import androidx.core.view.WindowInsetsCompat
|
|
|
+import androidx.core.view.WindowInsetsControllerCompat
|
|
|
import androidx.hilt.navigation.compose.hiltViewModel
|
|
|
import androidx.navigation.compose.NavHost
|
|
|
import androidx.navigation.compose.composable
|
|
|
import androidx.navigation.compose.rememberNavController
|
|
|
+import com.zaojiao.app.core.auth.R
|
|
|
import com.zaojiao.app.core.auth.model.TokenModel
|
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
|
+import kotlinx.coroutines.delay
|
|
|
+import kotlinx.coroutines.launch
|
|
|
|
|
|
private val LocalTokenSave = compositionLocalOf<((token: TokenModel?) -> Unit)?> { null }
|
|
|
|
|
|
+private val LocalFinish = compositionLocalOf<(() -> Unit)?> { null }
|
|
|
+
|
|
|
@AndroidEntryPoint
|
|
|
class LoginActivity : ComponentActivity() {
|
|
|
+ private lateinit var windowInsetsController: WindowInsetsControllerCompat
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
+ configureSystemBar()
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
+ this.onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
|
|
+ override fun handleOnBackPressed() {
|
|
|
+ TokenActivity.start(this@LoginActivity, null, null)
|
|
|
+ finish()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
setContent {
|
|
|
val navController = rememberNavController()
|
|
|
|
|
@@ -41,7 +91,11 @@ class LoginActivity : ComponentActivity() {
|
|
|
LocalTokenSave provides {
|
|
|
TokenActivity.start(this@LoginActivity, it?.accessToken, it?.refreshToken)
|
|
|
finish()
|
|
|
- }
|
|
|
+ },
|
|
|
+ LocalFinish provides {
|
|
|
+ TokenActivity.start(this@LoginActivity, null, null)
|
|
|
+ finish()
|
|
|
+ },
|
|
|
) {
|
|
|
MaterialTheme {
|
|
|
NavHost(
|
|
@@ -56,37 +110,278 @@ class LoginActivity : ComponentActivity() {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private fun configureSystemBar() {
|
|
|
+ WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
|
+ windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
|
|
+
|
|
|
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
|
|
+ window.statusBarColor = android.graphics.Color.TRANSPARENT
|
|
|
+ window.navigationBarColor = android.graphics.Color.TRANSPARENT
|
|
|
+
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
|
+ window.addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
|
|
|
+ }
|
|
|
+ windowInsetsController.apply {
|
|
|
+ show(WindowInsetsCompat.Type.systemBars())
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Composable
|
|
|
fun LoginPage(
|
|
|
loginViewModel: LoginViewModel = hiltViewModel(),
|
|
|
) {
|
|
|
+ val context = LocalContext.current
|
|
|
+ val callback = LocalTokenSave.current
|
|
|
+
|
|
|
+ val phone by loginViewModel.phone.collectAsState()
|
|
|
+ val smsCode by loginViewModel.smsCode.collectAsState()
|
|
|
+
|
|
|
Column(
|
|
|
modifier = Modifier
|
|
|
+ .background(color = Color.White)
|
|
|
.statusBarsPadding()
|
|
|
.fillMaxSize(),
|
|
|
) {
|
|
|
CloseButton()
|
|
|
|
|
|
- val callback = LocalTokenSave.current
|
|
|
|
|
|
- Box(
|
|
|
+ Column(
|
|
|
modifier = Modifier
|
|
|
- .clickable {
|
|
|
- callback?.invoke(null)
|
|
|
- }
|
|
|
- .background(color = Color.Green)
|
|
|
.weight(1f)
|
|
|
- .fillMaxWidth()
|
|
|
- )
|
|
|
+ .fillMaxWidth(),
|
|
|
+ verticalArrangement = Arrangement.Center,
|
|
|
+ horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
+ ) {
|
|
|
+ Text(
|
|
|
+ text = "手机登录/注册",
|
|
|
+ style = TextStyle(
|
|
|
+ color = Color(0xFF161616),
|
|
|
+ fontSize = 24.sp,
|
|
|
+ lineHeight = 24.sp,
|
|
|
+ ),
|
|
|
+ modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
|
+ )
|
|
|
+ Box(modifier = Modifier.height(40.dp))
|
|
|
+ Row(
|
|
|
+ modifier = Modifier
|
|
|
+ .background(
|
|
|
+ color = Color(0xFFF7F7F7),
|
|
|
+ shape = RoundedCornerShape(100.dp),
|
|
|
+ )
|
|
|
+ .width(284.dp)
|
|
|
+ .height(48.dp)
|
|
|
+ .padding(4.dp),
|
|
|
+ ) {
|
|
|
+ Box(modifier = Modifier.width(12.dp))
|
|
|
+ BasicTextField(
|
|
|
+ value = phone,
|
|
|
+ onValueChange = { value ->
|
|
|
+ loginViewModel.updatePhone(value)
|
|
|
+ },
|
|
|
+ keyboardOptions = KeyboardOptions(
|
|
|
+ keyboardType = KeyboardType.Number,
|
|
|
+ ),
|
|
|
+ textStyle = TextStyle(
|
|
|
+ color = Color(0xFF333333),
|
|
|
+ ),
|
|
|
+ singleLine = true,
|
|
|
+ modifier = Modifier
|
|
|
+ .weight(1f)
|
|
|
+ .align(Alignment.CenterVertically),
|
|
|
+ )
|
|
|
+ Box(modifier = Modifier.width(12.dp))
|
|
|
+
|
|
|
+ SendSmsCodeButton(
|
|
|
+ enabled = phone.length == 11,
|
|
|
+ onClick = {
|
|
|
+ loginViewModel.sendSmsCode(
|
|
|
+ onMessage = {
|
|
|
+ Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ }
|
|
|
+ Box(modifier = Modifier.height(12.dp))
|
|
|
+ Row(
|
|
|
+ modifier = Modifier
|
|
|
+ .background(
|
|
|
+ color = Color(0xFFF7F7F7),
|
|
|
+ shape = RoundedCornerShape(100.dp),
|
|
|
+ )
|
|
|
+ .width(284.dp)
|
|
|
+ .height(48.dp)
|
|
|
+ .padding(4.dp),
|
|
|
+ ) {
|
|
|
+ Box(modifier = Modifier.width(12.dp))
|
|
|
+ BasicTextField(
|
|
|
+ value = smsCode,
|
|
|
+ onValueChange = { value ->
|
|
|
+ loginViewModel.updateSmsCode(value)
|
|
|
+ },
|
|
|
+ keyboardOptions = KeyboardOptions(
|
|
|
+ keyboardType = KeyboardType.Number,
|
|
|
+ ),
|
|
|
+ textStyle = TextStyle(
|
|
|
+ color = Color(0xFF333333),
|
|
|
+ ),
|
|
|
+ singleLine = true,
|
|
|
+ modifier = Modifier
|
|
|
+ .weight(1f)
|
|
|
+ .align(Alignment.CenterVertically),
|
|
|
+ )
|
|
|
+ Box(modifier = Modifier.width(12.dp))
|
|
|
+ }
|
|
|
+ Box(modifier = Modifier.height(32.dp))
|
|
|
+
|
|
|
+ val enableButton = phone.length == 11 && smsCode.length == 4
|
|
|
+
|
|
|
+ Box(
|
|
|
+ modifier = Modifier
|
|
|
+ .clip(shape = RoundedCornerShape(100.dp))
|
|
|
+ .clickable(
|
|
|
+ enabled = enableButton
|
|
|
+ ) {
|
|
|
+ loginViewModel.requestLogin(
|
|
|
+ onSuccess = {
|
|
|
+ callback?.invoke(it)
|
|
|
+ },
|
|
|
+ onFailure = {
|
|
|
+ Toast
|
|
|
+ .makeText(context, it, Toast.LENGTH_SHORT)
|
|
|
+ .show()
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+ .background(
|
|
|
+ color = if (enableButton) Color(0xFF0548BB) else Color(0xFFB7CAEC)
|
|
|
+ )
|
|
|
+ .height(42.dp)
|
|
|
+ .width(284.dp),
|
|
|
+ ) {
|
|
|
+ Text(
|
|
|
+ text = "登录", style = TextStyle(
|
|
|
+ color = Color.White,
|
|
|
+ fontSize = 16.sp,
|
|
|
+ lineHeight = 18.sp,
|
|
|
+ fontWeight = FontWeight.Medium,
|
|
|
+ ), modifier = Modifier.align(Alignment.Center)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Column(
|
|
|
+ modifier = Modifier
|
|
|
+ .align(Alignment.CenterHorizontally)
|
|
|
+ .navigationBarsPadding()
|
|
|
+ .padding(bottom = 16.dp)
|
|
|
+ .wrapContentHeight(),
|
|
|
+ horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
+ ) {
|
|
|
+ Row(
|
|
|
+ modifier = Modifier
|
|
|
+ .wrapContentWidth()
|
|
|
+ .height(IntrinsicSize.Min),
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
+ ) {
|
|
|
+ Box(
|
|
|
+ modifier = Modifier
|
|
|
+ .background(color = Color(0xFFF5F5F5))
|
|
|
+ .size(40.dp, 2.dp),
|
|
|
+ )
|
|
|
+
|
|
|
+ Text(
|
|
|
+ text = "其他登录方式",
|
|
|
+ style = TextStyle(
|
|
|
+ color = Color(0xFFBFBFBF),
|
|
|
+ fontSize = 14.sp,
|
|
|
+ lineHeight = 14.sp,
|
|
|
+ ),
|
|
|
+ modifier = Modifier.padding(horizontal = 8.dp),
|
|
|
+ )
|
|
|
+
|
|
|
+ Box(
|
|
|
+ modifier = Modifier
|
|
|
+ .background(color = Color(0xFFF5F5F5))
|
|
|
+ .size(40.dp, 2.dp),
|
|
|
+ )
|
|
|
+ }
|
|
|
+ Box(modifier = Modifier.height(30.dp))
|
|
|
+ Image(
|
|
|
+ painter = painterResource(id = R.mipmap.login_wechat),
|
|
|
+ contentDescription = "",
|
|
|
+ modifier = Modifier.size(62.dp)
|
|
|
+ )
|
|
|
+ Box(modifier = Modifier.height(40.dp))
|
|
|
+ Text(
|
|
|
+ text = "我已阅读并接受《使用协议》和《隐私协议》",
|
|
|
+ style = TextStyle(
|
|
|
+ color = Color(0xFF999999),
|
|
|
+ fontSize = 12.sp,
|
|
|
+ lineHeight = 22.sp,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Composable
|
|
|
fun CloseButton() {
|
|
|
- Icon(
|
|
|
- imageVector = Icons.Filled.Close,
|
|
|
- contentDescription = "关闭按钮",
|
|
|
- )
|
|
|
+ val finish = LocalFinish.current
|
|
|
+
|
|
|
+ Text(text = "取消", style = TextStyle(
|
|
|
+ color = Color(0xFF0548BB),
|
|
|
+ fontSize = 14.sp,
|
|
|
+ lineHeight = 20.sp,
|
|
|
+ ), modifier = Modifier
|
|
|
+ .clickable(
|
|
|
+ indication = null,
|
|
|
+ interactionSource = MutableInteractionSource(),
|
|
|
+ ) {
|
|
|
+ finish?.invoke()
|
|
|
+ }
|
|
|
+ .padding(horizontal = 16.dp, vertical = 16.dp))
|
|
|
+}
|
|
|
+
|
|
|
+@Composable
|
|
|
+private fun SendSmsCodeButton(
|
|
|
+ enabled: Boolean,
|
|
|
+ onClick: () -> Unit,
|
|
|
+) {
|
|
|
+ val coroutineScope = rememberCoroutineScope()
|
|
|
+ var clickState by remember { mutableStateOf(true) }
|
|
|
+ var countDown by remember { mutableStateOf(0) }
|
|
|
+
|
|
|
+ Box(
|
|
|
+ modifier = Modifier
|
|
|
+ .clip(shape = RoundedCornerShape(100.dp))
|
|
|
+ .clickable(enabled = enabled && clickState) {
|
|
|
+ coroutineScope.launch {
|
|
|
+ clickState = false
|
|
|
+ repeat(60) {
|
|
|
+ countDown = 60 - it
|
|
|
+ delay(1000)
|
|
|
+ }
|
|
|
+ clickState = true
|
|
|
+ }
|
|
|
+ onClick.invoke()
|
|
|
+ }
|
|
|
+ .background(color = if (enabled && clickState) Color(0xFF0548BB) else Color(0xFFB7CAEC))
|
|
|
+ .width(110.dp)
|
|
|
+ .fillMaxHeight(),
|
|
|
+ ) {
|
|
|
+ Text(
|
|
|
+ text = if (clickState) "获取验证码" else "${countDown}秒后重发",
|
|
|
+ style = TextStyle(
|
|
|
+ color = Color.White,
|
|
|
+ fontSize = 16.sp,
|
|
|
+ lineHeight = 16.sp,
|
|
|
+ ),
|
|
|
+ modifier = Modifier
|
|
|
+ .align(Alignment.Center)
|
|
|
+ .wrapContentSize(),
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|