@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(R.id.layout_one_key_login_tv_phone) phoneTextView.text = phoneNumber // 需要回去手机号码 val serviceTextView = view.findViewById(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(R.id.layout_one_key_login_agreement_checkbox) checkbox.isChecked = viewModel.isPolicyAgreement.value checkbox.setOnCheckedChangeListener { _, isChecked -> viewModel.setIsPolicyAgreement(isChecked) } val agreementTextView = view.findViewById(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(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) }