425 lines
16 KiB
Kotlin
425 lines
16 KiB
Kotlin
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,"退出登录")
|
||
}
|
||
} |