rabbit-android/app/src/main/java/com/img/rabbit/pages/LoginPage.kt

1265 lines
49 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@file:Suppress("SameParameterValue")
package com.img.rabbit.pages
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.widget.CheckBox
import android.widget.TextView
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.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.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.img.rabbit.R
import com.img.rabbit.utils.AgreementTextHelper
import com.img.rabbit.viewmodel.GeneralViewModel
import com.img.rabbit.viewmodel.LoginViewModel
import com.g.gysdk.EloginActivityParam
import com.g.gysdk.GYManager
import com.g.gysdk.GYResponse
import com.g.gysdk.GyCallBack
import com.img.rabbit.components.CenterToast
import com.img.rabbit.config.Constants.agreementUrl
import com.img.rabbit.config.Constants.privacyUrl
import com.img.rabbit.pages.toolbar.TitleBar
import com.img.rabbit.provider.storage.PreferenceUtil
import com.img.rabbit.utils.AppEventBus
import com.img.rabbit.utils.LoginBindEvent
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
import com.img.rabbit.utils.WXAuthEvent
import kotlinx.coroutines.delay
import org.json.JSONObject
/**
* 登录页
* 包含两种打开方式1、直接打开登录页无返回按钮 2、从其他页面跳转打开登录页包含返回按钮
*/
@SuppressLint("UnrememberedMutableState")
@Composable
fun LoginScreen(navController: NavHostController? = null, generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel, isVisibilityBreak: Boolean) {
val TAG = "Rabbit_LoginScreen"
val context = LocalContext.current
//关于登录的事件监听
LaunchedEffect(Unit) {
AppEventBus.events.collect { event ->
when (event) {
//微信授权结果authType=0 ---->拿着Code登录
is WXAuthEvent.AuthResult -> {
if(loginViewModel.isLoginWxAuthor){
loginViewModel.requestWxLogin(event.code)
loginViewModel.isLoginWxAuthor = false
}
}
}
}
}
// 登录成功后,保存 token
LaunchedEffect(loginViewModel.loginResult.value) {
if (loginViewModel.loginResult.value?.data?.token?.isNotEmpty() == true) {
val loginInfo = loginViewModel.loginResult.value?.data
AppEventBus.post( LoginBindEvent.Login(userId = loginInfo?.user_id?:"", loginType = PreferenceUtil.getLoginType(), isLogin = true, data = loginInfo) )
CenterToast.show("登录成功")
if(isVisibilityBreak){
navController?.popBackStack() // 当允许返回上一页时,登录成功后,返回上一页
}
loginViewModel.loginResult.value = null //清理loginState
}else if(loginViewModel.loginResult.value != null && loginViewModel.loginResult.value?.data?.token == null){
Log.w(TAG,"登录失败,无有效的Token")
CenterToast.show("登录失败,请重新登录")
}
}
LaunchedEffect(loginViewModel.errorState.value) {
if(loginViewModel.errorState.value != null){
Log.w(TAG,loginViewModel.errorState.value?.message?:"登录失败")
CenterToast.show("登录失败")
}
loginViewModel.errorState.value = null
}
Scaffold{
Box(
modifier = Modifier.fillMaxSize().navigationBarsPadding()
) {
Image(
painter = painterResource(id = R.mipmap.ic_main_previous_mask),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
)
Column(
modifier = Modifier
.fillMaxSize()
) {
Box(modifier = Modifier.fillMaxSize()){
when(loginViewModel.loginScreenType.value){
LoginScreenType.LOGIN_ONE_KEY -> {
//一键登录
OneKeyLoginScreen(context, loginViewModel)
}
LoginScreenType.LOGIN_WX -> {
//微信登录
Box(modifier = Modifier.align(Alignment.Center).padding(bottom = 100.dp)){
WxLoginScreen(context, loginViewModel, generalViewModel)
}
}
LoginScreenType.LOGIN_ALIPAY -> {
//支付宝登录
Box(modifier = Modifier.align(Alignment.Center).padding(bottom = 100.dp)){
AliPayLoginScreen(context, loginViewModel)
}
}
else -> {
//默认验证码登录
CaptchaLoginScreen(context, loginViewModel)
}
}
// 其他登录方式Bar
Column (
modifier = Modifier
.fillMaxSize()
.padding(top = 27.dp),
verticalArrangement = Arrangement.Bottom
){
OtherLoginBar(viewModel = loginViewModel)
}
}
}
/*
LaunchedEffect(networkStatus) {
delay(1000L)
showNetworkDisconnected = true
}
if(showNetworkDisconnected){
if(!networkStatus){
NetworkDisconnectedPage(generalViewModel = generalViewModel, onNetworkStatus = {isNetworkAvailable->
if(isNetworkAvailable){
Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
}
generalViewModel.setNetworkStatus(isNetworkAvailable)
})
}
}
*/
// 顶部栏
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false, showBreak = isVisibilityBreak)
}
}
}
/**
* 验证码登录
*/
@Composable
private fun CaptchaLoginScreen(context: Context, viewModel: LoginViewModel) {
val gradientBrush = Brush.verticalGradient(
colors = listOf(
Color(0xFF91FEFA), // 浅蓝色
Color(0x0091FEFA) // 半透明
),
startY = 0f,
endY = Float.POSITIVE_INFINITY
)
// 验证码倒计时
var isCaptchaCountdown by remember { mutableStateOf(false) }
// 倒计时秒数
var captchaCountdown by remember { mutableIntStateOf(60) }
if (isCaptchaCountdown) {
LaunchedEffect(key1 = isCaptchaCountdown) {
while (captchaCountdown > 0) {
delay(1000) // 等待1秒
captchaCountdown--
}
isCaptchaCountdown = false
captchaCountdown = 60 // 重置倒计时
}
}
Column {
//登录标签
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 38.dp, end = 38.dp, top = 150.dp)
) {
Box(
modifier = Modifier
.width(4.dp)
.height(55.dp)
.background(gradientBrush)
)
Column {
Text(
text = "欢迎登录",
modifier = Modifier.padding(start = 12.dp),
fontSize =18.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF000000)
)
Text(
text = stringResource(R.string.app_name),
modifier = Modifier.padding(start = 12.dp),
fontSize =32.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF000000)
)
}
}
//登录框
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 38.dp, end = 38.dp, top = 80.dp)
) {
// 用户名框(手机号)
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(
Color(0x4DE3E0ED),
shape = RoundedCornerShape(12.dp),
)
){
Row(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Text(
text = "+86",
modifier = Modifier
.padding(start = 14.dp, end = 12.dp)
.align(Alignment.CenterVertically),
fontSize =14.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF3D3D3D)
)
Image(
painter = painterResource(id = R.mipmap.ic_vertical_divider_dotted_line),
contentDescription = null,
modifier = Modifier
.height(29.dp)
.width(1.dp)
.align(Alignment.CenterVertically)
)
BasicTextField(
value = viewModel.userName.value,
onValueChange = { viewModel.setUserName(it) },
modifier = Modifier
.fillMaxWidth()
.background(Color.Transparent)
.padding(horizontal = 12.dp)
.align(Alignment.CenterVertically),
textStyle = androidx.compose.ui.text.TextStyle(
color = Color.Black,
fontSize = 16.sp
),
singleLine = true,
maxLines = 1,
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Phone,
imeAction = androidx.compose.ui.text.input.ImeAction.Done
),
decorationBox = { innerTextField ->
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Transparent)
) {
if (viewModel.userName.value.isEmpty()) {
Text(
"请输入手机号",
color = Color.Gray,
fontSize = 16.sp
)
}
innerTextField()
}
},
)
}
}
// 分割线
Box(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.background(Color.Transparent)
)
// 验证码框
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(
Color(0x4DE3E0ED),
shape = RoundedCornerShape(12.dp),
)
) {
Row(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(end = 5.dp),
horizontalArrangement = Arrangement.End
) {
BasicTextField(
value = viewModel.captcha.value,
onValueChange = { viewModel.setCaptcha(it) },
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
.background(Color.Transparent),
textStyle = androidx.compose.ui.text.TextStyle(
color = Color.Black,
fontSize = 16.sp
),
singleLine = true,
maxLines = 1,
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Text,
imeAction = androidx.compose.ui.text.input.ImeAction.Done
),
decorationBox = { innerTextField ->
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically)
.padding(horizontal = 14.dp)
.background(Color.Transparent)
) {
if (viewModel.captcha.value.isEmpty()) {
Text(
"请输入验证码",
color = Color.Gray,
fontSize = 16.sp
)
}
innerTextField()
}
}
)
Box(
modifier = Modifier
.height(40.dp)
.width(98.dp)
.align(Alignment.CenterVertically)
.background(
if (isCaptchaCountdown) Color(0xFFE0E0E0) else Color(0xFFFFFFFF),
shape = RoundedCornerShape(8.dp)
)
.clickable(enabled = !isCaptchaCountdown) {
// 点击获取验证码
if (validatePhoneEmpty(
viewModel = viewModel,
showToast = true
)
) {
// 请求验证码(请完善requestCaptcha函数)
viewModel.requestCaptcha(viewModel.userName.value)
// 开始倒计时倒计时应该在requestCaptcha完成后开始
isCaptchaCountdown = true
return@clickable
}
}
){
if (isCaptchaCountdown) {
// 倒计时文本倒计时从60秒开始
Text(
"${captchaCountdown}",
color = Color(0xFF3D3D3D),
fontSize = 12.sp,
modifier = Modifier
.align(Alignment.Center)
)
} else {
Text(
"获取验证码",
color = Color(0xFF3D3D3D),
fontSize = 12.sp,
modifier = Modifier
.align(Alignment.Center)
)
}
}
}
}
}
// 登录按钮
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
.background(
Color(0xFF252525),
shape = RoundedCornerShape(359.dp),
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 点击登录
if (validateCaptchaLoginEmpty(
viewModel = viewModel,
showToast = true
)
) {
// 验证通过(通过验证码验证),请求登录
viewModel.requestLoginForCaptcha(
viewModel.userName.value,
viewModel.captcha.value
)
}
}
) {
Text(
"登录",
color = Color(0xFFC2FF43),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 12.dp)
.align(Alignment.Center)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 14.dp)
) {
Checkbox(
checked = viewModel.isPolicyAgreement.value,
onCheckedChange = { isChecked ->
viewModel.setIsPolicyAgreement(isChecked)
},
modifier = Modifier
.size(16.dp)
.scale(0.35f)
.padding(start = 6.dp)
.background(
if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color.Transparent,
shape = RoundedCornerShape(36.dp)
)
.border(
width = 1.dp,
color = if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color(0xFFCCCCCC),
shape = RoundedCornerShape(36.dp)
)
.align(Alignment.CenterVertically),
colors = androidx.compose.material3.CheckboxDefaults.colors(
checkedColor = Color.Transparent, // 隐藏默认背景
uncheckedColor = Color.Transparent, // 隐藏默认背景
checkmarkColor = Color.White
)
)
val annotatedText = buildAnnotatedString {
append("我已阅读并同意")
// 用户协议部分
pushStringAnnotation(tag = "USER_AGREEMENT", annotation = "user_agreement")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《用户协议》")
}
pop()
append("")
// 隐私政策部分
pushStringAnnotation(tag = "PRIVACY_POLICY", annotation = "privacy_policy")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《隐私政策》")
}
pop()
}
@Suppress("DEPRECATION")
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(offset, offset)
.firstOrNull()?.let { annotation ->
when (annotation.tag) {
"USER_AGREEMENT" -> {
// 打开用户协议
openAgreement(context = context, title = "用户协议", url = agreementUrl)
}
"PRIVACY_POLICY" -> {
// 打开隐私政策
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
}
}
}
},
style = androidx.compose.ui.text.TextStyle(
fontSize = 12.sp,
color = Color.Gray
),
modifier = Modifier
.padding(start = 4.dp)
.align(Alignment.CenterVertically)
)
}
}
}
/**
* 一键登录页
* 至少包含号码栏(NumberTextview)、品牌露出(SloganTextview)、登录按钮(LoginButton)、隐私确认(PrivacyCheckbox)、隐私标题(PrivacyTextview)
*/
@SuppressLint("UseKtx", "InflateParams", "SetTextI18n")
@Composable
private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel) {
val TAG = "Rabbit_LoginScreen_OneKeyLoginScreen"
val preLoginResult = GYManager.getInstance().preLoginResult
val phoneNumber = viewModel.oneKeyPreLogin?.number ?: ""
val operator = preLoginResult.operator//运营商CM 移动CT 电信CU 联通
val privacyName = "${preLoginResult.privacyName}"
val privacyUrl = preLoginResult.privacyUrl
// 详细打印preLoginResult信息
Log.w(TAG, "=== preLoginResult 详细信息 ===")
Log.w(TAG, "preLoginResult对象: $preLoginResult")
Log.w(TAG, "operator: ${preLoginResult.operator}")
Log.w(TAG, "isValid: ${preLoginResult.isValid}")
Log.w(TAG, "privacyName: ${preLoginResult.privacyName}")
Log.w(TAG, "privacyUrl: ${preLoginResult.privacyUrl}")
Log.w(TAG, "================================")
val agreementText = "登录即认可${privacyName}、《用户协议》和《隐私政策》并使用本机号码登录"
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
// 使用AndroidView嵌入XML布局
@Suppress("COMPOSE_APPLIER_CALL_MISMATCH")
AndroidView(
factory = { context ->
LayoutInflater.from(context).inflate(
R.layout.layout_one_key_login,
null,
false
) as ConstraintLayout
},
modifier = Modifier.fillMaxSize(),
update = { view ->
// 动态更新XML布局中的内容
val phoneTextView = view.findViewById<TextView>(R.id.layout_one_key_login_tv_phone)
phoneTextView.text = phoneNumber // 需要回去手机号码
val serviceTextView = view.findViewById<TextView>(R.id.layout_one_key_login_tv_service)
val serviceText = when (operator) {
"CT" -> {//运营商CM 移动CT 电信CU 联通
"天翼账号提供认证服务"
}
"CU" -> {//联通
"认证服务由联通统一认证提供"
}
else -> {//移动
"移动账号提供认证服务"
}
}
serviceTextView.text = serviceText // 品牌露出,如:“天翼账号提供认证服务”
val checkbox = view.findViewById<CheckBox>(R.id.layout_one_key_login_agreement_checkbox)
checkbox.isChecked = viewModel.isPolicyAgreement.value
checkbox.setOnCheckedChangeListener { _, isChecked ->
viewModel.setIsPolicyAgreement(isChecked)
}
val agreementTextView = view.findViewById<TextView>(R.id.layout_one_key_login_agreement_tv)
val targets = mapOf(
"serviceAgreement" to privacyName,
"userAgreement" to "《用户协议》",
"privacyAgreement" to "《隐私政策》"
)
// 设置可点击的协议文本
AgreementTextHelper.setupAgreementTextView(agreementText, targets, agreementTextView, isUnderlineText = false) { agreementType ->
when (agreementType) {
"serviceAgreement" -> openAgreement(context = context, title = privacyName, url = privacyUrl)
"userAgreement" -> openAgreement(context = context, title = "用户协议", url = agreementUrl)
"privacyAgreement" -> openAgreement(context = context, title = "隐私政策", url = privacyUrl)
}
}
val loginButton = view.findViewById<TextView>(R.id.layout_one_key_login_btn)
// 处理登录点击
oneKeyLogin(
context = context,
numberTv = phoneTextView,
sloganTv = serviceTextView,
loginBtn = loginButton,
checkBox = checkbox,
privacyTv = agreementTextView,
viewModel = viewModel
)
// loginButton.setOnClickListener {
// // 处理登录点击
// oneKeyLogin(
// context = context,
// numberTv = phoneTextView,
// sloganTv = serviceTextView,
// loginBtn = loginButton,
// checkBox = checkbox,
// privacyTv = agreementTextView,
// viewModel = viewModel
// )
// }
}
)
}
}
@Composable
private fun WxLoginScreen(
context: Context,
viewModel: LoginViewModel,
generalViewModel: GeneralViewModel
) {
Column {
Image(
painter = painterResource(id = R.mipmap.ic_launcher_logo),
contentDescription = null,
modifier = Modifier
.size(86.dp)
.align(Alignment.CenterHorizontally)
)
Text(
text = "截图兔",
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
color = Color(0xFF1A1A1A),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.align(Alignment.CenterHorizontally)
)
Text(
text = "为了更好地为您提供服务,请先完成微信授权",
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
color = Color(0xFFAAAAAA),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
.align(Alignment.CenterHorizontally)
)
// 登录按钮
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 8.dp)
.background(
Color(0xFF252525),
shape = RoundedCornerShape(359.dp),
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 启动微信验证,请求登录
if (viewModel.isPolicyAgreement.value) {
//打开微信登录
viewModel.authorWechat(context, generalViewModel.api)
} else {
CenterToast.show("请先同意用户协议和隐私政策")
}
}
) {
Row(
modifier = Modifier
.align(Alignment.Center)
) {
Image(
painter = painterResource(id = R.drawable.ic_wx_icon),
contentDescription = null,
modifier = Modifier
.size(32.dp)
.padding(start = 12.dp)
.align(Alignment.CenterVertically)
)
Text(
"微信授权登录",
color = Color(0xFFC2FF43),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 12.dp)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 14.dp)
) {
Checkbox(
checked = viewModel.isPolicyAgreement.value,
onCheckedChange = { isChecked ->
viewModel.setIsPolicyAgreement(isChecked)
},
modifier = Modifier
.size(16.dp)
.scale(0.35f)
.padding(start = 6.dp)
.background(
if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color.Transparent,
shape = RoundedCornerShape(36.dp)
)
.border(
width = 1.dp,
color = if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color(0xFFCCCCCC),
shape = RoundedCornerShape(36.dp)
)
.align(Alignment.CenterVertically),
colors = androidx.compose.material3.CheckboxDefaults.colors(
checkedColor = Color.Transparent, // 隐藏默认背景
uncheckedColor = Color.Transparent, // 隐藏默认背景
checkmarkColor = Color.White
)
)
val annotatedText = buildAnnotatedString {
append("我已阅读并同意")
// 用户协议部分
pushStringAnnotation(tag = "USER_AGREEMENT", annotation = "user_agreement")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《用户协议》")
}
pop()
append("")
// 隐私政策部分
pushStringAnnotation(tag = "PRIVACY_POLICY", annotation = "privacy_policy")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《隐私政策》")
}
pop()
}
@Suppress("DEPRECATION")
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(offset, offset)
.firstOrNull()?.let { annotation ->
when (annotation.tag) {
"USER_AGREEMENT" -> {
// 打开用户协议
openAgreement(context = context, title = "用户协议", url = agreementUrl)
}
"PRIVACY_POLICY" -> {
// 打开隐私政策
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
}
}
}
},
style = androidx.compose.ui.text.TextStyle(
fontSize = 12.sp,
color = Color.Gray
),
modifier = Modifier
.padding(start = 4.dp)
.align(Alignment.CenterVertically)
)
}
}
}
@Composable
private fun AliPayLoginScreen(
context: Context,
viewModel: LoginViewModel,
) {
Column{
Image(
painter = painterResource(id = R.mipmap.ic_launcher_logo),
contentDescription = null,
modifier = Modifier
.size(86.dp)
.align(Alignment.CenterHorizontally)
)
Text(
text = "截图兔",
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
color = Color(0xFF1A1A1A),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.align(Alignment.CenterHorizontally)
)
Text(
text = "为了更好地为您提供服务,请先完成支付宝授权",
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
color = Color(0xFFAAAAAA),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
.align(Alignment.CenterHorizontally)
)
// 登录按钮
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 8.dp)
.background(
Color(0xFF252525),
shape = RoundedCornerShape(359.dp),
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 启动支付宝验证,请求登录
if (viewModel.isPolicyAgreement.value) {
// 打开支付宝登录
viewModel.requestAliPayAuthParam(context)
} else {
CenterToast.show("请先同意用户协议和隐私政策")
}
}
) {
Row(
modifier = Modifier
.align(Alignment.Center)
) {
Image(
painter = painterResource(id = R.drawable.ic_alipay_icon),
contentDescription = null,
modifier = Modifier
.size(32.dp)
.padding(start = 12.dp)
.align(Alignment.CenterVertically)
)
Text(
"支付宝授权登录",
color = Color(0xFFC2FF43),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 12.dp)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 14.dp)
) {
Checkbox(
checked = viewModel.isPolicyAgreement.value,
onCheckedChange = { isChecked ->
viewModel.setIsPolicyAgreement(isChecked)
},
modifier = Modifier
.size(16.dp)
.scale(0.35f)
.padding(start = 6.dp)
.background(
if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color.Transparent,
shape = RoundedCornerShape(36.dp)
)
.border(
width = 1.dp,
color = if (viewModel.isPolicyAgreement.value) Color(0xFF252525)
else Color(0xFFCCCCCC),
shape = RoundedCornerShape(36.dp)
)
.align(Alignment.CenterVertically),
colors = androidx.compose.material3.CheckboxDefaults.colors(
checkedColor = Color.Transparent, // 隐藏默认背景
uncheckedColor = Color.Transparent, // 隐藏默认背景
checkmarkColor = Color.White
)
)
val annotatedText = buildAnnotatedString {
append("我已阅读并同意")
// 用户协议部分
pushStringAnnotation(tag = "USER_AGREEMENT", annotation = "user_agreement")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《用户协议》")
}
pop()
append("")
// 隐私政策部分
pushStringAnnotation(tag = "PRIVACY_POLICY", annotation = "privacy_policy")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("《隐私政策》")
}
pop()
}
@Suppress("DEPRECATION")
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(offset, offset)
.firstOrNull()?.let { annotation ->
when (annotation.tag) {
"USER_AGREEMENT" -> {
// 打开用户协议
openAgreement(context = context, title = "用户协议", url = agreementUrl)
}
"PRIVACY_POLICY" -> {
// 打开隐私政策
openAgreement(context = context, title = "隐私政策", url = privacyUrl)
}
}
}
},
style = androidx.compose.ui.text.TextStyle(
fontSize = 12.sp,
color = Color.Gray
),
modifier = Modifier
.padding(start = 4.dp)
.align(Alignment.CenterVertically)
)
}
}
}
@Composable
private fun OtherLoginBar(viewModel: LoginViewModel) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 64.dp, end = 64.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier
.width(60.dp)
.height(2.dp)
.align(Alignment.CenterVertically)
.background(
brush = Brush.horizontalGradient(
colors = listOf(Color(0x00D8D8D8), Color(0xFFE5E5E5))
)
)
)
Text(
"其他登录方式",
color = Color(0xFF767676),
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier
.padding(horizontal = 14.dp)
.align(Alignment.CenterVertically)
)
Box(
modifier = Modifier
.width(60.dp)
.height(2.dp)
.align(Alignment.CenterVertically)
.background(
brush = Brush.horizontalGradient(
colors = listOf(Color(0xFFE5E5E5), Color(0x00D8D8D8))
)
)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 6.dp, vertical = 27.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Image(
painter = painterResource(id = R.mipmap.ic_wechat),
contentDescription = null,
modifier = Modifier
.size(32.dp)
.weight(1f)
.align(Alignment.CenterVertically)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 微信登录
viewModel.loginScreenType.value = LoginScreenType.LOGIN_WX
}
)
Image(
painter = painterResource(id = R.mipmap.ic_alipay),
contentDescription = null,
modifier = Modifier
.size(32.dp)
.weight(1f)
.align(Alignment.CenterVertically)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 支付宝登录
viewModel.loginScreenType.value = LoginScreenType.LOGIN_ALIPAY
}
)
Image(
painter = painterResource(id = R.mipmap.ic_phone),
contentDescription = null,
modifier = Modifier
.size(32.dp)
.weight(1f)
.align(Alignment.CenterVertically)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
// 校验码登录
viewModel.loginScreenType.value = LoginScreenType.LOGIN_CAPTCHA
}
)
}
}
}
/**
* 验证手机号是否为空
*/
private fun validatePhoneEmpty(viewModel: LoginViewModel, showToast: Boolean = false): Boolean {
if (showToast && viewModel.userName.value.isEmpty()) {
CenterToast.show("请输入手机号")
}
return viewModel.userName.value.isNotEmpty()
}
/**
* 验证验证码登录是否为空
*/
private fun validateCaptchaLoginEmpty(viewModel: LoginViewModel, showToast: Boolean = false): Boolean {
if (showToast) {
if(viewModel.userName.value.isEmpty()){
CenterToast.show("请输入手机号")
}else if(viewModel.captcha.value.isEmpty()){
CenterToast.show("请输入验证码")
}else if(!viewModel.isPolicyAgreement.value){
CenterToast.show("请同意用户协议和隐私政策")
}
}
return viewModel.userName.value.isNotEmpty() && viewModel.captcha.value.isNotEmpty() && viewModel.isPolicyAgreement.value
}
private fun oneKeyLogin(
context: Context,
numberTv: TextView,
sloganTv: TextView,
loginBtn: TextView,
checkBox: CheckBox,
privacyTv: TextView,
viewModel: LoginViewModel,
) {
val gyCallBack = object : GyCallBack {
override fun onSuccess(response: GYResponse?) {
loginBtn.isEnabled = true
Log.i("OneKeyLogin", "OneKeyLoginViewModel------onSuccess:$response")
try {
val jsonObject = JSONObject(response?.msg?:"{}")
val data = jsonObject.getJSONObject("data")
val token = data.getString("token")
viewModel.requestOneKeyLogin(response?.gyuid?:"", token)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onFailed(p0: GYResponse?) {
loginBtn.isEnabled = true
Log.e("OneKeyLogin", "OneKeyLoginViewModel------onFailed:$p0")
}
}
val eloginActivityParam = EloginActivityParam()
.setActivity(context as Activity)
.setNumberTextview(numberTv)
.setSloganTextview(sloganTv)
.setLoginButton(loginBtn)
.setPrivacyCheckbox(checkBox)
.setPrivacyTextview(privacyTv)
.setUiErrorListener { msg ->
loginBtn.isEnabled = true
Log.e("OneKeyLogin", "OneKeyLoginViewModel------UIErrorListener.onError:$msg")
}
.setLoginOnClickListener {
if (!checkBox.isChecked) {
throw IllegalStateException("请先仔细阅读协议并勾选,然后再点击登录")
}
loginBtn.isEnabled = false
}
// 预先注册登录回调
GYManager.getInstance().eAccountLogin(eloginActivityParam, 5000, gyCallBack)
}
enum class LoginScreenType {
LOGIN_NORMAL,
LOGIN_ONE_KEY,
LOGIN_CAPTCHA,
LOGIN_WX,
LOGIN_ALIPAY,
}
@Preview(showBackground = true)
@Composable
private fun PreviewOneKeyLoginScreen() {
OneKeyLoginScreen(LocalContext.current, viewModel())
}
@Preview(showBackground = true)
@Composable
private fun PreviewWxLoginScreen() {
WxLoginScreen(LocalContext.current, viewModel(), viewModel())
}
@Preview(showBackground = true)
@Composable
private fun PreviewAliPayLoginScreen() {
AliPayLoginScreen(LocalContext.current, viewModel())
}
@Preview(showBackground = true)
@Composable
private fun PreviewLoginScreen() {
LoginScreen(navController = rememberNavController(), generalViewModel = viewModel(), loginViewModel = viewModel(), isVisibilityBreak = false)
}