package com.img.rabbit.viewmodel import android.app.Activity import android.content.Context import android.os.Build import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.State import androidx.lifecycle.viewModelScope 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.local.toAlipayResult import com.img.rabbit.bean.response.LoginInfoEntity import com.img.rabbit.bean.response.UserConfigEntity import com.img.rabbit.components.CenterToast 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.provider.utils.HeadParamUtils.applicationContext import com.img.rabbit.utils.AppEventBus import com.img.rabbit.utils.AppUpdate import com.img.rabbit.utils.GlobalEvent import com.img.rabbit.utils.GlobalEventBus import com.img.rabbit.utils.LoginBindEvent import com.img.rabbit.utils.MMKVUtils import com.img.rabbit.utils.StringUtils import com.tencent.mm.opensdk.modelmsg.SendAuth import com.tencent.mm.opensdk.openapi.IWXAPI import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import okhttp3.RequestBody.Companion.toRequestBody class LoginViewModel : BaseViewModel() { private val TAG = "LoginViewModel" private val ONEKEY_TAG = "OneKeyLoginViewModel" var isLoginWxAuthor = false val authInfoForAlipay: MutableState = mutableStateOf("") val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL) // 登录用户名 val userName = mutableStateOf("") fun setUserName(loginName: String) { userName.value = loginName } // 登录验证码 val captcha = mutableStateOf("") fun setCaptcha(loginCaptcha: String) { captcha.value = loginCaptcha } // 登录验证码发送时间戳 val captchaTimestamp = mutableStateOf("") private val isGYUIDValid = mutableStateOf(false) var oneKeyPreLogin: OnekeyPreLogin? = null // 是否同意政策协议 private val _policyAgreement = mutableStateOf(true) val isPolicyAgreement: State = _policyAgreement fun setIsPolicyAgreement(isAgreement: Boolean) { _policyAgreement.value = isAgreement } // 登录获取结果 val loginResult = mutableStateOf?>(null) // 用户配置获取结果 val userConfigResult = mutableStateOf?>(null) // 错误状态 val errorState = mutableStateOf(null) fun reset(){ isLoginWxAuthor = false authInfoForAlipay.value = "" loginScreenType.value = LoginScreenType.LOGIN_NORMAL userName.value = "" captcha.value = "" captchaTimestamp.value = "" isGYUIDValid.value = false oneKeyPreLogin = null setIsPolicyAgreement(true) loginResult.value = null userConfigResult.value = null errorState.value = null } fun requestUserConfig(isInitConfig: Boolean = false){ mLaunch { //先置空配置 PreferenceUtil.saveUserConfig(null) 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) userConfigResult.value = response if(isInitConfig){ val versionEntity = response.data.config?.versionEntity AppUpdate.checkUpdate(versionEntity) { isUpdate, _, _ -> if(isUpdate){ // 有更新,提示更新 viewModelScope.launch{ versionEntity?.let { GlobalEventBus.emit( GlobalEvent.ShowAppUpdateNotify) } } } } } //以下是刷新状态 // applicationContext?.let { GlobalStateManager(it) }?.storeGlobalUserConfigNotify(true) }else{ Log.w("LoginViewModel", "获取配置失败: code=${response.code}, message=${response.message}") } } } 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(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) { // 调用 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) Log.i(ONEKEY_TAG, "--->开始登录") requestLogin(jsonObject, LoginScreenType.LOGIN_ONE_KEY) } /** * 请求验证码 */ fun requestCaptcha(phone: String) { // 发送请求获取验证码 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 { "获取验证码失败" }) } } } /** * 请求登录(验证码) */ fun requestLoginForCaptcha(phone: String, code: String) { // 调用 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, LoginScreenType.LOGIN_CAPTCHA) } fun authorWechat(context: Context, wxApi:IWXAPI) { if (isPolicyAgreement.value) { if (!wxApi.isWXAppInstalled) { CenterToast.show("您没有安装微信客户端,请先下载安装") return } isLoginWxAuthor = true val req = SendAuth.Req() req.scope = "snsapi_userinfo" // 只能填 snsapi_userinfo req.state = context.packageName + Math.random() * 1000 + "_phone" wxApi.sendReq(req) }else{ CenterToast.show("请先同意用户协议和隐私政策") } } fun requestWxLogin(code: String){ val jsonObject = getWxLoginParam(code) ?: return requestLogin(jsonObject, LoginScreenType.LOGIN_WX) } /** * 拿着微信授权码完成登录(在WXEntryActivity中调用) * @param code 微信授权码 */ fun getWxLoginParam(code: String): JsonObject? { if(code.isEmpty()){ return null } PreferenceUtil.saveWxCode(code) // 调用 API 获取数据 val jsonWx = JsonObject() jsonWx.addProperty("code", code) jsonWx.addProperty("code_type", "") val jsonObject = JsonObject() jsonObject.addProperty("login_type", "weixin") jsonObject.add("weixin", jsonWx) return jsonObject } /** * 请求支付宝登录参数 */ fun requestAliPayAuthParam(context: Context) { mLaunch { val response = ApiManager.serviceVo.getAlipayAuthParam() val data = response.data val param = data.param authorAlipay(context = context,authParam = param) authInfoForAlipay.value = param } } private fun authorAlipay(context: Context, authParam: String){ if(authParam.isEmpty()){ CenterToast.show("请先获取支付宝登录授权码") return } // 发送请求获取支付宝登录授权码 mLaunch { val authorResult = doAlipayLogin(context as Activity, authParam) Log.i("loginWithAliPay", "支付宝登录结果:$authorResult") val alipayResult = authorResult.toAlipayResult() // 处理支付宝登录结果 when (alipayResult.resultStatus) { "9000" -> { // 登录成功,result 字段中包含 auth_code val authCode = StringUtils.parseAlipayResult(alipayResult.result ?: "")["auth_code"] ?: "" val jsonObject = getAlipayLoginParam(authCode) ?: return@mLaunch requestLogin(jsonObject, LoginScreenType.LOGIN_ALIPAY) } "6001" -> { "用户取消登录" } else -> { alipayResult.memo ?: "登录失败" } } } } /** * 封装支付宝登录逻辑 * @param activity 当前 Activity 上下文 * @param authInfo 后端生成的授权字符串 * @return 支付宝返回的原始 Map 结果 */ private suspend fun doAlipayLogin(activity: Activity, authInfo: String): Map { 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() } } /** * 拿着支付宝授权码完成登录 * @param authCode 支付宝授权码 */ private fun getAlipayLoginParam(authCode: String): JsonObject? { if(authCode.isEmpty()){ return null } // 调用 API 获取数据 val jsonWx = JsonObject() jsonWx.addProperty("auth_code", authCode)//code val jsonObject = JsonObject() jsonObject.addProperty("type", "alipay") jsonObject.addProperty("bind", "") jsonObject.add("data", jsonWx) return jsonObject } //请求登录 private fun requestLogin(jsonObject: JsonObject, loginType: LoginScreenType){ mLaunch { val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody()) if (response.status) { PreferenceUtil.saveLoginType(loginType.name) PreferenceUtil.saveLoginInfo(response.data.apply { isLogin = true })//记录登录数据 PreferenceUtil.saveAccessToken(response.data.token) loginResult.value = response }else{ errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "登录失败" }) } } } fun requestUserInfo(){ PreferenceUtil.saveUserInfo(null) mLaunch { val response = ApiManager.serviceVo.userInfo() if(response.status){ PreferenceUtil.saveUserInfo(response.data) } } } /** * 请求退出登录 */ @OptIn(DelicateCoroutinesApi::class) fun requestLogout() { mLaunch { val response = ApiManager.serviceVo.logout() if (response.status) { PreferenceUtil.clearLogin() AppEventBus.post( LoginBindEvent.Login(userId = null, loginType = PreferenceUtil.getLoginType(), isLogin = false, data = null) ) } else { errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "退出登录失败" }) } } } enum class JumpLoginType(val type: Int,val desc: String){ NORMAL(0,"正常登录"), FROM_ADD(1,"添加账号"), FROM_LOGOUT(2,"退出登录") } }