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

445 lines
17 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 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<String> = 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<Boolean> = _policyAgreement
fun setIsPolicyAgreement(isAgreement: Boolean) {
_policyAgreement.value = isAgreement
}
// 登录获取结果
val loginResult = mutableStateOf<ResultVo<LoginInfoEntity>?>(null)
// 用户配置获取结果
val userConfigResult = mutableStateOf<ResultVo<UserConfigEntity>?>(null)
// 错误状态
val errorState = mutableStateOf<ErrorBean?>(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<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) {
// 调用 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<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()
}
}
/**
* 拿着支付宝授权码完成登录
* @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,"退出登录")
}
}