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

1470 lines
57 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 android.widget.Toast
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.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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
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.bean.local.toAlipayResult
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.GlobalStateManager
import com.img.rabbit.provider.storage.PreferenceUtil
import com.img.rabbit.utils.StringUtils
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
import kotlinx.coroutines.delay
import org.json.JSONObject
@SuppressLint("UnrememberedMutableState")
@Composable
fun LoginScreen(navController: NavHostController? = null, generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel, isVisibilityBreak: Boolean) {
val context = LocalContext.current
val networkStatus by generalViewModel.networkStatus.observeAsState(initial = true)
var showNetworkDisconnected by remember { mutableStateOf(false) }
// 网络状态监听
LaunchedEffect(networkStatus) {
if (!networkStatus) {
// 网络断开时的处理
Log.w("NetworkStatus","网络断开")
}else{
Log.w("NetworkStatus","网络已连接")
}
}
// 登录成功后,保存 token
LaunchedEffect(loginViewModel.loginState.value) {
if (loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token != null) {
//登录成功
PreferenceUtil.saveAccessToken(loginViewModel.loginState.value?.data?.token)
loginViewModel.setLogin(true)
//更新登录状态
GlobalStateManager(context).storeGlobalLoginNotify(true)
//清理loginState
loginViewModel.loginState.value = null
//提示登录成功
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
// 当允许返回上一页时,登录成功后,返回上一页
if(isVisibilityBreak){
navController?.popBackStack()
}
}else if(loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token == null){
//登录失败
loginViewModel.setLogin(false)
Log.w("LoginScreen","登录失败,无有效的Token")
Toast.makeText(context, "登录失败,请重新登录", Toast.LENGTH_SHORT).show()
}
}
var globalWxAuthorization by mutableStateOf(GlobalStateManager(context).globalWxAuthorizationFlow().collectAsState(initial = ""))
LaunchedEffect(globalWxAuthorization.value) {
if(globalWxAuthorization.value?.isNotEmpty() == true){
loginViewModel.requestWxLogin(globalWxAuthorization.value!!)
//清理globalWxAuthorization
GlobalStateManager(context).storeGlobalWxAuthorization("")
}
}
LaunchedEffect(loginViewModel.authInfoForAlipay.value) {
if(loginViewModel.authInfoForAlipay.value.isEmpty()) return@LaunchedEffect
loginViewModel.loginWithAliPay(context){rawResult ->
Log.i("loginWithAliPay", "支付宝登录结果:$rawResult")
val alipayResult = rawResult.toAlipayResult()
// 处理支付宝登录结果
when (alipayResult.resultStatus) {
"9000" -> {
// 登录成功result 字段中包含 auth_code
val authCode = StringUtils.parseAlipayResult(alipayResult.result ?: "")["auth_code"] ?: ""
loginViewModel.requestAlipayLogin(authCode)
}
"6001" -> {
"用户取消登录"
}
else -> {
alipayResult.memo ?: "登录失败"
}
}
//清理authInfoForAlipay
loginViewModel.authInfoForAlipay.value = ""
}
}
Scaffold{
Box(
modifier = Modifier.fillMaxSize()
) {
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, generalViewModel)
}
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, generalViewModel)
}
}
// 其他登录方式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(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, generalViewModel: GeneralViewModel) {
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(
context = context,
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(
context = context,
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()
}
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, generalViewModel: GeneralViewModel) {
val ONEKEY_TAG = "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(ONEKEY_TAG, "=== preLoginResult 详细信息 ===")
Log.w(ONEKEY_TAG, "preLoginResult对象: $preLoginResult")
Log.w(ONEKEY_TAG, "operator: ${preLoginResult.operator}")
Log.w(ONEKEY_TAG, "isValid: ${preLoginResult.isValid}")
Log.w(ONEKEY_TAG, "privacyName: ${preLoginResult.privacyName}")
Log.w(ONEKEY_TAG, "privacyUrl: ${preLoginResult.privacyUrl}")
Log.w(ONEKEY_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)
//TODO 服务协议,如:“登录即认可《天翼账号服务与隐私协议》、《用户协议》和《隐私政策》并使用本机号码登录”
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)
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.loginWithWechat(context, generalViewModel.api)
} else {
Toast.makeText(
context,
"请先同意用户协议和隐私政策",
Toast.LENGTH_SHORT
).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()
}
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()
} else {
Toast.makeText(
context,
"请先同意用户协议和隐私政策",
Toast.LENGTH_SHORT
).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()
}
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")
@Composable
private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel) {
val preLoginResult = GYManager.getInstance().preLoginResult
preLoginResult.operator
preLoginResult.isValid
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 230.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "18698756851",
fontWeight = FontWeight.Bold,
fontSize = 36.sp,
color = Color(0xFF1A1A1A),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
)
Text(
text = "天翼账号提供认证服务",
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
color = Color(0xFF767676),
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
)
// 登录按钮
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 30.dp, end = 30.dp, top = 46.dp)
.background(
Color(0xFF252525),
shape = RoundedCornerShape(359.dp),
)
.clickable {
// 点击登录
if (validateCaptchaLoginEmpty(
context = context,
viewModel = viewModel,
showToast = true
)
) {
//TODO 验证码登录请求
Toast.makeText(context, "登录成功!", Toast.LENGTH_SHORT).show()
}
}
) {
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(top = 16.dp, 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)
),
colors = androidx.compose.material3.CheckboxDefaults.colors(
checkedColor = Color.Transparent, // 隐藏默认背景
uncheckedColor = Color.Transparent, // 隐藏默认背景
checkmarkColor = Color.White
)
)
val annotatedText = buildAnnotatedString {
append("我已阅读并同意")
// 用户协议部分
pushStringAnnotation(tag = "SERVICE_AGREEMENT", annotation = "service_agreement")
withStyle(style = SpanStyle(
color = Color(0xFF767676),
fontWeight = FontWeight.Bold,
//textDecoration = TextDecoration.Underline
)) {
append("${preLoginResult.privacyName}")
}
pop()
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()
}
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(offset, offset)
.firstOrNull()?.let { annotation ->
when (annotation.tag) {
"SERVICE_AGREEMENT" -> {
// 打开服务协议
preLoginResult.privacyUrl?.let {
Intent(Intent.ACTION_VIEW, it.toUri()).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.let { intent ->
context.startActivity(intent)
}
}
}
"USER_AGREEMENT" -> {
//TODO 打开用户协议
Toast.makeText(context, "打开用户协议", Toast.LENGTH_SHORT).show()
}
"PRIVACY_POLICY" -> {
//TODO 打开隐私政策
Toast.makeText(context, "打开隐私政策", Toast.LENGTH_SHORT).show()
}
}
}
},
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(context: Context, viewModel: LoginViewModel, showToast: Boolean = false): Boolean {
if (showToast && viewModel.userName.value.isEmpty()) {
Toast.makeText(context, "请输入手机号", Toast.LENGTH_SHORT).show()
}
return viewModel.userName.value.isNotEmpty()
}
/**
* 验证验证码登录是否为空
*/
private fun validateCaptchaLoginEmpty(context: Context, viewModel: LoginViewModel, showToast: Boolean = false): Boolean {
if (showToast) {
if(viewModel.userName.value.isEmpty()){
Toast.makeText(context, "请输入手机号", Toast.LENGTH_SHORT).show()
}else if(viewModel.captcha.value.isEmpty()){
Toast.makeText(context, "请输入验证码", Toast.LENGTH_SHORT).show()
}else if(!viewModel.isPolicyAgreement.value){
Toast.makeText(context, "请同意用户协议和隐私政策", Toast.LENGTH_SHORT).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 eloginActivityParam = EloginActivityParam()
.setActivity(context as Activity)
.setNumberTextview(numberTv)
.setSloganTextview(sloganTv)
.setLoginButton(loginBtn)
.setPrivacyCheckbox(checkBox)
.setPrivacyTextview(privacyTv)
.setUiErrorListener { msg -> //隐私协议未打勾、界面不合规、setLoginOnClickListener抛出异常等情况下的回调
Log.e("OneKeyLogin", "UIErrorListener.onError:$msg")
}
.setLoginOnClickListener {
if (!checkBox.isChecked) {
// 抛出异常避免sdk进行后续登录动作否则eAccountLogin会回调onFailed错误
throw IllegalStateException("请先仔细阅读协议并勾选,然后再点击登录")
}
//启动登录时候的转圈圈
}
GYManager.getInstance().eAccountLogin(eloginActivityParam, 5000, object : GyCallBack {
override fun onSuccess(response: GYResponse?) {
// 登录成功,需要与后端交互
Log.i("OneKeyLogin", "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?) {
// 登录失败
Log.e("OneKeyLogin", "onFailed:$p0")
}
})
}
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(), 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)
}