rabbit-android/app/src/main/java/com/img/rabbit/viewmodel/LoginViewModel.kt

425 lines
16 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.

package com.img.rabbit.viewmodel
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableIntStateOf
import com.alipay.sdk.app.AuthTask
import com.img.rabbit.pages.LoginScreenType
import com.g.gysdk.GYManager
import com.g.gysdk.GYResponse
import com.g.gysdk.GyCallBack
import com.g.gysdk.GyConfig
import com.github.gzuliyujiang.oaid.DeviceIdentifier
import com.google.gson.JsonObject
import com.img.rabbit.bean.local.ErrorBean
import com.img.rabbit.bean.local.OnekeyPreLogin
import com.img.rabbit.bean.response.LoginInfoEntity
import com.img.rabbit.config.Constants
import com.img.rabbit.provider.api.ApiManager
import com.img.rabbit.provider.api.ResultVo
import com.img.rabbit.provider.storage.GlobalStateManager
import com.img.rabbit.provider.storage.PreferenceUtil
import com.img.rabbit.utils.MMKVUtils
import com.tencent.mm.opensdk.modelmsg.SendAuth
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.internal.platform.PlatformRegistry.applicationContext
class LoginViewModel : BaseViewModel() {
private val TAG = "LoginViewModel"
private val ONEKEY_TAG = "OneKeyLoginViewModel"
//private lateinit var api: IWXAPI
val authInfoForAlipay: MutableState<String> = mutableStateOf("")
val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL)
// 登录用户名
val userName = mutableStateOf("")
// 登录验证码
val captcha = mutableStateOf("")
// 登录验证码发送时间戳
val captchaTimestamp = mutableStateOf("")
fun setCaptcha(loginCaptcha: String) {
captcha.value = loginCaptcha
}
fun setUserName(loginName: String) {
userName.value = loginName
}
private val isGYUIDValid = mutableStateOf(false)
var oneKeyPreLogin: OnekeyPreLogin? = null
// 是否同意政策协议
private val _policyAgreement = mutableStateOf(true)
val isPolicyAgreement: State<Boolean> = _policyAgreement
fun setIsPolicyAgreement(isAgreement: Boolean) {
_policyAgreement.value = isAgreement
}
private val _isLogin = mutableStateOf(false)
val isLogin: State<Boolean> = _isLogin
fun setLogin(isLogin: Boolean) {
_isLogin.value = isLogin
}
// 登录状态
val loginState = mutableStateOf<ResultVo<LoginInfoEntity>?>(null)
// 错误状态
val errorState = mutableStateOf<ErrorBean?>(null)
fun requestUserConfig(){
mLaunch {
val oaid = MMKVUtils.getString("oaid") ?: ""
val response = ApiManager.serviceVo.getUserConfig(oaid, Build.VERSION.SDK_INT, "", DeviceIdentifier.getAndroidID(applicationContext), MMKVUtils.getString("gt_cid") ?: "")
if (response.status) {
PreferenceUtil.saveXToken(response.data.token)
PreferenceUtil.setTimeDiff(response.data.nowtime.toLong() - System.currentTimeMillis() / 1000)
PreferenceUtil.saveUserConfig(response.data)
}else{
Log.w("LoginViewModel", "获取配置失败: code=${response.code}, message=${response.message}")
}
isLoading.value = false // 加载完成
}
}
fun oneKeyLoginForGeTuiSdk(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
// 初始化 SDK
GYManager.getInstance().init(GyConfig.with(activity.applicationContext).callBack(object : GyCallBack {
override fun onSuccess(response: GYResponse?) {
isGYUIDValid.value = true
Log.i(ONEKEY_TAG, "--->初始化: onSuccess")
//预登录(提高拉起速度,可选但推荐)
GYManager.getInstance().ePreLogin(/* timeout = */ 8000, /* gyCallBack = */ object : GyCallBack {
override fun onSuccess(response: GYResponse?) {
Log.i(ONEKEY_TAG, "--->预登录: onSuccess--->${response}")
val preLoginData = response?.msg
if (!preLoginData.isNullOrEmpty()) {
try {
val json = Json { ignoreUnknownKeys = true }
oneKeyPreLogin =
json.decodeFromString<OnekeyPreLogin>(preLoginData)
// 打印解析后的详细数据
Log.i(ONEKEY_TAG, "=== 预登录数据解析结果 ===")
Log.i(ONEKEY_TAG, "流程ID: ${oneKeyPreLogin?.process_id}")
Log.i(ONEKEY_TAG, "运营商类型: ${oneKeyPreLogin?.operatorType}")
Log.i(ONEKEY_TAG, "客户端类型: ${oneKeyPreLogin?.clienttype}")
Log.i(ONEKEY_TAG, "访问码: ${oneKeyPreLogin?.accessCode}")
Log.i(ONEKEY_TAG, "手机号: ${oneKeyPreLogin?.number}")
Log.i(ONEKEY_TAG, "过期时间: ${oneKeyPreLogin?.expiredTime}")
Log.i(ONEKEY_TAG, "错误码: ${oneKeyPreLogin?.errorCode}")
Log.i(ONEKEY_TAG, "错误描述: ${oneKeyPreLogin?.errorDesc}")
Log.i(ONEKEY_TAG, "耗时: ${oneKeyPreLogin?.costTime}ms")
Log.i(ONEKEY_TAG, "================================")
// 根据解析结果决定是否继续根据errorCode判断
if (oneKeyPreLogin?.errorCode == 0) {
oneKeyLoginValid(
onShowOneKeyScreen = onShowOneKeyScreen
)
} else {
onShowOneKeyScreen(false)
Log.e(ONEKEY_TAG, "预登录校验失败: ${oneKeyPreLogin?.errorDesc}")
}
} catch (e: Exception) {
Log.e(ONEKEY_TAG, "JSON解析失败: ${e.message}")
Log.e(ONEKEY_TAG, "原始数据: $preLoginData")
// 解析失败时继续执行原逻辑
onShowOneKeyScreen(false)
}
}
}
override fun onFailed(response: GYResponse?) {
onShowOneKeyScreen(false)
Log.i(ONEKEY_TAG, "--->预登录: onFailed--->${response}")
}
})
}
override fun onFailed(response: GYResponse?) {
onShowOneKeyScreen(false)
Log.i(ONEKEY_TAG, "--->初始化: onFailed--->${response}")
}
}).build())
}
private fun oneKeyLoginValid(onShowOneKeyScreen:(Boolean)->Unit) {
if (GYManager.getInstance().isPreLoginResultValid) {
//预登录有效,启动登录授权页
onShowOneKeyScreen(true)
Log.i(ONEKEY_TAG, "--->预登录校验有效A: onSuccess")
} else {
//考虑到是用户在等待建议超时8s以上至少设置5s以上
GYManager.getInstance().ePreLogin(5000, object : GyCallBack {
override fun onSuccess(response: GYResponse?) {
//预登录成功,启动登录授权页
onShowOneKeyScreen(true)
Log.i(ONEKEY_TAG, "--->预登录校验有效B: onSuccess--->${response}")
}
override fun onFailed(response: GYResponse?) {
//预登录失败,提示用户稍后重试
onShowOneKeyScreen(false)
Log.i(ONEKEY_TAG, "--->预登录校验有效B: onFailed--->${response}")
}
})
}
}
fun requestOneKeyLogin(gyuid: String, token: String) {
isLoading.value = true // 开始加载
// 调用 API 获取数据
val jsonOneKey = JsonObject()
jsonOneKey.addProperty("gyuid", gyuid)
jsonOneKey.addProperty("token", token)
val jsonObject = JsonObject()
jsonObject.addProperty("type", "onekey")
jsonObject.addProperty("bind", "")
jsonObject.add("data", jsonOneKey)
requestLogin(jsonObject)
}
/**
* 请求验证码
*/
fun requestCaptcha(phone: String) {
// 发送请求获取验证码
isLoading.value = true // 开始加载
mLaunch {
// 调用 API 获取数据
val jsonObject = JsonObject()
jsonObject.addProperty("phone", phone)
val response = ApiManager.serviceVo.sendCode(jsonObject.toString().toRequestBody())
if (response.status) {
Log.w("LoginViewModel", "请求验证码成功: ${response.data.timestamp}")
captchaTimestamp.value = response.data.timestamp
}else{
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "获取验证码失败" })
}
isLoading.value = false // 加载完成
}
}
/**
* 请求登录(验证码)
*/
fun requestLoginForCaptcha(phone: String, code: String) {
isLoading.value = true // 开始加载
// 调用 API 获取数据
val jsonPhone = JsonObject()
jsonPhone.addProperty("timestamp", captchaTimestamp.value)
jsonPhone.addProperty("phone", phone)
jsonPhone.addProperty("code", code)
val jsonObject = JsonObject()
jsonObject.addProperty("login_type", "phone")
jsonObject.add("phone", jsonPhone)
requestLogin(jsonObject)
}
fun loginWithWechat(context: Context, wxApi:IWXAPI) {
if (isPolicyAgreement.value) {
doWxAuth(context, wxApi)
}else{
Toast.makeText(context, "请先同意用户协议和隐私政策", Toast.LENGTH_SHORT).show()
}
}
//获取微信授权
private fun doWxAuth(context: Context, wxApi:IWXAPI) {
if (!wxApi.isWXAppInstalled) {
Toast.makeText(context, "您没有安装微信客户端,请先下载安装", Toast.LENGTH_SHORT).show()
return
}
val req = SendAuth.Req()
req.scope = "snsapi_userinfo" // 只能填 snsapi_userinfo
req.state = context.packageName + Math.random() * 1000 + "_phone"
wxApi.sendReq(req)
}
/**
* 拿着微信授权码完成登录(在WXEntryActivity中调用)
* @param wechatCode 微信授权码
*/
fun requestWxLogin(wechatCode: String) {
if(wechatCode.isEmpty()){
return
}
isLoading.value = true // 开始加载
PreferenceUtil.saveWxCode(wechatCode)
// 调用 API 获取数据
val jsonWx = JsonObject()
jsonWx.addProperty("code", wechatCode)
jsonWx.addProperty("code_type", "")
val jsonObject = JsonObject()
jsonObject.addProperty("login_type", "weixin")
jsonObject.add("weixin", jsonWx)
requestLogin(jsonObject)
}
/**
* 支付宝登录
* authInfo: 该参数需由后端生成并加签,包含 app_id、pid、target_id 等信息
*/
fun loginWithAliPay(context: Context,onAuthResult: (Map<String, String>) -> Unit) {
if(authInfoForAlipay.value.isEmpty()){
Toast.makeText(context, "请先获取支付宝登录授权码", Toast.LENGTH_SHORT).show()
return
}
// 发送请求获取支付宝登录授权码
mLaunch {
onAuthResult(doAlipayLogin(context as Activity, authInfoForAlipay.value))
}
}
/**
* 请求支付宝登录参数
*/
fun requestAliPayAuthParam() {
isLoading.value = true // 开始加载
mLaunch {
val response = ApiManager.serviceVo.getAlipayAuthParam()
val data = response.data
val param = data.param
authInfoForAlipay.value = param
isLoading.value = false // 加载完成
}
}
/**
* 封装支付宝登录逻辑
* @param activity 当前 Activity 上下文
* @param authInfo 后端生成的授权字符串
* @return 支付宝返回的原始 Map 结果
*/
private suspend fun doAlipayLogin(activity: Activity, authInfo: String): Map<String, String> {
return withContext(Dispatchers.IO) {
// 初始化 AuthTask
val authTask = AuthTask(activity)
// 调用 authV2。第二个参数为 true 表示如果未安装支付宝则展示 Loading 界面, 该方法会阻塞当前线程直到用户操作结束
val result = authTask.authV2(authInfo, true)
Log.i(TAG, "doAlipayLogin: $result")
result ?: emptyMap()
}
}
/**
* 拿着微信授权码完成登录(在WXEntryActivity中调用)
* @param authCode 微信授权码
*/
fun requestAlipayLogin(authCode: String) {
if(authCode.isEmpty()){
return
}
isLoading.value = true // 开始加载
// 调用 API 获取数据
val jsonWx = JsonObject()
jsonWx.addProperty("auth_code", authCode)//code
val jsonObject = JsonObject()
jsonObject.addProperty("type", "alipay")
jsonObject.addProperty("bind", "")
jsonObject.add("data", jsonWx)
requestLogin(jsonObject)
}
//请求登录
private fun requestLogin(jsonObject: JsonObject){
mLaunch {
val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody())
if (response.status) {
loginState.value = response
val loginInfoEntity = response.data
loginInfoEntity.isLogin = true
//记录登录数据
PreferenceUtil.saveLoginInfo(loginInfoEntity)
}else{
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "登录失败" })
}
isLoading.value = false // 加载完成
}
}
fun requestUserInfo(){
mLaunch {
val response = ApiManager.serviceVo.userInfo()
if(response.status){
PreferenceUtil.saveUserInfo(response.data)
}
}
}
/**
* 请求退出登录
*/
@OptIn(DelicateCoroutinesApi::class)
fun requestLogout(context: Context) {
isLoading.value = true // 开始加载
mLaunch {
val response = ApiManager.serviceVo.logout()
if (response.status) {
//logoutState.value = response
PreferenceUtil.clearLogin()
setLogin(false)
// 跳转登录页面
//restViewModel.intValue = 1
GlobalScope.launch {
GlobalStateManager(context).storeGlobalLogout(true)
}
} else {
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "退出登录失败" })
}
isLoading.value = false // 加载完成
}
}
enum class JumpLoginType(val type: Int,val desc: String){
NORMAL(0,"正常登录"),
FROM_ADD(1,"添加账号"),
FROM_LOGOUT(2,"退出登录")
}
}