parent
19dc61e9af
commit
f78f459616
|
|
@ -4,7 +4,7 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-02-26T05:37:22.871237900Z">
|
||||
<DropdownSelection timestamp="2026-02-27T07:54:46.218482200Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=JRBI89BIE6AI5TG6" />
|
||||
|
|
|
|||
|
|
@ -173,5 +173,6 @@ dependencies {
|
|||
|
||||
implementation(libs.android.cn.oaid) //获取手机设备id
|
||||
implementation(libs.fastaes) //解密
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.32.0")
|
||||
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.img.rabbit
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
|
|
@ -9,14 +10,18 @@ import androidx.compose.animation.core.Animatable
|
|||
import androidx.compose.animation.core.tween
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
|
|
@ -25,6 +30,7 @@ import androidx.compose.foundation.text.ClickableText
|
|||
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.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -52,7 +58,9 @@ import com.img.rabbit.config.Constants.privacyUrl
|
|||
import com.img.rabbit.pages.LoginScreen
|
||||
import com.img.rabbit.pages.LoginScreenType
|
||||
import com.img.rabbit.pages.MainScreen
|
||||
import com.img.rabbit.provider.storage.GlobalStateManager
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil.saveBDVID
|
||||
import com.img.rabbit.utils.ChannelUtils
|
||||
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
|
|
@ -60,38 +68,57 @@ import com.img.rabbit.viewmodel.LoginViewModel
|
|||
import com.img.rabbit.viewmodel.SplashViewModel
|
||||
import com.umeng.analytics.MobclickAgent
|
||||
import com.umeng.commonsdk.UMConfigure
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@SuppressLint("UnrememberedMutableState", "CoroutineCreationDuringComposition")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// 必须在 super.onCreate 之前调用
|
||||
val splashScreen = installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
initUM()
|
||||
|
||||
// 启用Edge-to-Edge模式(沉浸模式)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
var showUpdateDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val splashViewModel: SplashViewModel = viewModel()
|
||||
val generalViewModel: GeneralViewModel = viewModel()
|
||||
val loginViewModel: LoginViewModel = viewModel()
|
||||
var loginViewModel: LoginViewModel = viewModel()
|
||||
val context = LocalContext.current
|
||||
var showSplash by remember { mutableStateOf(false) }
|
||||
var globalLogout by mutableStateOf(GlobalStateManager(context).globalLogoutFlow().collectAsState(initial = false))
|
||||
|
||||
LaunchedEffect(generalViewModel.agreementStatus.value) {
|
||||
if (generalViewModel.agreementStatus.value == true){
|
||||
saveBDVID()
|
||||
//获取服务器时间
|
||||
generalViewModel.getServerTime()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(generalViewModel.serverTime.value) {
|
||||
if (generalViewModel.serverTime.value != null){
|
||||
// 获取用户配置
|
||||
loginViewModel.requestUserConfig()
|
||||
initUM()
|
||||
}
|
||||
}
|
||||
|
||||
//获取服务器时间
|
||||
generalViewModel.getServerTime()
|
||||
// 获取用户配置
|
||||
loginViewModel.requestUserConfig()
|
||||
//初始化微信登录
|
||||
loginViewModel.initWXApi(this)
|
||||
|
||||
//loginViewModel.initWXApi(this)
|
||||
// 设置启动页显示条件
|
||||
splashScreen.setKeepOnScreenCondition {
|
||||
splashViewModel.isLoading.value // 当为 true 时,启动页不消失
|
||||
}
|
||||
|
||||
|
||||
AppTheme {
|
||||
SplashScreenContent{
|
||||
//未同意提示政策弹窗
|
||||
|
|
@ -114,7 +141,18 @@ class MainActivity : ComponentActivity() {
|
|||
showSplash = true
|
||||
}
|
||||
|
||||
if(showSplash){
|
||||
if(showSplash || globalLogout.value == true){
|
||||
// 全局注销,重新登录
|
||||
if(globalLogout.value == true){
|
||||
loginViewModel = viewModel()
|
||||
loginViewModel.requestUserConfig()
|
||||
//loginViewModel.initWXApi(this)
|
||||
}
|
||||
// 初始化全局注销状态为 false
|
||||
GlobalScope.launch {
|
||||
GlobalStateManager(context).storeGlobalLogout(false)
|
||||
}
|
||||
|
||||
val token = PreferenceUtil.getAccessToken()
|
||||
// 未登录,显示登录页
|
||||
if (token.isNullOrEmpty() && !loginViewModel.isLogin.value) {
|
||||
|
|
@ -135,6 +173,18 @@ class MainActivity : ComponentActivity() {
|
|||
MainScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(showUpdateDialog){
|
||||
UpdateDialog(
|
||||
title = "新版本,更新提示",
|
||||
newVersion = "1.0.0",
|
||||
desc = "修复了一些问题,新增了一些功能",
|
||||
url = "https://www.baidu.com",
|
||||
){ state ->
|
||||
showUpdateDialog = state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -418,6 +468,186 @@ private fun PrivacyPolicyScreen(viewModel: LoginViewModel, onAgreementChange: (B
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
private fun UpdateDialog(
|
||||
title: String = "新版本,更新提示",
|
||||
newVersion: String = "1.0.0",
|
||||
desc: String = "修复了一些问题,新增了一些功能",
|
||||
url: String,
|
||||
onStatusChange: (state: Boolean) -> Unit
|
||||
){
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0x80000000))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
onStatusChange(false)
|
||||
},
|
||||
verticalArrangement = Arrangement.Center
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = 36.dp)
|
||||
.background(Color.White, shape = RoundedCornerShape(26.dp))
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//什么都不用做,只是解决点击穿透问题
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_dialog_top_mask1),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 46.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(14.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = newVersion,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = desc,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(16.dp)
|
||||
)
|
||||
|
||||
//切换/退出账号
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 18.dp, end = 18.dp, bottom = 20.dp)
|
||||
) {
|
||||
//取消解绑手机号
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(0x00000000),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = Color(0xFF000000),
|
||||
shape = RoundedCornerShape(359.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 取消解绑手机号
|
||||
onStatusChange(false)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"取消",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(11.dp)
|
||||
)
|
||||
|
||||
|
||||
|
||||
//退出登录
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(0xFF252525),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 确定解绑手机号
|
||||
onStatusChange(false)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"确定",
|
||||
color = Color(0xFFC2FF43),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun MainScreenPreview() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.img.rabbit.bean.local
|
||||
|
||||
import android.net.Uri
|
||||
import com.img.rabbit.bean.response.UploadFileEntity
|
||||
|
||||
data class FileManagerBean(
|
||||
var fileEntity: UploadFileEntity,
|
||||
var uri: Uri? = null
|
||||
)
|
||||
|
|
@ -3,7 +3,7 @@ package com.img.rabbit.bean.response
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class UserEntity {
|
||||
class LoginInfoEntity {
|
||||
val user_id: String = ""
|
||||
val name: String = ""
|
||||
val avater: String = ""
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.img.rabbit.bean.response
|
||||
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
class UploadFileEntity(
|
||||
var id: String = "",
|
||||
var url: String = ""
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.img.rabbit.bean.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
|
||||
@Serializable
|
||||
class UserInfoEntity(
|
||||
val alipayid: String = "",
|
||||
val app_id: String = "",
|
||||
val appleid: String = "",
|
||||
val avater: String = "",
|
||||
val balance: String = "",
|
||||
val client_cid: String = "",
|
||||
val coupon_count: Int = 0,
|
||||
val ip_area: String = "",
|
||||
val ip_area_name: String = "",
|
||||
val name: String = "",
|
||||
val phone: String = "",
|
||||
val role: String = "",
|
||||
val temp: Boolean = false,
|
||||
val unionid: String = "",
|
||||
val user_id: String = "",
|
||||
val vip: Int = 0,
|
||||
val vip_expire: String = "",
|
||||
val vip_expire_time: String = "",
|
||||
val vip_name: String = "",
|
||||
val weixinAppId: String = "",
|
||||
val weixinAppIdType: String = "",
|
||||
val weixinAppOpenId: String = "",
|
||||
val weixinOpenId: String = ""
|
||||
)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.img.rabbit.components
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
|
@ -52,38 +52,30 @@ import androidx.core.content.FileProvider
|
|||
import java.io.File
|
||||
|
||||
// 首先添加ImageUtils的导入
|
||||
import android.graphics.Bitmap.CompressFormat
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil3.compose.AsyncImage
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.utils.ImageUtils
|
||||
import kotlin.apply
|
||||
import kotlin.collections.all
|
||||
import kotlin.collections.minus
|
||||
import kotlin.collections.plus
|
||||
import kotlin.collections.toMutableList
|
||||
import kotlin.let
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ImagePicker(
|
||||
modifier: Modifier = Modifier,
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f,
|
||||
maxCount: Int = 3,
|
||||
imageHeight: Dp = 146.dp,
|
||||
aspectRatio: Float = 747f / 395f,
|
||||
maxCount: Int = 10,
|
||||
addButtonName: String = "添加图片",
|
||||
currentImageUris: List<Uri> = emptyList(),
|
||||
currentImagePaths: List<String> = emptyList(), // 新增参数
|
||||
onImagesUpdated: (uris: List<Uri>, paths: List<String>) -> Unit
|
||||
onImagesUpdated: (currentUri: Uri, uris: List<Uri>) -> Unit,
|
||||
onDeleteUpdated: (currentUri: Uri, uris: List<Uri>) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
// 添加临时文件管理
|
||||
val tempImageUri = remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 记录当前操作场景
|
||||
var currentScene by remember { mutableStateOf<PickerScene?>(null) }
|
||||
// 新增选择对话框
|
||||
var showChoiceDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -103,86 +95,32 @@ fun ImagePicker(
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
result.data?.data?.let { uri ->
|
||||
if (currentImages.size < maxCount) {
|
||||
onImagesUpdated(currentImages + uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 相册选择逻辑
|
||||
val galleryLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
contract = ActivityResultContracts.PickVisualMedia()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
uri?.let { uri ->
|
||||
if (currentImageUris.size < maxCount) {
|
||||
// 保存图片到存储并获取路径
|
||||
val savedPath = ImageUtils.saveUriToStorage(
|
||||
context = context,
|
||||
uri = it,
|
||||
format = CompressFormat.JPEG,
|
||||
quality = 90
|
||||
)
|
||||
// 更新图片列表和路径列表
|
||||
onImagesUpdated(
|
||||
currentImageUris + it,
|
||||
if (savedPath != null) currentImagePaths + savedPath else currentImagePaths
|
||||
)
|
||||
onImagesUpdated( uri, currentImageUris + uri )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拍照逻辑
|
||||
val cameraLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.TakePicture()
|
||||
contract = ActivityResultContracts.TakePicture()
|
||||
) { success ->
|
||||
if (success) {
|
||||
tempImageUri.value?.let { uri ->
|
||||
if (currentImageUris.size < maxCount) {
|
||||
// 保存图片到存储并获取路径
|
||||
val savedPath = ImageUtils.saveUriToStorage(
|
||||
context = context,
|
||||
uri = uri,
|
||||
format = CompressFormat.JPEG,
|
||||
quality = 90
|
||||
)
|
||||
// 更新图片列表和路径列表
|
||||
onImagesUpdated(
|
||||
currentImageUris + uri,
|
||||
if (savedPath != null) currentImagePaths + savedPath else currentImagePaths
|
||||
)
|
||||
onImagesUpdated( uri, currentImageUris + uri )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
// 修改权限回调逻辑
|
||||
if (permissions.all { it.value }) {
|
||||
when (currentScene) {
|
||||
PickerScene.GALLERY -> galleryLauncher.launch("image/*")
|
||||
PickerScene.CAMERA -> {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
File.createTempFile("IMG_", ".jpg", context.externalCacheDir)
|
||||
)
|
||||
tempImageUri.value = uri
|
||||
cameraLauncher.launch(uri)
|
||||
}
|
||||
null -> { /* 无操作 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showChoiceDialog) {
|
||||
AlertDialog(
|
||||
|
|
@ -193,14 +131,9 @@ fun ImagePicker(
|
|||
TextButton(
|
||||
onClick = {
|
||||
showChoiceDialog = false
|
||||
|
||||
currentScene = PickerScene.CAMERA
|
||||
permissionLauncher.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
)
|
||||
val uri = FileProvider.getUriForFile(context,"${context.packageName}.fileProvider",File.createTempFile("IMG_", ".jpg", context.externalCacheDir))
|
||||
tempImageUri.value = uri
|
||||
cameraLauncher.launch(uri)
|
||||
}
|
||||
) { Text(text = "拍照", fontSize = 14.sp) }
|
||||
},
|
||||
|
|
@ -208,13 +141,7 @@ fun ImagePicker(
|
|||
TextButton(
|
||||
onClick = {
|
||||
showChoiceDialog = false
|
||||
|
||||
currentScene = PickerScene.GALLERY
|
||||
permissionLauncher.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
)
|
||||
galleryLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||
}
|
||||
) { Text(text = "相册", fontSize = 14.sp) }
|
||||
}
|
||||
|
|
@ -237,7 +164,7 @@ fun ImagePicker(
|
|||
LazyRow(modifier = modifier.defaultMinSize(minHeight = (imageHeight + 8.dp))) {
|
||||
items(getDisplayImages()) { uri ->
|
||||
Box(
|
||||
Modifier.padding(end = 6.dp, top = 6.dp).background(color = Color(0x80E5E5E7), shape = RoundedCornerShape(8.dp))
|
||||
Modifier.padding(4.dp).background(color = Color(0x80E5E5E7), shape = RoundedCornerShape(8.dp))
|
||||
) {
|
||||
AsyncImage(
|
||||
model = uri,
|
||||
|
|
@ -246,10 +173,7 @@ fun ImagePicker(
|
|||
.height(imageHeight)
|
||||
.aspectRatio(aspectRatio)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
.clickable {
|
||||
previewImageUri = uri
|
||||
}
|
||||
)
|
||||
|
|
@ -259,58 +183,17 @@ fun ImagePicker(
|
|||
32.dp
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_picture_del),
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.aspectRatio(1f) // 设置宽高比为1:1
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 4.dp, end = 4.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
// 找到当前uri在列表中的索引,同时从paths列表中移除对应的路径
|
||||
val uriIndex = currentImageUris.indexOf(uri)
|
||||
val newPaths = if (uriIndex >= 0 && uriIndex < currentImagePaths.size) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(uriIndex) }
|
||||
} else {
|
||||
// 如果uri不在currentImageUris中,可能是从currentImagePaths转换过来的
|
||||
// 尝试通过路径匹配找到对应的索引
|
||||
val pathIndex = currentImagePaths.indexOf(uri.path)
|
||||
if (pathIndex >= 0) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(pathIndex) }
|
||||
} else {
|
||||
currentImagePaths
|
||||
}
|
||||
}
|
||||
onImagesUpdated(currentImageUris - uri, newPaths)
|
||||
.clickable{
|
||||
onDeleteUpdated(uri, currentImageUris - uri)
|
||||
}
|
||||
)
|
||||
/*
|
||||
IconButton(
|
||||
onClick = {
|
||||
// 找到当前uri在列表中的索引,同时从paths列表中移除对应的路径
|
||||
val uriIndex = currentImageUris.indexOf(uri)
|
||||
val newPaths = if (uriIndex >= 0 && uriIndex < currentImagePaths.size) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(uriIndex) }
|
||||
} else {
|
||||
// 如果uri不在currentImageUris中,可能是从currentImagePaths转换过来的
|
||||
// 尝试通过路径匹配找到对应的索引
|
||||
val pathIndex = currentImagePaths.indexOf(uri.path)
|
||||
if (pathIndex >= 0) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(pathIndex) }
|
||||
} else {
|
||||
currentImagePaths
|
||||
}
|
||||
}
|
||||
onImagesUpdated(currentImageUris - uri, newPaths)
|
||||
},
|
||||
modifier = Modifier.align(Alignment.TopEnd)
|
||||
) {
|
||||
Icon(Icons.Default.Close, "删除")
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,17 +224,12 @@ fun ImagePicker(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(
|
||||
onClick: () -> Unit,
|
||||
label: String = "添加图片",
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f
|
||||
) {
|
||||
private fun AddButton(onClick: () -> Unit, label: String = "添加图片") {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.height(imageHeight)
|
||||
.aspectRatio(aspectRatio)
|
||||
.size(100.dp)
|
||||
.padding(4.dp)
|
||||
.border(1.dp, Color(0xFFA5A5A5), RoundedCornerShape(8.dp))
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
|
|
@ -363,8 +241,8 @@ private fun AddButton(
|
|||
|
||||
@Composable
|
||||
private fun AddSingleButton(
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f,
|
||||
imageHeight: Dp = 146.dp,
|
||||
aspectRatio: Float = 747f / 395f,
|
||||
onClick: () -> Unit,
|
||||
label: String = "添加图片"
|
||||
) {
|
||||
|
|
@ -438,6 +316,3 @@ private fun ZoomableImage(uri: Uri, modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加场景枚举
|
||||
enum class PickerScene { GALLERY, CAMERA }
|
||||
|
|
@ -12,6 +12,8 @@ object Constants {
|
|||
const val WechatAppSecret = ""//微信secret
|
||||
const val UmengAppkey = ""//TODO 友盟appKey
|
||||
|
||||
const val AppId = ""//appid
|
||||
|
||||
//解密
|
||||
const val AESDecrypt = "e4rOtnF8tJjtHO7ecZeJHN1rapED5ImB"
|
||||
//加密字符
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ 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.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -107,9 +106,14 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
if (loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token != null) {
|
||||
//登录成功
|
||||
PreferenceUtil.saveAccessToken(loginViewModel.loginState.value?.data?.token)
|
||||
loginViewModel.setLogin(true)
|
||||
// 获取用户信息
|
||||
loginViewModel.requestUserInfo()
|
||||
|
||||
loginViewModel.setLogin(true)
|
||||
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
|
||||
|
||||
//清理loginState
|
||||
loginViewModel.loginState.value = null
|
||||
}else if(loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token == null){
|
||||
//登录失败
|
||||
loginViewModel.setLogin(false)
|
||||
|
|
@ -120,7 +124,11 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
|
||||
var globalWxAuthorization by mutableStateOf(GlobalStateManager(context).globalWxAuthorizationFlow().collectAsState(initial = ""))
|
||||
LaunchedEffect(globalWxAuthorization.value) {
|
||||
globalWxAuthorization.value?.let { loginViewModel.requestWxLogin(it) }
|
||||
if(globalWxAuthorization.value?.isNotEmpty() == true){
|
||||
loginViewModel.requestWxLogin(globalWxAuthorization.value!!)
|
||||
//清理globalWxAuthorization
|
||||
GlobalStateManager(context).storeGlobalWxAuthorization("")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(loginViewModel.authInfoForAlipay.value) {
|
||||
|
|
@ -144,6 +152,8 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
alipayResult.memo ?: "登录失败"
|
||||
}
|
||||
}
|
||||
//清理authInfoForAlipay
|
||||
loginViewModel.authInfoForAlipay.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,9 +170,6 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
.wrapContentHeight()
|
||||
)
|
||||
|
||||
// 顶部栏
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false, showBreak = isVisibilityBreak)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -176,7 +183,7 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
LoginScreenType.LOGIN_WX -> {
|
||||
//微信登录
|
||||
Box(modifier = Modifier.align(Alignment.Center).padding(bottom = 100.dp)){
|
||||
WxLoginScreen(context, loginViewModel)
|
||||
WxLoginScreen(context, loginViewModel, generalViewModel)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -200,7 +207,7 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
.padding(top = 27.dp),
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
){
|
||||
OtherLoginBar(context = context, viewModel = loginViewModel)
|
||||
OtherLoginBar(viewModel = loginViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -221,6 +228,9 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部栏
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false, showBreak = isVisibilityBreak)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -700,8 +710,7 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel, gener
|
|||
loginBtn = loginButton,
|
||||
checkBox = checkbox,
|
||||
privacyTv = agreementTextView,
|
||||
viewModel = viewModel,
|
||||
generalViewModel = generalViewModel
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -713,6 +722,7 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel, gener
|
|||
private fun WxLoginScreen(
|
||||
context: Context,
|
||||
viewModel: LoginViewModel,
|
||||
generalViewModel: GeneralViewModel
|
||||
) {
|
||||
Column {
|
||||
Image(
|
||||
|
|
@ -761,8 +771,8 @@ private fun WxLoginScreen(
|
|||
) {
|
||||
// 启动微信验证,请求登录
|
||||
if (viewModel.isPolicyAgreement.value) {
|
||||
//TODO 打开微信登录
|
||||
viewModel.loginWithWechat(context)
|
||||
//打开微信登录
|
||||
viewModel.loginWithWechat(context, generalViewModel.api)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
|
|
@ -1244,8 +1254,7 @@ private fun OneKeyLoginScreen(context: Context, viewModel: LoginViewModel) {
|
|||
*/
|
||||
|
||||
@Composable
|
||||
private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
||||
val scope = rememberCoroutineScope()
|
||||
private fun OtherLoginBar(viewModel: LoginViewModel) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -1309,18 +1318,6 @@ private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
/*
|
||||
if (viewModel.isPolicyAgreement.value) {
|
||||
//TODO 打开微信登录
|
||||
viewModel.loginWithWechat(context)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"请先同意用户协议和隐私政策",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
*/
|
||||
// 微信登录
|
||||
viewModel.loginScreenType.value = LoginScreenType.LOGIN_WX
|
||||
}
|
||||
|
|
@ -1336,18 +1333,6 @@ private fun OtherLoginBar(context: Context, viewModel: LoginViewModel) {
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
/*
|
||||
if (viewModel.isPolicyAgreement.value) {
|
||||
// 打开支付宝登录
|
||||
viewModel.requestAliPayAuthParam()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"请先同意用户协议和隐私政策",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
*/
|
||||
// 支付宝登录
|
||||
viewModel.loginScreenType.value = LoginScreenType.LOGIN_ALIPAY
|
||||
}
|
||||
|
|
@ -1405,7 +1390,6 @@ private fun oneKeyLogin(
|
|||
checkBox: CheckBox,
|
||||
privacyTv: TextView,
|
||||
viewModel: LoginViewModel,
|
||||
generalViewModel: GeneralViewModel
|
||||
) {
|
||||
val eloginActivityParam = EloginActivityParam()
|
||||
.setActivity(context as Activity)
|
||||
|
|
@ -1426,7 +1410,7 @@ private fun oneKeyLogin(
|
|||
}
|
||||
GYManager.getInstance().eAccountLogin(eloginActivityParam, 5000, object : GyCallBack {
|
||||
override fun onSuccess(response: GYResponse?) {
|
||||
//TODO 登录成功,需要与后端交互
|
||||
// 登录成功,需要与后端交互
|
||||
Log.i("OneKeyLogin", "onSuccess:$response")
|
||||
try {
|
||||
val jsonObject = JSONObject(response?.msg?:"{}")
|
||||
|
|
@ -1439,7 +1423,7 @@ private fun oneKeyLogin(
|
|||
}
|
||||
|
||||
override fun onFailed(p0: GYResponse?) {
|
||||
//TODO 登录失败
|
||||
// 登录失败
|
||||
Log.e("OneKeyLogin", "onFailed:$p0")
|
||||
}
|
||||
})
|
||||
|
|
@ -1464,7 +1448,7 @@ private fun PreviewOneKeyLoginScreen() {
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewWxLoginScreen() {
|
||||
WxLoginScreen(LocalContext.current, viewModel())
|
||||
WxLoginScreen(LocalContext.current, viewModel(), viewModel())
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,24 @@ package com.img.rabbit.pages
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
|
@ -21,15 +34,22 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.screen.HomeScreen
|
||||
import com.img.rabbit.pages.screen.MineScreen
|
||||
|
|
@ -40,8 +60,11 @@ import com.img.rabbit.pages.screen.make.ResizeScreen
|
|||
import com.img.rabbit.pages.screen.mine.setting.AboutScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.AccountBindScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.AccountManagerScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.BindScreen
|
||||
import com.img.rabbit.pages.screen.other.CameraGuideScreen
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.route.ScreenRoute
|
||||
import com.img.rabbit.viewmodel.BindViewModel
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
|
||||
|
|
@ -54,6 +77,7 @@ sealed class TabItem(val title: String, val normalIconRes: Int, val selectedIcon
|
|||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel) {
|
||||
|
||||
val navController = rememberNavController()
|
||||
val networkStatus by generalViewModel.networkStatus.observeAsState(initial = true)
|
||||
val isNavigationBarVisible by generalViewModel.isNavigationBarVisible.observeAsState(initial = true)
|
||||
|
|
@ -134,6 +158,14 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
navController = navController,
|
||||
startDestination = ScreenRoute.Home.route
|
||||
) {
|
||||
// Main页面
|
||||
composable(ScreenRoute.Main.route) {
|
||||
MainScreen(
|
||||
generalViewModel = generalViewModel,
|
||||
loginViewModel = loginViewModel
|
||||
)
|
||||
}
|
||||
|
||||
// Tab页面
|
||||
composable(ScreenRoute.Home.route) {
|
||||
HomeScreen(
|
||||
|
|
@ -177,7 +209,7 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
OnlineServiceScreen(navController = navController)
|
||||
}
|
||||
composable(ScreenRoute.Setting.route) {
|
||||
SettingScreen(navController = navController)
|
||||
SettingScreen(navController = navController, loginViewModel = loginViewModel)
|
||||
}
|
||||
|
||||
// 设置页面(Setting)
|
||||
|
|
@ -200,6 +232,59 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
isVisibilityBreak = true
|
||||
)
|
||||
}
|
||||
composable(
|
||||
route = "login?type={type}",
|
||||
arguments = listOf(
|
||||
navArgument("type") {
|
||||
type = NavType.IntType
|
||||
defaultValue = LoginViewModel.JumpLoginType.NORMAL.type
|
||||
}
|
||||
)
|
||||
) { backStackEntry ->
|
||||
val isVisibilityBreak = when(backStackEntry.arguments?.getInt("type")){
|
||||
LoginViewModel.JumpLoginType.FROM_ADD.type -> {
|
||||
// 添加账号
|
||||
true
|
||||
}
|
||||
LoginViewModel.JumpLoginType.FROM_LOGOUT.type -> {
|
||||
// 退出登录
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
LoginScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel,
|
||||
loginViewModel = loginViewModel,
|
||||
isVisibilityBreak = isVisibilityBreak
|
||||
)
|
||||
}
|
||||
|
||||
// 绑定页面(Bind)
|
||||
/*
|
||||
composable(ScreenRoute.Bind.route) {
|
||||
BindScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel,
|
||||
bindType = BindViewModel.BindType.FROM_PHONE.type
|
||||
)
|
||||
}
|
||||
*/
|
||||
composable(
|
||||
route = "bind?type={type}",
|
||||
arguments = listOf(
|
||||
navArgument("type") {
|
||||
type = NavType.IntType
|
||||
defaultValue = BindViewModel.BindType.FROM_PHONE.type
|
||||
}
|
||||
)
|
||||
) { backStackEntry ->
|
||||
BindScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel,
|
||||
bindType = backStackEntry.arguments?.getInt("type")?:BindViewModel.BindType.FROM_PHONE.type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据选中的Tab切换导航路由
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.img.rabbit.pages.screen
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -14,7 +15,6 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.offset
|
||||
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.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -38,23 +38,26 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil3.compose.AsyncImage
|
||||
import com.img.rabbit.BuildConfig
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.utils.AppUpdate
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
|
||||
@Composable
|
||||
fun MineScreen(
|
||||
navController: NavHostController,
|
||||
generalViewModel: GeneralViewModel,
|
||||
) {
|
||||
val TAG = "Rabbit_Mine"
|
||||
val context = LocalContext.current
|
||||
val vipMember by remember { mutableStateOf(false) }
|
||||
val userInfo by remember { mutableStateOf(PreferenceUtil.loginUserInfo()) }
|
||||
val userInfo by remember { mutableStateOf(PreferenceUtil.getUserInfo()) }
|
||||
|
||||
// 监听返回事件
|
||||
val currentBackStackEntry = navController.currentBackStackEntry
|
||||
|
|
@ -69,7 +72,9 @@ fun MineScreen(
|
|||
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF9F9F9))
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFF9F9F9))
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_top_mask),
|
||||
|
|
@ -129,11 +134,11 @@ fun MineScreen(
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
if(userInfo == null){
|
||||
if (userInfo == null) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转登录页面
|
||||
navController.navigate("login")
|
||||
navController.navigate("login?type=${LoginViewModel.JumpLoginType.NORMAL.type}")
|
||||
} else {
|
||||
//TODO 已登录,跳转个人信息页面
|
||||
//navController.navigate("userInfo")
|
||||
|
|
@ -153,14 +158,19 @@ fun MineScreen(
|
|||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
) {
|
||||
// 点击复制ID
|
||||
if (userInfo != null) {
|
||||
val clipboardManager = android.content.Context.CLIPBOARD_SERVICE
|
||||
val clipboard = context.getSystemService(clipboardManager) as android.content.ClipboardManager
|
||||
val clip = android.content.ClipData.newPlainText("User ID", userInfo?.user_id)
|
||||
val clipboard =
|
||||
context.getSystemService(clipboardManager) as android.content.ClipboardManager
|
||||
val clip = android.content.ClipData.newPlainText(
|
||||
"User ID",
|
||||
userInfo?.user_id
|
||||
)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(context, "已复制到剪贴板", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, "已复制到剪贴板", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
){
|
||||
|
|
@ -334,9 +344,13 @@ fun MineScreen(
|
|||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.5.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -385,9 +399,13 @@ fun MineScreen(
|
|||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.5.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -397,8 +415,19 @@ fun MineScreen(
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 处理点击事件
|
||||
Toast.makeText(context, "版本更新", Toast.LENGTH_SHORT).show()
|
||||
//更新检测
|
||||
val versionConfig =
|
||||
PreferenceUtil.getUserConfig()?.config?.versionEntity
|
||||
|
||||
AppUpdate.checkUpdate(versionConfig) { isUpdate, tips ->
|
||||
if (isUpdate) {
|
||||
//提示执行更新
|
||||
//startUpdate(result, fragment)
|
||||
Log.i(TAG, "checkUpdate: 有新版本, tips = $tips")
|
||||
} else {
|
||||
Toast.makeText(context, tips, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -424,19 +453,36 @@ fun MineScreen(
|
|||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp) // 改为end padding
|
||||
)
|
||||
Row {
|
||||
Text(
|
||||
text = "V${BuildConfig.VERSION_NAME}",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.5.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.img.rabbit.pages.screen.mine
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -18,15 +20,15 @@ import androidx.compose.foundation.layout.wrapContentSize
|
|||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -34,12 +36,24 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.img.rabbit.components.ImagePicker
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.viewmodel.FeedbackViewModel
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
@Composable
|
||||
fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewModel = viewModel()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(viewModel.errorState.value) {
|
||||
if(viewModel.errorState.value != null){
|
||||
Toast.makeText(context, viewModel.errorState.value?.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
viewModel.errorState.value = null
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -57,7 +71,9 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
) {
|
||||
// 意见反馈类型
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "*",
|
||||
|
|
@ -74,7 +90,10 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 12.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -172,10 +191,15 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
}
|
||||
//补充反馈内容
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "*",
|
||||
|
|
@ -255,10 +279,15 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
}
|
||||
//提供相关图片
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "请提供相关问题的图片",
|
||||
|
|
@ -271,35 +300,48 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
color = Color(0xFFAAAAAA),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight().padding(top = 12.dp)
|
||||
.wrapContentHeight()
|
||||
.padding(top = 12.dp)
|
||||
) {
|
||||
ImagePicker(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
imageHeight = 100.dp,
|
||||
aspectRatio = 100f / 100f,
|
||||
maxCount = 3,
|
||||
addButtonName = "添加图片",
|
||||
currentImageUris = viewModel.currentImageUris,
|
||||
currentImagePaths = emptyList(), // 新增参数
|
||||
onImagesUpdated = { uris, _ ->
|
||||
viewModel.setCurrentImageUris(uris)
|
||||
onImagesUpdated = { uri, uris ->
|
||||
viewModel.setCurrentImageUris(uris)
|
||||
viewModel.uploadImage(context, uri)
|
||||
},
|
||||
onDeleteUpdated = { uri, uris ->
|
||||
viewModel.setCurrentImageUris(uris)
|
||||
viewModel.deleteImage(uri)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//联系方式
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "联系方式",
|
||||
|
|
@ -312,7 +354,8 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
color = Color(0xFFAAAAAA),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
|
|
@ -342,7 +385,12 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(Color.Transparent)
|
||||
.padding(start = 18.dp, top = 10.dp, end = 18.dp, bottom = 10.dp),
|
||||
.padding(
|
||||
start = 18.dp,
|
||||
top = 10.dp,
|
||||
end = 18.dp,
|
||||
bottom = 10.dp
|
||||
),
|
||||
textStyle = androidx.compose.ui.text.TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 14.sp
|
||||
|
|
@ -390,8 +438,21 @@ fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewMode
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 提交反馈
|
||||
viewModel.submitFeedback()
|
||||
if (viewModel.feedbackMore.isEmpty()) {
|
||||
Toast.makeText(context, "请填写详细问题或意见...", Toast.LENGTH_SHORT).show()
|
||||
return@clickable
|
||||
}
|
||||
// 提交反馈
|
||||
val jsonObject = JsonObject()
|
||||
jsonObject.addProperty("type", viewModel.feedbackType.value)
|
||||
jsonObject.addProperty("content", viewModel.feedbackMore)
|
||||
jsonObject.addProperty("contact", viewModel.feedbackContact)
|
||||
val imageArray = JsonArray()
|
||||
viewModel.uploadFiles.forEach { fileEntity ->
|
||||
imageArray.add(fileEntity.fileEntity.url)
|
||||
}
|
||||
jsonObject.add("images", imageArray)
|
||||
viewModel.submitFeedback(jsonObject.toString().toRequestBody())
|
||||
}
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -19,25 +19,61 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
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.shadow
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.AppDataStoreUtils
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
|
||||
@Composable
|
||||
fun SettingScreen(navController: NavHostController) {
|
||||
fun SettingScreen(navController: NavHostController, loginViewModel: LoginViewModel) {
|
||||
val context = LocalContext.current
|
||||
var cacheDataSize by remember { mutableStateOf("正在计算...") }
|
||||
/*
|
||||
LaunchedEffect(loginViewModel.logoutState.value) {
|
||||
if(loginViewModel.logoutState.value?.status == true){
|
||||
// 执行用户数据清除
|
||||
PreferenceUtil.clearLogin()
|
||||
loginViewModel.setLogin(false)
|
||||
// 跳转登录页面
|
||||
loginViewModel.restViewModel.intValue = 1
|
||||
|
||||
loginViewModel.logoutState.value = null
|
||||
|
||||
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_NORMAL
|
||||
navController.navigate("login?type=${LoginViewModel.JumpLoginType.FROM_LOGOUT.type}") {
|
||||
// 清除所有页面,包括登录页
|
||||
popUpTo(navController.graph.startDestinationId) { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
AppDataStoreUtils.getAppStorageStats(context){ _, _, _, totalDataCacheSize ->
|
||||
cacheDataSize = totalDataCacheSize
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -47,7 +83,7 @@ fun SettingScreen(navController: NavHostController) {
|
|||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
// 设置内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
|
|
@ -72,7 +108,8 @@ fun SettingScreen(navController: NavHostController) {
|
|||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转页面
|
||||
//navController.navigate("feedback")
|
||||
//navController.navigate("")
|
||||
AppDataStoreUtils.openAppSettings(context)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -94,7 +131,7 @@ fun SettingScreen(navController: NavHostController) {
|
|||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
"12MB",
|
||||
cacheDataSize,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
|
|
@ -292,7 +329,8 @@ fun SettingScreen(navController: NavHostController) {
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 退出登录
|
||||
// 退出登录
|
||||
loginViewModel.requestLogout(context)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -316,5 +354,5 @@ fun SettingScreen(navController: NavHostController) {
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewSettingScreen(){
|
||||
SettingScreen(navController = rememberNavController())
|
||||
SettingScreen(navController = rememberNavController(), loginViewModel = viewModel())
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.img.rabbit.pages.screen.mine.setting
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
|
@ -19,11 +20,13 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
|||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -31,21 +34,46 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.shadow
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.viewmodel.AccountBindViewModel
|
||||
import com.img.rabbit.viewmodel.BindViewModel
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@Composable
|
||||
fun AccountBindScreen(navController: NavHostController) {
|
||||
val showDialogStatus = mutableStateOf(false)
|
||||
fun AccountBindScreen(navController: NavHostController, viewModel: AccountBindViewModel = viewModel()) {
|
||||
val context = LocalContext.current
|
||||
/**
|
||||
* 0:m默认未绑定,1:已绑定手机号(去解绑),2:已绑定微信(去解绑)
|
||||
*/
|
||||
val showDialogStatus = mutableIntStateOf(0)
|
||||
val userInfo by remember { mutableStateOf(PreferenceUtil.getUserInfo()) }
|
||||
|
||||
|
||||
LaunchedEffect(viewModel.unBindState.value) {
|
||||
if(viewModel.unBindState.value != null){
|
||||
Toast.makeText(context, "解绑成功!", Toast.LENGTH_SHORT).show()
|
||||
viewModel.unBindState.value = null
|
||||
}
|
||||
}
|
||||
LaunchedEffect(viewModel.errorState.value) {
|
||||
if(viewModel.errorState.value != null){
|
||||
Toast.makeText(context, "解绑失败...", Toast.LENGTH_SHORT).show()
|
||||
viewModel.errorState.value = null
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -56,9 +84,11 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
TitleBar(navController = navController, paddingValues = it, title = "账号绑定")
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(start = 17.dp, end = 17.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
// 设置内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
|
|
@ -82,8 +112,12 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 跳转绑定与解绑手机号
|
||||
showDialogStatus.value = !showDialogStatus.value
|
||||
// 跳转绑定与解绑手机号
|
||||
if (userInfo?.phone.isNullOrEmpty()) {
|
||||
navController.navigate("bind?type=${BindViewModel.BindType.FROM_PHONE.type}")
|
||||
} else {
|
||||
showDialogStatus.intValue = 1
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -104,8 +138,14 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
val phone = if(userInfo?.phone.isNullOrEmpty()){
|
||||
"去绑定"
|
||||
}else{
|
||||
"+86 ${userInfo?.phone?.replaceRange(3, 7, "****") ?: ""}"
|
||||
}
|
||||
|
||||
Text(
|
||||
"+86 123****123",
|
||||
phone,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
|
|
@ -126,9 +166,13 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.5.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -139,6 +183,11 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 跳转绑定或解绑微信
|
||||
if (userInfo?.weixinAppId.isNullOrEmpty()) {
|
||||
navController.navigate("bind?type=${BindViewModel.BindType.FROM_WX.type}")
|
||||
} else {
|
||||
showDialogStatus.intValue = 2
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -156,13 +205,16 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
val wxBind = if(userInfo?.weixinAppId.isNullOrEmpty()){
|
||||
"去绑定"
|
||||
}else{
|
||||
"已绑定,去解绑"
|
||||
}
|
||||
Text(
|
||||
"去绑定",
|
||||
wxBind,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
|
|
@ -186,11 +238,21 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
}
|
||||
}
|
||||
|
||||
if(showDialogStatus.value){
|
||||
if(showDialogStatus.intValue > 0){
|
||||
UnBindPhoneDialog(
|
||||
showDialogStatus = showDialogStatus,
|
||||
onStatusChange = {
|
||||
showDialogStatus.value = it
|
||||
onStatusChange = { stateVal, typeVal, isCancel ->
|
||||
if(!isCancel){
|
||||
if(typeVal == 1){
|
||||
// 解绑手机号
|
||||
PreferenceUtil.getUserInfo()?.phone?.let { phone -> viewModel.requestUnBindByPhone(phone) }
|
||||
}else{
|
||||
// 解绑微信
|
||||
PreferenceUtil.getWxCode()?.let { wechatCode -> viewModel.requestUnBindByWechat(wechatCode) }
|
||||
}
|
||||
}
|
||||
|
||||
showDialogStatus.intValue = stateVal
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -200,9 +262,25 @@ fun AccountBindScreen(navController: NavHostController) {
|
|||
|
||||
@Composable
|
||||
private fun UnBindPhoneDialog(
|
||||
showDialogStatus: MutableState<Boolean>,
|
||||
onStatusChange: (Boolean) -> Unit
|
||||
showDialogStatus: MutableState<Int>,
|
||||
onStatusChange: (state:Int, type:Int, isCancel:Boolean) -> Unit
|
||||
){
|
||||
val title = if(showDialogStatus.value == 1){
|
||||
"解开绑定的手机号?"
|
||||
}else{
|
||||
"确定解除绑定的微信?"
|
||||
}
|
||||
val content = if(showDialogStatus.value == 1){
|
||||
"当前绑定的手机号码为"
|
||||
}else{
|
||||
"解除后将无法使用该微信登录此账号,请谨慎操作!"
|
||||
}
|
||||
val content1 = if(showDialogStatus.value == 1){
|
||||
"+86 ${PreferenceUtil.getUserInfo()?.phone?.replaceRange(3, 7, "****") ?: ""}"
|
||||
}else{
|
||||
""
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -210,8 +288,8 @@ private fun UnBindPhoneDialog(
|
|||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
showDialogStatus.value = false
|
||||
) {
|
||||
onStatusChange(0, showDialogStatus.value, true)
|
||||
},
|
||||
verticalArrangement = Arrangement.Center
|
||||
){
|
||||
|
|
@ -225,7 +303,7 @@ private fun UnBindPhoneDialog(
|
|||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
) {
|
||||
//什么都不用做,只是解决点击穿透问题
|
||||
}
|
||||
) {
|
||||
|
|
@ -244,7 +322,7 @@ private fun UnBindPhoneDialog(
|
|||
.padding(top = 46.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "解开绑定的手机号?",
|
||||
text = title,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
|
|
@ -260,7 +338,7 @@ private fun UnBindPhoneDialog(
|
|||
)
|
||||
|
||||
Text(
|
||||
text = "当前绑定的手机号码为",
|
||||
text = content,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
|
|
@ -270,7 +348,7 @@ private fun UnBindPhoneDialog(
|
|||
)
|
||||
|
||||
Text(
|
||||
text = "+86 123****456",
|
||||
text = content1,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
|
|
@ -288,7 +366,9 @@ private fun UnBindPhoneDialog(
|
|||
|
||||
//切换/退出账号
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 18.dp, end = 18.dp, bottom = 20.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 18.dp, end = 18.dp, bottom = 20.dp)
|
||||
) {
|
||||
//取消解绑手机号
|
||||
Box(
|
||||
|
|
@ -300,13 +380,17 @@ private fun UnBindPhoneDialog(
|
|||
Color(0x00000000),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.border(width = 1.dp, color = Color(0xFF000000), shape = RoundedCornerShape(359.dp))
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = Color(0xFF000000),
|
||||
shape = RoundedCornerShape(359.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 取消解绑手机号
|
||||
showDialogStatus.value = false
|
||||
// 取消解绑手机号
|
||||
onStatusChange(0, showDialogStatus.value, true)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -344,7 +428,7 @@ private fun UnBindPhoneDialog(
|
|||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 确定解绑手机号
|
||||
showDialogStatus.value = false
|
||||
onStatusChange(0, showDialogStatus.value, false)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -376,5 +460,5 @@ private fun PreviewAccountBindScreen(){
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewUnBindPhoneDialog(){
|
||||
UnBindPhoneDialog(showDialogStatus = mutableStateOf(true), onStatusChange = {})
|
||||
UnBindPhoneDialog(showDialogStatus = mutableIntStateOf(0), onStatusChange = { _, _, _->})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ 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.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -35,15 +36,18 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.bean.local.UserInfo
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.viewmodel.AccountManagerViewModel
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
|
||||
@Composable
|
||||
fun AccountManagerScreen(navController: NavHostController) {
|
||||
fun AccountManagerScreen(navController: NavHostController, viewModel: AccountManagerViewModel = viewModel()) {
|
||||
val userList by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
|
|
@ -53,6 +57,10 @@ fun AccountManagerScreen(navController: NavHostController) {
|
|||
)
|
||||
)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.requestAccount()
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -225,7 +233,7 @@ private fun AddItem(navController: NavController){
|
|||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 添加账号
|
||||
navController.navigate("login")
|
||||
navController.navigate("login?type=${LoginViewModel.JumpLoginType.FROM_ADD.type}")
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -49,7 +49,9 @@ fun TitleBar(navController: NavController?, paddingValues: PaddingValues, title:
|
|||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { navController?.popBackStack() }
|
||||
) {
|
||||
navController?.popBackStack()
|
||||
}
|
||||
.padding(end = 26.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -18,6 +19,7 @@ class GlobalStateManager(
|
|||
companion object {
|
||||
private val GLOBAL_LOADING = booleanPreferencesKey("global_loading")
|
||||
private val GLOBAL_WX_AUTHORIZATION = stringPreferencesKey("global_wx_authorization")
|
||||
private val GLOBAL_LOGOUT = booleanPreferencesKey("global_logout")
|
||||
}
|
||||
|
||||
suspend fun storeGlobalLoading(value: Boolean) {
|
||||
|
|
@ -46,4 +48,17 @@ class GlobalStateManager(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun storeGlobalLogout(value: Boolean) {
|
||||
context.storeData.edit { preferences ->
|
||||
preferences[GLOBAL_LOGOUT] = value
|
||||
}
|
||||
}
|
||||
fun globalLogoutFlow(): Flow<Boolean?> {
|
||||
return context.storeData.data.map {
|
||||
preferences ->
|
||||
preferences[GLOBAL_LOGOUT]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
package com.img.rabbit.provider.storage
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.img.rabbit.bean.response.UserEntity
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.img.rabbit.bean.response.UserConfigEntity
|
||||
import com.img.rabbit.bean.response.LoginInfoEntity
|
||||
import com.img.rabbit.bean.response.UserInfoEntity
|
||||
import com.img.rabbit.provider.utils.HeadParamUtils.applicationContext
|
||||
import com.img.rabbit.utils.MMKVUtils
|
||||
import com.img.rabbit.utils.MMKVUtils.mmkv
|
||||
import com.img.rabbit.utils.appwalle.ChannelReader
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* SharedPreferences工具类,用于简化数据持久化操作
|
||||
|
|
@ -11,76 +18,98 @@ import com.tencent.mmkv.MMKV
|
|||
object PreferenceUtil {
|
||||
private const val KEY_ACCESS_TOKEN = "access_token"
|
||||
private const val KEY_X_TOKEN = "x_token"
|
||||
|
||||
private const val KEY_LOGIN_INFO = "login_info"
|
||||
private const val KEY_DB_VID = "bd_vid"
|
||||
private const val KEY_USER_CONFIG = "user_config"
|
||||
private const val KEY_WX_CODE = "wx_code"
|
||||
private const val KEY_USER_INFO = "user_info"
|
||||
|
||||
|
||||
|
||||
// Gson实例
|
||||
private val gson: Gson = GsonBuilder().create()
|
||||
|
||||
val kv by lazy { MMKV.defaultMMKV() }
|
||||
|
||||
|
||||
//服务器时间和本地时间的偏移量
|
||||
private var timeDiff = 0L
|
||||
|
||||
|
||||
// Gson实例
|
||||
private val gson: Gson = GsonBuilder().create()
|
||||
|
||||
|
||||
/**
|
||||
* 保存AccessToken
|
||||
*/
|
||||
fun saveAccessToken(token: String?) {
|
||||
kv.encode(KEY_ACCESS_TOKEN, token)
|
||||
mmkv.encode(KEY_ACCESS_TOKEN, token)
|
||||
mmkv.encode(KEY_X_TOKEN, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存的AccessToken
|
||||
*/
|
||||
fun getAccessToken(): String? {
|
||||
return kv.decodeString(KEY_ACCESS_TOKEN, null)
|
||||
return mmkv.decodeString(KEY_ACCESS_TOKEN, null)
|
||||
}
|
||||
|
||||
fun saveXToken(token: String?) {
|
||||
kv.encode(KEY_X_TOKEN, token)
|
||||
mmkv.encode(KEY_X_TOKEN, token)
|
||||
}
|
||||
|
||||
fun getXToken(): String? {
|
||||
return kv.decodeString(KEY_X_TOKEN, null)
|
||||
return mmkv.decodeString(KEY_X_TOKEN, null)
|
||||
}
|
||||
|
||||
fun saveUserConfig(config: UserConfigEntity) {
|
||||
val resultJson = Gson().toJson(config)
|
||||
mmkv.encode(KEY_USER_CONFIG, resultJson)
|
||||
}
|
||||
|
||||
fun getUserConfig(): UserConfigEntity? {
|
||||
return gson.fromJson(mmkv.decodeString(KEY_USER_CONFIG, "{}"), UserConfigEntity::class.java)
|
||||
}
|
||||
|
||||
|
||||
fun getUserInfos(): MutableList<UserEntity>? {
|
||||
fun loginInfo(): LoginInfoEntity?{
|
||||
return getLoginInfos()?.find { it.isLogin }
|
||||
}
|
||||
|
||||
fun saveLoginInfo(loginInfoEntity: LoginInfoEntity) {
|
||||
val loginInfos = getLoginInfos()?: mutableListOf()
|
||||
val isContain = loginInfos.find { it.user_id == loginInfoEntity.user_id } != null
|
||||
if(!isContain){
|
||||
loginInfos.add(loginInfoEntity)
|
||||
}
|
||||
|
||||
mmkv.encode(KEY_LOGIN_INFO, gson.toJson(loginInfos))
|
||||
}
|
||||
|
||||
fun removeLoginInfo(loginInfoEntity: LoginInfoEntity) {
|
||||
val loginInfos = getLoginInfos()?: mutableListOf()
|
||||
loginInfos.removeIf { it.user_id == loginInfoEntity.user_id }
|
||||
mmkv.encode(KEY_LOGIN_INFO, gson.toJson(loginInfos))
|
||||
}
|
||||
|
||||
fun getLoginInfos(): MutableList<LoginInfoEntity>? {
|
||||
/**
|
||||
*[{"user_id":"25","name":"手机用户0253","avater":"https://cdn.batiao8.com/jietutu/logo.png","token":"45731e27-d101-4ec3-975c-e665cf86a579"},{"user_id":"25","name":"手机用户0253","avater":"https://cdn.batiao8.com/jietutu/logo.png","token":"45731e27-d101-4ec3-975c-e665cf86a579"}]
|
||||
*/
|
||||
return gson.fromJson(kv.decodeString(KEY_USER_INFO, "[]"), Array<UserEntity>::class.java)?.toMutableList()
|
||||
return gson.fromJson(mmkv.decodeString(KEY_LOGIN_INFO, "[]"), Array<LoginInfoEntity>::class.java)?.toMutableList()
|
||||
}
|
||||
|
||||
fun loginUserInfo(): UserEntity?{
|
||||
return getUserInfos()?.find { it.isLogin }
|
||||
fun saveUserInfo(userInfoEntity: UserInfoEntity) {
|
||||
mmkv.encode(KEY_USER_INFO, gson.toJson(userInfoEntity))
|
||||
}
|
||||
|
||||
fun saveUserInfo(userEntity: UserEntity) {
|
||||
val userInfos = getUserInfos()?: mutableListOf()
|
||||
val isContain = userInfos.find { it.user_id == userEntity.user_id } != null
|
||||
if(!isContain){
|
||||
userInfos.add(userEntity)
|
||||
}
|
||||
|
||||
kv.encode(KEY_USER_INFO, gson.toJson(userInfos))
|
||||
fun getUserInfo(): UserInfoEntity? {
|
||||
return gson.fromJson(mmkv.decodeString(KEY_USER_INFO, "{}"), UserInfoEntity::class.java)
|
||||
}
|
||||
|
||||
fun removeUserInfo(userEntity: UserEntity) {
|
||||
val userInfos = getUserInfos()?: mutableListOf()
|
||||
userInfos.removeIf { it.user_id == userEntity.user_id }
|
||||
kv.encode(KEY_USER_INFO, gson.toJson(userInfos))
|
||||
|
||||
fun saveWxCode(code: String) {
|
||||
mmkv.encode(KEY_WX_CODE, code)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有数据
|
||||
*/
|
||||
fun clearAll() {
|
||||
kv.clearAll()
|
||||
fun getWxCode(): String? {
|
||||
return mmkv.decodeString(KEY_WX_CODE, null)
|
||||
}
|
||||
|
||||
//真实的服务器时间
|
||||
|
|
@ -90,4 +119,50 @@ object PreferenceUtil {
|
|||
fun setTimeDiff(timeDiff: Long) {
|
||||
this.timeDiff = timeDiff
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存百度归因bd_vid
|
||||
*/
|
||||
fun saveBDVID() {
|
||||
try {
|
||||
val str = applicationContext?.let { ChannelReader.get(it) }
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
val jsonObj = JSONObject(str)
|
||||
jsonObj.getString("bd_vid").let {
|
||||
MMKVUtils.put("bd_vid", it)
|
||||
}
|
||||
} else {
|
||||
MMKVUtils.put("bd_vid", "")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取百度归因bd_vid
|
||||
*/
|
||||
fun getBDVID(): String {
|
||||
return MMKVUtils.getString(KEY_DB_VID) ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录时,清除用户信息
|
||||
*/
|
||||
fun clearLogin(){
|
||||
mmkv.remove(KEY_ACCESS_TOKEN)
|
||||
mmkv.remove(KEY_X_TOKEN)
|
||||
mmkv.remove(KEY_LOGIN_INFO)
|
||||
mmkv.remove(KEY_USER_INFO)
|
||||
mmkv.remove(KEY_WX_CODE)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清除所有数据
|
||||
*/
|
||||
fun clearAll() {
|
||||
mmkv.clearAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.img.rabbit.provider.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.img.rabbit.R
|
||||
import okhttp3.internal.platform.ContextAwarePlatform
|
||||
import okhttp3.internal.platform.Platform
|
||||
|
||||
object HeadParamUtils {
|
||||
var applicationContext: Context?
|
||||
get() = (Platform.get() as? ContextAwarePlatform)?.applicationContext
|
||||
set(value) {
|
||||
(Platform.get() as? ContextAwarePlatform)?.applicationContext = value
|
||||
}
|
||||
|
||||
fun getAppVersionName(): String? {
|
||||
return try {
|
||||
val pm = applicationContext?.packageManager
|
||||
val pi = applicationContext?.packageName?.let { pm?.getPackageInfo(it, 0) }
|
||||
pi?.versionName
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
fun getAppName(): String? {
|
||||
return applicationContext?.resources?.getString(R.string.app_name)
|
||||
}
|
||||
|
||||
fun buildVersionNo(): Int? {
|
||||
return try {
|
||||
val pm = applicationContext?.packageManager
|
||||
val pi = applicationContext?.packageName?.let { pm?.getPackageInfo(it, 0) }
|
||||
pi?.versionCode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,15 @@
|
|||
package com.img.rabbit.provider.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.github.gzuliyujiang.oaid.DeviceIdentifier
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.BuildConfig
|
||||
import com.img.rabbit.config.Constants
|
||||
import com.img.rabbit.config.Constants.LOG_REQUEST
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil.getBDVID
|
||||
import com.img.rabbit.provider.utils.HeadParamUtils.applicationContext
|
||||
import com.img.rabbit.provider.utils.HeadParamUtils.getAppVersionName
|
||||
import com.img.rabbit.utils.ChannelUtils
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
|
|
@ -15,10 +23,32 @@ class HeaderInterceptor : Interceptor {
|
|||
val mBuilder = original.newBuilder()
|
||||
val accessToken = PreferenceUtil.getXToken()?:""
|
||||
mBuilder.header("x-token", accessToken)
|
||||
mBuilder.header("x-version", getAppVersionName()?:"")
|
||||
mBuilder.header("x-platform", "android")
|
||||
mBuilder.header("x-device-id", DeviceIdentifier.getAndroidID(applicationContext))
|
||||
mBuilder.header("x-mobile-brand", android.os.Build.BRAND)
|
||||
mBuilder.header("x-mobile-model", android.os.Build.MODEL)
|
||||
mBuilder.header("x-package", BuildConfig.APPLICATION_ID)
|
||||
mBuilder.header("x-base-version", getAppVersionName()?:"")
|
||||
mBuilder.header("x-channel", "rabbit_${ChannelUtils.getChannel(applicationContext)}")
|
||||
mBuilder.header("x-click-id", getBDVID())
|
||||
mBuilder.header("x-app-id", Constants.AppId)
|
||||
|
||||
val stringBuilder = StringBuilder()
|
||||
stringBuilder.append("-------------> 📤 header start<-------------\n")
|
||||
stringBuilder.append("│ x-token = $accessToken\n")
|
||||
stringBuilder.append("│ x-version = ${getAppVersionName()}\n")
|
||||
stringBuilder.append("│ x-device-id = ${DeviceIdentifier.getAndroidID(applicationContext)}\n")
|
||||
stringBuilder.append("│ x-mobile-brand = ${android.os.Build.BRAND}\n")
|
||||
stringBuilder.append("│ x-mobile-model = ${android.os.Build.MODEL}\n")
|
||||
stringBuilder.append("│ x-base-version = ${getAppVersionName()}\n")
|
||||
stringBuilder.append("│ x-channel = rabbit_${ChannelUtils.getChannel(applicationContext)}\n")
|
||||
stringBuilder.append("│ x-click-id = ${getBDVID()}\n")
|
||||
stringBuilder.append("│ x-package = ${BuildConfig.APPLICATION_ID}\n")
|
||||
stringBuilder.append("-------------> header end <-------------")
|
||||
|
||||
Log.i(LOG_REQUEST, stringBuilder.toString())
|
||||
|
||||
|
||||
val request = mBuilder
|
||||
.method(original.method, original.body)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.img.rabbit.route
|
|||
|
||||
// 定义导航路由
|
||||
sealed class ScreenRoute(val route: String) {
|
||||
//Main页面
|
||||
object Main : ScreenRoute("main")
|
||||
//Tab页面
|
||||
object Home : ScreenRoute("home")
|
||||
object Mine : ScreenRoute("mine")
|
||||
|
|
@ -26,4 +28,5 @@ sealed class ScreenRoute(val route: String) {
|
|||
object BindAccount : ScreenRoute("bindAccount")
|
||||
object ManagerAccount : ScreenRoute("managerAccount")
|
||||
object AboutMine : ScreenRoute("aboutMine")
|
||||
object Bind : ScreenRoute("bind")
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.usage.StorageStatsManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.Settings
|
||||
import android.text.format.Formatter
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
|
|
@ -8,6 +16,8 @@ import androidx.datastore.preferences.core.edit
|
|||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
// 创建DataStore实例
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
|
||||
|
|
@ -46,3 +56,98 @@ class AppDataStore(private val context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AppDataStoreUtils {
|
||||
fun getAppStorageStats(context: Context,onResult:(appSize:String,cacheSize:String,dataSize:String,totalDataCacheSize:String)->Unit) {
|
||||
var cacheSize = "0MB"
|
||||
var dataSize = "0MB"
|
||||
var totalDataCacheSize = "0MB"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val storageStatsManager = context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
|
||||
|
||||
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||
|
||||
// 获取应用所在的存储卷 UUID (通常是内部存储)
|
||||
val uuid: UUID = storageManager.getUuidForPath(context.filesDir)
|
||||
|
||||
// 查询当前应用的统计信息
|
||||
val stats = storageStatsManager.queryStatsForUid(uuid, context.applicationInfo.uid)
|
||||
|
||||
// 格式化为易读字符串 (如 10.5 MB)
|
||||
//val appSize = Formatter.formatFileSize(context, stats.appBytes) // 应用本身 (APK等)
|
||||
dataSize = Formatter.formatFileSize(context, stats.dataBytes) // 用户数据 (包含缓存)
|
||||
cacheSize = Formatter.formatFileSize(context, stats.cacheBytes) // 仅缓存数据
|
||||
totalDataCacheSize = Formatter.formatFileSize(context, stats.dataBytes + stats.cacheBytes) // 数据缓存 (如数据库缓存)
|
||||
}else{
|
||||
// 1. 缓存大小 (Cache): 包括内部缓存和外部缓存
|
||||
val internalCache = getFolderSize(context.cacheDir)
|
||||
val externalCache = getFolderSize(context.externalCacheDir)
|
||||
val totalCache = internalCache + externalCache
|
||||
|
||||
// 2. 数据大小 (Data): 通常指 files、databases、shared_prefs 等
|
||||
val filesDirSize = getFolderSize(context.filesDir)
|
||||
val dbDir = context.getDatabasePath("dummy").parentFile // 获取数据库根目录
|
||||
val databasesSize = getFolderSize(dbDir)
|
||||
val prefsSize = getFolderSize(File(context.filesDir.parent, "shared_prefs"))
|
||||
|
||||
val totalUserData = filesDirSize + databasesSize + prefsSize
|
||||
|
||||
// 格式化显示
|
||||
cacheSize = Formatter.formatFileSize(context, totalCache)
|
||||
dataSize = Formatter.formatFileSize(context, totalUserData)
|
||||
totalDataCacheSize = Formatter.formatFileSize(context, totalCache + totalUserData)
|
||||
}
|
||||
|
||||
onResult("",dataSize, cacheSize, totalDataCacheSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归计算文件夹或文件的大小
|
||||
*/
|
||||
fun getFolderSize(file: File?): Long {
|
||||
if (file == null || !file.exists()) return 0L
|
||||
|
||||
var size: Long = 0
|
||||
val fileList = file.listFiles() ?: return file.length() // 如果是文件直接返回长度
|
||||
|
||||
for (subFile in fileList) {
|
||||
size += if (subFile.isDirectory) {
|
||||
getFolderSize(subFile) // 递归遍历子目录
|
||||
} else {
|
||||
subFile.length()
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* 引导用户打开应用设置页面(手动清除)
|
||||
*/
|
||||
fun openAppSettings(context: Context) {
|
||||
|
||||
try {
|
||||
// 尝试直接进入存储详情页
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = android.net.Uri.fromParts("package", context.packageName, null)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// 如果直达失败,降级跳转到应用详情主页
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = android.net.Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接清除应用数据(包括缓存和数据库)
|
||||
*/
|
||||
fun clearAppData(context: Context) {
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
// 这一步会直接清除所有私有数据并结束进程
|
||||
val isSuccess = activityManager.clearApplicationUserData()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.img.rabbit.BuildConfig
|
||||
import com.img.rabbit.bean.response.VersionEntity
|
||||
|
||||
object AppUpdate {
|
||||
fun checkUpdate(versionInfo: VersionEntity?, onResult: (isUpdate: Boolean, tips: String) -> Unit) {
|
||||
|
||||
if(versionInfo == null){
|
||||
onResult.invoke(false, "已是最新版本")
|
||||
return
|
||||
}
|
||||
versionInfo.apply {
|
||||
if (checkVersion(version, BuildConfig.VERSION_NAME)) {
|
||||
if (last_version_force == BuildConfig.VERSION_NAME) {
|
||||
force = true
|
||||
} else if (checkVersion(last_version_force, BuildConfig.VERSION_NAME)) {
|
||||
force = true
|
||||
}
|
||||
onResult.invoke(true, "检测到新版本")
|
||||
//提示执行更新
|
||||
//startUpdate(result, fragment)
|
||||
} else {
|
||||
onResult.invoke(false, "当前版本为最新版本")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkVersion(version: String, origin: String): Boolean {
|
||||
if (TextUtils.isEmpty(version) || TextUtils.isEmpty(origin)) {
|
||||
return false
|
||||
}
|
||||
val versions = version.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val origins = origin.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (versions.size != 3) {
|
||||
return false
|
||||
}
|
||||
if (origins.size != 3) {
|
||||
return false
|
||||
}
|
||||
for (i in 0..2) {
|
||||
if (versions[i].toInt() > origins[i].toInt()) {
|
||||
return true
|
||||
} else if (versions[i].toInt() < origins[i].toInt()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,12 @@ import com.tencent.vasdolly.helper.ChannelReaderUtil
|
|||
|
||||
object ChannelUtils {
|
||||
|
||||
fun getChannel(context: Context): String {
|
||||
fun getChannel(context: Context?): String {
|
||||
if (BuildConfig.DEBUG) {
|
||||
MMKVUtils.put("app_channel", "test")
|
||||
} else {
|
||||
if (TextUtils.isEmpty(MMKVUtils.getString("app_channel"))) {
|
||||
MMKVUtils.put("app_channel", getChannelBy(context))
|
||||
MMKVUtils.put("app_channel", getChannelBy(context?:return ""))
|
||||
}
|
||||
}
|
||||
return MMKVUtils.getString("app_channel") ?: ""
|
||||
|
|
|
|||
|
|
@ -25,6 +25,25 @@ import java.io.ByteArrayOutputStream
|
|||
import java.io.OutputStream
|
||||
|
||||
object ImageUtils {
|
||||
fun imageCompressToUri(context: Context, uri: Uri): ByteArray? {
|
||||
// 获取输入流并解码为Bitmap
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
?: throw IOException("无法从Uri中读取文件(File)")
|
||||
|
||||
val bitmap = inputStream.use { inputStream ->
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
} ?: throw IOException("无法解码图片")
|
||||
|
||||
// 压缩图片 (JPEG格式,80%质量)
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
|
||||
val compressedByteArray = outputStream.toByteArray()
|
||||
bitmap.recycle() // 释放Bitmap内存
|
||||
|
||||
// 使用压缩后的字节数组
|
||||
return compressedByteArray
|
||||
}
|
||||
|
||||
fun decodeSampledBitmapFromResource(
|
||||
context: Context,
|
||||
resId: Int,
|
||||
|
|
|
|||
|
|
@ -6,21 +6,17 @@ import java.util.Collections
|
|||
|
||||
object MMKVUtils {
|
||||
|
||||
var mmkv: MMKV? = null
|
||||
|
||||
init {
|
||||
mmkv = MMKV.defaultMMKV()
|
||||
}
|
||||
val mmkv by lazy { MMKV.defaultMMKV() }
|
||||
|
||||
fun put(key: String, value: Any?): Boolean {
|
||||
return when (value) {
|
||||
is String -> mmkv?.encode(key, value)!!
|
||||
is Float -> mmkv?.encode(key, value)!!
|
||||
is Boolean -> mmkv?.encode(key, value)!!
|
||||
is Int -> mmkv?.encode(key, value)!!
|
||||
is Long -> mmkv?.encode(key, value)!!
|
||||
is Double -> mmkv?.encode(key, value)!!
|
||||
is ByteArray -> mmkv?.encode(key, value)!!
|
||||
is String -> mmkv.encode(key, value)
|
||||
is Float -> mmkv.encode(key, value)
|
||||
is Boolean -> mmkv.encode(key, value)
|
||||
is Int -> mmkv.encode(key, value)
|
||||
is Long -> mmkv.encode(key, value)
|
||||
is Double -> mmkv.encode(key, value)
|
||||
is ByteArray -> mmkv.encode(key, value)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -32,68 +28,68 @@ object MMKVUtils {
|
|||
if (t == null) {
|
||||
return false
|
||||
}
|
||||
return mmkv?.encode(key, t)!!
|
||||
return mmkv.encode(key, t)
|
||||
}
|
||||
|
||||
fun put(key: String, sets: Set<String>?): Boolean {
|
||||
if (sets == null) {
|
||||
return false
|
||||
}
|
||||
return mmkv?.encode(key, sets)!!
|
||||
return mmkv.encode(key, sets)
|
||||
}
|
||||
|
||||
fun getInt(key: String): Int? {
|
||||
return mmkv?.decodeInt(key, 0)
|
||||
fun getInt(key: String): Int {
|
||||
return mmkv.decodeInt(key, 0)
|
||||
}
|
||||
|
||||
fun getDouble(key: String): Double? {
|
||||
return mmkv?.decodeDouble(key, 0.00)
|
||||
fun getDouble(key: String): Double {
|
||||
return mmkv.decodeDouble(key, 0.00)
|
||||
}
|
||||
|
||||
fun getLong(key: String): Long? {
|
||||
return mmkv?.decodeLong(key, 0L)
|
||||
fun getLong(key: String): Long {
|
||||
return mmkv.decodeLong(key, 0L)
|
||||
}
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
|
||||
return mmkv?.decodeBool(key, defaultValue) ?: defaultValue
|
||||
return mmkv.decodeBool(key, defaultValue) ?: defaultValue
|
||||
}
|
||||
|
||||
fun getFloat(key: String): Float? {
|
||||
return mmkv?.decodeFloat(key, 0F)
|
||||
fun getFloat(key: String): Float {
|
||||
return mmkv.decodeFloat(key, 0F)
|
||||
}
|
||||
|
||||
fun getByteArray(key: String): ByteArray? {
|
||||
return mmkv?.decodeBytes(key)
|
||||
return mmkv.decodeBytes(key)
|
||||
}
|
||||
|
||||
fun getString(key: String): String? {
|
||||
return mmkv?.decodeString(key, "")
|
||||
return mmkv.decodeString(key, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* SpUtils.getParcelable<Class>("")
|
||||
*/
|
||||
inline fun <reified T : Parcelable> getParcelable(key: String): T? {
|
||||
return mmkv?.decodeParcelable(key, T::class.java)
|
||||
return mmkv.decodeParcelable(key, T::class.java)
|
||||
}
|
||||
|
||||
fun getStringSet(key: String): Set<String>? {
|
||||
return mmkv?.decodeStringSet(key, Collections.emptySet())
|
||||
return mmkv.decodeStringSet(key, Collections.emptySet())
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已经存在
|
||||
*/
|
||||
fun contains(key: String): Boolean? {
|
||||
return mmkv?.containsKey(key)
|
||||
fun contains(key: String): Boolean {
|
||||
return mmkv.containsKey(key)
|
||||
}
|
||||
|
||||
fun removeKey(key: String) {
|
||||
mmkv?.removeValueForKey(key)
|
||||
mmkv.removeValueForKey(key)
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
mmkv?.clearAll()
|
||||
mmkv.clearAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
package com.img.rabbit.utils.appwalle;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class ApkUtil {
|
||||
private ApkUtil() {
|
||||
}
|
||||
|
||||
public static long getCommentLength(FileChannel fileChannel) throws IOException {
|
||||
long archiveSize = fileChannel.size();
|
||||
if (archiveSize < 22L) {
|
||||
throw new IOException("APK too small for ZIP End of Central Directory (EOCD) record");
|
||||
} else {
|
||||
long maxCommentLength = Math.min(archiveSize - 22L, 65535L);
|
||||
long eocdWithEmptyCommentStartPosition = archiveSize - 22L;
|
||||
for(int expectedCommentLength = 0; (long)expectedCommentLength <= maxCommentLength; ++expectedCommentLength) {
|
||||
long eocdStartPos = eocdWithEmptyCommentStartPosition - (long)expectedCommentLength;
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(4);
|
||||
fileChannel.position(eocdStartPos);
|
||||
fileChannel.read(byteBuffer);
|
||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (byteBuffer.getInt(0) == 101010256) {
|
||||
ByteBuffer commentLengthByteBuffer = ByteBuffer.allocate(2);
|
||||
fileChannel.position(eocdStartPos + 20L);
|
||||
fileChannel.read(commentLengthByteBuffer);
|
||||
commentLengthByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
int actualCommentLength = commentLengthByteBuffer.getShort(0);
|
||||
if (actualCommentLength == expectedCommentLength) {
|
||||
return actualCommentLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("ZIP End of Central Directory (EOCD) record not found");
|
||||
}
|
||||
}
|
||||
|
||||
public static long findCentralDirStartOffset(FileChannel fileChannel) throws IOException {
|
||||
return findCentralDirStartOffset(fileChannel, getCommentLength(fileChannel));
|
||||
}
|
||||
|
||||
public static long findCentralDirStartOffset(FileChannel fileChannel, long commentLength) throws IOException {
|
||||
ByteBuffer zipCentralDirectoryStart = ByteBuffer.allocate(4);
|
||||
zipCentralDirectoryStart.order(ByteOrder.LITTLE_ENDIAN);
|
||||
fileChannel.position(fileChannel.size() - commentLength - 6L);
|
||||
fileChannel.read(zipCentralDirectoryStart);
|
||||
return zipCentralDirectoryStart.getInt(0);
|
||||
}
|
||||
|
||||
public static Pair<ByteBuffer, Long> findApkSigningBlock(FileChannel fileChannel) throws IOException, SignatureNotFoundException {
|
||||
long centralDirOffset = findCentralDirStartOffset(fileChannel);
|
||||
return findApkSigningBlock(fileChannel, centralDirOffset);
|
||||
}
|
||||
|
||||
public static Pair<ByteBuffer, Long> findApkSigningBlock(FileChannel fileChannel, long centralDirOffset) throws IOException, SignatureNotFoundException {
|
||||
if (centralDirOffset < 32L) {
|
||||
throw new SignatureNotFoundException("APK too small for APK Signing Block. ZIP Central Directory offset: " + centralDirOffset);
|
||||
} else {
|
||||
fileChannel.position(centralDirOffset - 24L);
|
||||
ByteBuffer footer = ByteBuffer.allocate(24);
|
||||
fileChannel.read(footer);
|
||||
footer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (footer.getLong(8) == 2334950737559900225L && footer.getLong(16) == 3617552046287187010L) {
|
||||
long apkSigBlockSizeInFooter = footer.getLong(0);
|
||||
if (apkSigBlockSizeInFooter >= (long)footer.capacity() && apkSigBlockSizeInFooter <= 2147483639L) {
|
||||
int totalSize = (int)(apkSigBlockSizeInFooter + 8L);
|
||||
long apkSigBlockOffset = centralDirOffset - (long)totalSize;
|
||||
if (apkSigBlockOffset < 0L) {
|
||||
throw new SignatureNotFoundException("APK Signing Block offset out of range: " + apkSigBlockOffset);
|
||||
} else {
|
||||
fileChannel.position(apkSigBlockOffset);
|
||||
ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
|
||||
fileChannel.read(apkSigBlock);
|
||||
apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
|
||||
long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
|
||||
if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
|
||||
throw new SignatureNotFoundException("APK Signing Block sizes in header and footer do not match: " + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
|
||||
} else {
|
||||
return Pair.of(apkSigBlock, apkSigBlockOffset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new SignatureNotFoundException("APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
|
||||
}
|
||||
} else {
|
||||
throw new SignatureNotFoundException("No APK Signing Block before ZIP Central Directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Integer, ByteBuffer> findIdValues(ByteBuffer apkSigningBlock) throws SignatureNotFoundException {
|
||||
checkByteOrderLittleEndian(apkSigningBlock);
|
||||
ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
|
||||
Map<Integer, ByteBuffer> idValues = new LinkedHashMap();
|
||||
int entryCount = 0;
|
||||
while(pairs.hasRemaining()) {
|
||||
++entryCount;
|
||||
if (pairs.remaining() < 8) {
|
||||
throw new SignatureNotFoundException("Insufficient data to read size of APK Signing Block entry #" + entryCount);
|
||||
}
|
||||
|
||||
long lenLong = pairs.getLong();
|
||||
if (lenLong < 4L || lenLong > 2147483647L) {
|
||||
throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount + " size out of range: " + lenLong);
|
||||
}
|
||||
|
||||
int len = (int)lenLong;
|
||||
int nextEntryPos = pairs.position() + len;
|
||||
if (len > pairs.remaining()) {
|
||||
throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount + " size out of range: " + len + ", available: " + pairs.remaining());
|
||||
}
|
||||
int id = pairs.getInt();
|
||||
idValues.put(id, getByteBuffer(pairs, len - 4));
|
||||
pairs.position(nextEntryPos);
|
||||
}
|
||||
return idValues;
|
||||
}
|
||||
|
||||
private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
|
||||
if (start < 0) {
|
||||
throw new IllegalArgumentException("start: " + start);
|
||||
} else if (end < start) {
|
||||
throw new IllegalArgumentException("end < start: " + end + " < " + start);
|
||||
} else {
|
||||
int capacity = source.capacity();
|
||||
if (end > source.capacity()) {
|
||||
throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
|
||||
} else {
|
||||
int originalLimit = source.limit();
|
||||
int originalPosition = source.position();
|
||||
ByteBuffer var7;
|
||||
try {
|
||||
source.position(0);
|
||||
source.limit(end);
|
||||
source.position(start);
|
||||
ByteBuffer result = source.slice();
|
||||
result.order(source.order());
|
||||
var7 = result;
|
||||
} finally {
|
||||
source.position(0);
|
||||
source.limit(originalLimit);
|
||||
source.position(originalPosition);
|
||||
}
|
||||
return var7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer getByteBuffer(ByteBuffer source, int size) throws BufferUnderflowException {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("size: " + size);
|
||||
} else {
|
||||
int originalLimit = source.limit();
|
||||
int position = source.position();
|
||||
int limit = position + size;
|
||||
if (limit >= position && limit <= originalLimit) {
|
||||
source.limit(limit);
|
||||
ByteBuffer var6;
|
||||
try {
|
||||
ByteBuffer result = source.slice();
|
||||
result.order(source.order());
|
||||
source.position(limit);
|
||||
var6 = result;
|
||||
} finally {
|
||||
source.limit(originalLimit);
|
||||
}
|
||||
|
||||
return var6;
|
||||
} else {
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
|
||||
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
|
||||
throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.img.rabbit.utils.appwalle;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 封装读取逻辑
|
||||
*/
|
||||
public final class ChannelReader {
|
||||
private ChannelReader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取注入的内容
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static String get(@NonNull Context context) {
|
||||
String apkPath = getApkPath(context);
|
||||
String raw = TextUtils.isEmpty(apkPath) ? null : getRaw(new File(apkPath));
|
||||
if (!TextUtils.isEmpty(raw)) {
|
||||
raw = raw.replaceAll("\"", "");
|
||||
raw = raw.replaceAll("#", "");
|
||||
}
|
||||
|
||||
try {
|
||||
if (TextUtils.isEmpty(raw)) {
|
||||
return "";
|
||||
}
|
||||
return new String(Base64.decode(raw.getBytes(), Base64.NO_WRAP));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getApkPath(@NonNull Context context) {
|
||||
String apkPath = null;
|
||||
try {
|
||||
ApplicationInfo applicationInfo = context.getApplicationInfo();
|
||||
if (applicationInfo == null) {
|
||||
return null;
|
||||
}
|
||||
apkPath = applicationInfo.sourceDir;
|
||||
} catch (Exception var3) {
|
||||
Log.d("异常", var3.getMessage());
|
||||
}
|
||||
return apkPath;
|
||||
}
|
||||
|
||||
public static String getRaw(File apkFile) {
|
||||
return PayloadReader.getString(apkFile, 1896449981);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.img.rabbit.utils.appwalle;
|
||||
|
||||
final class Pair<A, B> {
|
||||
private final A mFirst;
|
||||
private final B mSecond;
|
||||
|
||||
private Pair(A first, B second) {
|
||||
this.mFirst = first;
|
||||
this.mSecond = second;
|
||||
}
|
||||
|
||||
public static <A, B> Pair<A, B> of(A first, B second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
public A getFirst() {
|
||||
return this.mFirst;
|
||||
}
|
||||
|
||||
public B getSecond() {
|
||||
return this.mSecond;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result = 1;
|
||||
result = 31 * result + (this.mFirst == null ? 0 : this.mFirst.hashCode());
|
||||
result = 31 * result + (this.mSecond == null ? 0 : this.mSecond.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
} else if (obj == null) {
|
||||
return false;
|
||||
} else if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
} else {
|
||||
@SuppressWarnings("rawtypes") Pair other = (Pair) obj;
|
||||
if (this.mFirst == null) {
|
||||
if (other.mFirst != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!this.mFirst.equals(other.mFirst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.mSecond == null) {
|
||||
return other.mSecond == null;
|
||||
} else return this.mSecond.equals(other.mSecond);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.img.rabbit.utils.appwalle;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class PayloadReader {
|
||||
private PayloadReader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定ID的数据
|
||||
*
|
||||
* @param apkFile
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public static String getString(File apkFile, int id) {
|
||||
byte[] bytes = get(apkFile, id);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] get(File apkFile, int id) {
|
||||
Map<Integer, ByteBuffer> idValues = getAll(apkFile);
|
||||
if (idValues == null) {
|
||||
return null;
|
||||
} else {
|
||||
ByteBuffer byteBuffer = (ByteBuffer) idValues.get(id);
|
||||
return byteBuffer == null ? null : getBytes(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getBytes(ByteBuffer byteBuffer) {
|
||||
byte[] array = byteBuffer.array();
|
||||
int arrayOffset = byteBuffer.arrayOffset();
|
||||
return Arrays.copyOfRange(array, arrayOffset + byteBuffer.position(), arrayOffset + byteBuffer.limit());
|
||||
}
|
||||
|
||||
private static Map<Integer, ByteBuffer> getAll(File apkFile) {
|
||||
Map<Integer, ByteBuffer> idValues = null;
|
||||
try {
|
||||
RandomAccessFile randomAccessFile = null;
|
||||
FileChannel fileChannel = null;
|
||||
try {
|
||||
randomAccessFile = new RandomAccessFile(apkFile, "r");
|
||||
fileChannel = randomAccessFile.getChannel();
|
||||
ByteBuffer apkSigningBlock2 = (ByteBuffer) ApkUtil.findApkSigningBlock(fileChannel).getFirst();
|
||||
idValues = ApkUtil.findIdValues(apkSigningBlock2);
|
||||
} catch (IOException var18) {
|
||||
Log.d("异常", Objects.requireNonNull(var18.getMessage()));
|
||||
} finally {
|
||||
try {
|
||||
if (fileChannel != null) {
|
||||
fileChannel.close();
|
||||
}
|
||||
} catch (IOException var17) {
|
||||
Log.d("异常", Objects.requireNonNull(var17.getMessage()));
|
||||
}
|
||||
try {
|
||||
if (randomAccessFile != null) {
|
||||
randomAccessFile.close();
|
||||
}
|
||||
} catch (IOException var16) {
|
||||
Log.d("异常", Objects.requireNonNull(var16.getMessage()));
|
||||
}
|
||||
}
|
||||
} catch (SignatureNotFoundException var20) {
|
||||
Log.d("异常", Objects.requireNonNull(var20.getMessage()));
|
||||
}
|
||||
return idValues;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.img.rabbit.utils.appwalle;
|
||||
|
||||
public class SignatureNotFoundException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SignatureNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.google.gson.JsonObject
|
||||
import com.img.rabbit.bean.local.ErrorBean
|
||||
import com.img.rabbit.bean.response.LoginInfoEntity
|
||||
import com.img.rabbit.provider.api.ApiManager
|
||||
import com.img.rabbit.provider.api.ResultVo
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
class AccountBindViewModel : BaseViewModel() {
|
||||
// 登录状态
|
||||
val unBindState = mutableStateOf<ResultVo<LoginInfoEntity>?>(null)
|
||||
// 错误状态
|
||||
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||
|
||||
/**
|
||||
* 请求登录(验证码)
|
||||
*/
|
||||
fun requestUnBindByPhone(phone: String) {
|
||||
isLoading.value = true // 开始加载
|
||||
|
||||
// 调用 API 获取数据
|
||||
val jsonPhone = JsonObject()
|
||||
jsonPhone.addProperty("phone", phone)
|
||||
|
||||
val jsonObject = JsonObject()
|
||||
jsonObject.addProperty("type", "phone")
|
||||
jsonObject.addProperty("bind", "2")
|
||||
jsonObject.add("data", jsonPhone)
|
||||
|
||||
unBindAccount(jsonObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* 拿着微信授权码完成登录(在WXEntryActivity中调用)
|
||||
* @param wechatCode 微信授权码
|
||||
*/
|
||||
fun requestUnBindByWechat(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("type", "weixin")
|
||||
jsonObject.addProperty("bind", "2")
|
||||
jsonObject.add("data", jsonWx)
|
||||
|
||||
unBindAccount(jsonObject)
|
||||
}
|
||||
|
||||
fun unBindAccount(jsonObject: JsonObject){
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody())
|
||||
if (response.status) {
|
||||
unBindState.value = response
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "解绑失败" })
|
||||
}
|
||||
isLoading.value = false // 加载完成
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.img.rabbit.bean.local.ErrorBean
|
||||
import com.img.rabbit.provider.api.ApiManager
|
||||
import okhttp3.RequestBody
|
||||
|
||||
class AccountManagerViewModel : BaseViewModel() {
|
||||
// 错误状态
|
||||
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||
|
||||
fun requestAccount() {
|
||||
isLoading.value = true // 开始加载
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.account()
|
||||
if (response.status) {
|
||||
// TODO 处理获取账号列表
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "提交失败..." })
|
||||
}
|
||||
isLoading.value = false // 开始加载
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.g.gysdk.GYManager
|
||||
import com.g.gysdk.GYResponse
|
||||
import com.g.gysdk.GyCallBack
|
||||
import com.g.gysdk.GyConfig
|
||||
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.pages.screen.mine.setting.BindScreenType
|
||||
import com.img.rabbit.provider.api.ApiManager
|
||||
import com.img.rabbit.provider.api.ResultVo
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.tencent.mm.opensdk.modelmsg.SendAuth
|
||||
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
class BindViewModel : BaseViewModel() {
|
||||
private val TAG = "BindViewModel"
|
||||
private val ONEKEY_TAG = "BindViewModel_OneKey"
|
||||
val bindScreenType = mutableStateOf(BindScreenType.BIND_NORMAL)
|
||||
// 登录状态
|
||||
val bindState = mutableStateOf<ResultVo<LoginInfoEntity>?>(null)
|
||||
// 错误状态
|
||||
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||
|
||||
// 是否同意政策协议
|
||||
private val _policyAgreement = mutableStateOf(true)
|
||||
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
||||
fun setIsPolicyAgreement(isAgreement: Boolean) {
|
||||
_policyAgreement.value = isAgreement
|
||||
}
|
||||
|
||||
|
||||
// 一键登录是否有效
|
||||
private val isGYUIDValid = mutableStateOf(false)
|
||||
var oneKeyPreLogin: OnekeyPreLogin? = null
|
||||
// 登录用户名
|
||||
val userName = mutableStateOf("")
|
||||
fun setUserName(loginName: String) {
|
||||
userName.value = loginName
|
||||
}
|
||||
// 登录验证码
|
||||
val captcha = mutableStateOf("")
|
||||
// 登录验证码发送时间戳
|
||||
val captchaTimestamp = mutableStateOf("")
|
||||
fun setCaptcha(loginCaptcha: String) {
|
||||
captcha.value = loginCaptcha
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
} else {
|
||||
//考虑到是用户在等待,建议超时8s以上,至少设置5s以上
|
||||
GYManager.getInstance().ePreLogin(5000, object : GyCallBack {
|
||||
override fun onSuccess(response: GYResponse?) {
|
||||
//预登录成功,启动登录授权页
|
||||
onShowOneKeyScreen(true)
|
||||
}
|
||||
|
||||
override fun onFailed(response: GYResponse?) {
|
||||
//预登录失败,提示用户稍后重试
|
||||
onShowOneKeyScreen(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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", "1")
|
||||
jsonObject.add("data", jsonOneKey)
|
||||
|
||||
requestBind(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(TAG, "请求验证码成功: ${response.data.timestamp}")
|
||||
captchaTimestamp.value = response.data.timestamp
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "获取验证码失败" })
|
||||
}
|
||||
isLoading.value = false // 加载完成
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求登录(验证码)
|
||||
*/
|
||||
fun requestBindForCaptcha(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("type", "phone")
|
||||
jsonObject.addProperty("bind", "1")
|
||||
jsonObject.add("data", jsonPhone)
|
||||
|
||||
requestBind(jsonObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求微信授权
|
||||
*/
|
||||
fun requestWithWechatAuth(context: Context, wxApi: IWXAPI) {
|
||||
if (isPolicyAgreement.value) {
|
||||
doWechatAuth(context, wxApi)
|
||||
}else{
|
||||
Toast.makeText(context, "请先同意用户协议和隐私政策", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
//获取微信授权
|
||||
private fun doWechatAuth(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 requestWxBind(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("type", "weixin")
|
||||
jsonObject.addProperty("bind", "1")
|
||||
jsonObject.add("data", jsonWx)
|
||||
|
||||
requestBind(jsonObject)
|
||||
}
|
||||
|
||||
|
||||
//请求绑定
|
||||
private fun requestBind(jsonObject: JsonObject){
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody())
|
||||
if (response.status) {
|
||||
bindState.value = response
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "登录失败" })
|
||||
}
|
||||
isLoading.value = false // 加载完成
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum class BindType(val type: Int,val desc: String){
|
||||
FROM_PHONE(0,"手机号"),
|
||||
FROM_WX(1,"微信")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,20 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.img.rabbit.bean.local.ErrorBean
|
||||
import com.img.rabbit.bean.local.FileManagerBean
|
||||
import com.img.rabbit.provider.api.ApiManager
|
||||
import com.img.rabbit.utils.ImageUtils
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.io.IOException
|
||||
|
||||
class FeedbackViewModel : ViewModel() {
|
||||
val isLoading = mutableStateOf(true)
|
||||
|
||||
fun setLoading(loading: Boolean) {
|
||||
isLoading.value = loading
|
||||
}
|
||||
class FeedbackViewModel : BaseViewModel() {
|
||||
// 错误状态
|
||||
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||
|
||||
// 反馈类型
|
||||
private val _feedbackType = mutableStateOf(FeedbackType.FUNCTION)
|
||||
|
|
@ -38,6 +43,15 @@ class FeedbackViewModel : ViewModel() {
|
|||
_currentImageUris.value = currentImageUris
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
private val _uploadFiles = mutableStateOf(emptyList<FileManagerBean>())
|
||||
val uploadFiles: List<FileManagerBean>
|
||||
get() = _uploadFiles.value
|
||||
|
||||
fun setUploadFiles(uploadFiles: List<FileManagerBean>) {
|
||||
_uploadFiles.value = uploadFiles
|
||||
}
|
||||
|
||||
// 联系方式
|
||||
private val _feedbackContact = mutableStateOf("")
|
||||
val feedbackContact: String
|
||||
|
|
@ -47,9 +61,66 @@ class FeedbackViewModel : ViewModel() {
|
|||
_feedbackContact.value = feedbackContact
|
||||
}
|
||||
|
||||
// 提交反馈
|
||||
fun submitFeedback() {
|
||||
setLoading(true)
|
||||
|
||||
fun uploadImage(context: Context, uri: Uri) {
|
||||
isLoading.value = true // 开始加载
|
||||
mLaunch {
|
||||
try {
|
||||
val compressedUri = ImageUtils.imageCompressToUri(context, uri) ?: throw IOException("无法压缩图片")
|
||||
val requestFile = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), compressedUri)
|
||||
val filePart = MultipartBody.Part.createFormData("file", uri.lastPathSegment, requestFile)
|
||||
val response = ApiManager.serviceVo.upload(filePart, "feedback")
|
||||
if (response.status) {
|
||||
val updateFile = FileManagerBean(response.data, uri)
|
||||
setUploadFiles(uploadFiles + updateFile)
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "上传失败" })
|
||||
}
|
||||
isLoading.value = false // 开始加载
|
||||
}catch (e: IOException){
|
||||
e.printStackTrace()
|
||||
isLoading.value = false // 开始加载
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun batchDeleteImage(uris: List<Uri>) {
|
||||
isLoading.value = true // 开始加载
|
||||
uris.forEach {
|
||||
deleteImage(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteImage(uri: Uri) {
|
||||
isLoading.value = true // 开始加载
|
||||
val fileId = uploadFiles.firstOrNull { it.uri == uri }?.fileEntity?.id
|
||||
if(fileId.isNullOrEmpty()){
|
||||
isLoading.value = false // 开始加载
|
||||
return
|
||||
}
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.deleteFile(fileId)
|
||||
if (response.status) {
|
||||
setUploadFiles(uploadFiles.filter { it.uri != uri })
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "删除失败" })
|
||||
}
|
||||
isLoading.value = false // 开始加载
|
||||
}
|
||||
}
|
||||
|
||||
fun submitFeedback(requestBody: RequestBody) {
|
||||
isLoading.value = true // 开始加载
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.feedback(requestBody)
|
||||
if (response.status) {
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "谢谢您的反馈!" })
|
||||
}else{
|
||||
errorState.value = ErrorBean(response.code.toString(), response.message.ifEmpty { "提交失败..." })
|
||||
}
|
||||
isLoading.value = false // 开始加载
|
||||
}
|
||||
}
|
||||
|
||||
enum class FeedbackType(val keyName: String, val value: String) {
|
||||
|
|
|
|||
|
|
@ -8,25 +8,33 @@ import android.net.Network
|
|||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.img.rabbit.config.Constants
|
||||
import com.img.rabbit.provider.api.ApiManager
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.img.rabbit.utils.MMKVUtils.mmkv
|
||||
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||
import com.tencent.mm.opensdk.openapi.WXAPIFactory
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
class GeneralViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val kv by lazy { MMKV.defaultMMKV() }
|
||||
lateinit var api: IWXAPI
|
||||
private val _networkStatus = MutableLiveData<Boolean>()
|
||||
val networkStatus: LiveData<Boolean> = _networkStatus
|
||||
fun setNetworkStatus(status: Boolean) {
|
||||
_networkStatus.value = status
|
||||
}
|
||||
|
||||
private val _serverTime = mutableStateOf<Long?>(null)
|
||||
val serverTime: State<Long?> = _serverTime
|
||||
|
||||
private val _isNavigationBarVisible = MutableLiveData<Boolean>()
|
||||
val isNavigationBarVisible: LiveData<Boolean> = _isNavigationBarVisible
|
||||
|
||||
|
|
@ -46,7 +54,7 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
|||
private val _agreementStatus = MutableLiveData<Boolean>()
|
||||
val agreementStatus: LiveData<Boolean> = _agreementStatus
|
||||
private fun getIsAgreement(): Boolean{
|
||||
return kv.getBoolean("isAgreement", false)
|
||||
return mmkv.getBoolean("isAgreement", false)
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
@ -64,6 +72,9 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
|||
_networkStatus.value = isNetworkAvailable()
|
||||
// 初始化隐私政策状态
|
||||
_agreementStatus.value = getIsAgreement()
|
||||
|
||||
// 初始化微信API
|
||||
initWXApi(application)
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(): Boolean {
|
||||
|
|
@ -83,10 +94,14 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
|||
}
|
||||
|
||||
fun setIsAgreement(agreement: Boolean){
|
||||
kv.putBoolean("isAgreement", agreement)
|
||||
mmkv.putBoolean("isAgreement", agreement)
|
||||
_agreementStatus.value = agreement
|
||||
}
|
||||
|
||||
private fun initWXApi(context: Context) {
|
||||
api = WXAPIFactory.createWXAPI(context, Constants.WechatAppId)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
connectivityManager.unregisterNetworkCallback(networkCallback)
|
||||
|
|
@ -97,6 +112,7 @@ class GeneralViewModel(application: Application) : AndroidViewModel(application)
|
|||
GlobalScope.launch {
|
||||
val response = ApiManager.serviceVo.getServerTime()
|
||||
if (response.status) {
|
||||
_serverTime.value = response.data
|
||||
PreferenceUtil.setTimeDiff(response.data - System.currentTimeMillis() / 1000)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import android.widget.Toast
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import com.alipay.sdk.app.AuthTask
|
||||
import com.img.rabbit.pages.LoginScreenType
|
||||
import com.g.gysdk.GYManager
|
||||
|
|
@ -17,22 +16,23 @@ 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.Gson
|
||||
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.WxBean
|
||||
import com.img.rabbit.bean.response.UserEntity
|
||||
import com.img.rabbit.bean.response.UserConfigEntity
|
||||
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
|
||||
|
|
@ -40,18 +40,10 @@ import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
|||
|
||||
class LoginViewModel : BaseViewModel() {
|
||||
private val TAG = "LoginViewModel"
|
||||
|
||||
private val ONEKEY_TAG = "OneKeyLoginViewModel"
|
||||
//private lateinit var api: IWXAPI
|
||||
|
||||
private lateinit var api: IWXAPI
|
||||
|
||||
private val _wxState = MutableLiveData<WxBean?>()
|
||||
val wxState: LiveData<WxBean?> = _wxState
|
||||
|
||||
fun updateWxState(newState: WxBean) {
|
||||
_wxState.value = newState
|
||||
}
|
||||
|
||||
// private val _authInfo = MutableLiveData<String>()
|
||||
val authInfoForAlipay: MutableState<String> = mutableStateOf("")
|
||||
|
||||
val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL)
|
||||
|
|
@ -61,34 +53,29 @@ class LoginViewModel : BaseViewModel() {
|
|||
val captcha = mutableStateOf("")
|
||||
// 登录验证码发送时间戳
|
||||
val captchaTimestamp = mutableStateOf("")
|
||||
|
||||
// 是否同意政策协议
|
||||
private val _policyAgreement = mutableStateOf(true)
|
||||
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
||||
|
||||
fun setCaptcha(loginCaptcha: String) {
|
||||
captcha.value = loginCaptcha
|
||||
}
|
||||
fun setUserName(loginName: String) {
|
||||
userName.value = loginName
|
||||
}
|
||||
|
||||
|
||||
private val isGYUIDValid = mutableStateOf(false)
|
||||
var oneKeyPreLogin: OnekeyPreLogin? = null
|
||||
|
||||
|
||||
fun setUserName(loginName: String) {
|
||||
userName.value = loginName
|
||||
}
|
||||
|
||||
fun setCaptcha(loginCaptcha: String) {
|
||||
captcha.value = loginCaptcha
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 是否同意政策协议
|
||||
private val _policyAgreement = mutableStateOf(true)
|
||||
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
||||
fun setIsPolicyAgreement(isAgreement: Boolean) {
|
||||
_policyAgreement.value = isAgreement
|
||||
}
|
||||
|
||||
//用户配置
|
||||
val _userConfig = MutableLiveData<UserConfigEntity?>()
|
||||
|
||||
val userConfig: UserConfigEntity? get() = _userConfig.value
|
||||
|
||||
private val _isLogin = mutableStateOf(false)
|
||||
val isLogin: State<Boolean> = _isLogin
|
||||
|
||||
|
|
@ -98,7 +85,9 @@ class LoginViewModel : BaseViewModel() {
|
|||
|
||||
|
||||
|
||||
val loginState = mutableStateOf<ResultVo<UserEntity>?>(null)
|
||||
// 登录状态
|
||||
val loginState = mutableStateOf<ResultVo<LoginInfoEntity>?>(null)
|
||||
// 错误状态
|
||||
val errorState = mutableStateOf<ErrorBean?>(null)
|
||||
|
||||
fun requestUserConfig(){
|
||||
|
|
@ -108,12 +97,7 @@ class LoginViewModel : BaseViewModel() {
|
|||
if (response.status) {
|
||||
PreferenceUtil.saveXToken(response.data.token)
|
||||
PreferenceUtil.setTimeDiff(response.data.nowtime.toLong() - System.currentTimeMillis() / 1000)
|
||||
_userConfig.postValue(response.data)
|
||||
|
||||
val resultJson = Gson().toJson(response.data)
|
||||
MMKVUtils.put("userConfig", resultJson)
|
||||
|
||||
Log.w("LoginViewModel", "获取配置成功: $resultJson")
|
||||
PreferenceUtil.saveUserConfig(response.data)
|
||||
}else{
|
||||
Log.w("LoginViewModel", "获取配置失败: code=${response.code}, message=${response.message}")
|
||||
}
|
||||
|
|
@ -155,7 +139,6 @@ class LoginViewModel : BaseViewModel() {
|
|||
// 根据解析结果决定是否继续(根据errorCode判断)
|
||||
if (oneKeyPreLogin?.errorCode == 0) {
|
||||
oneKeyLoginValid(
|
||||
activity = activity,
|
||||
onShowOneKeyScreen = onShowOneKeyScreen
|
||||
)
|
||||
} else {
|
||||
|
|
@ -186,7 +169,7 @@ class LoginViewModel : BaseViewModel() {
|
|||
}).build())
|
||||
}
|
||||
|
||||
private fun oneKeyLoginValid(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
|
||||
private fun oneKeyLoginValid(onShowOneKeyScreen:(Boolean)->Unit) {
|
||||
if (GYManager.getInstance().isPreLoginResultValid) {
|
||||
//预登录有效,启动登录授权页
|
||||
onShowOneKeyScreen(true)
|
||||
|
|
@ -214,12 +197,6 @@ class LoginViewModel : BaseViewModel() {
|
|||
|
||||
// 调用 API 获取数据
|
||||
val jsonOneKey = JsonObject()
|
||||
// jsonOneKey.addProperty("gyuid", gyuid)
|
||||
// jsonOneKey.addProperty("token", token)
|
||||
// val jsonObject = JsonObject()
|
||||
// jsonObject.addProperty("login_type", "onekey")
|
||||
// jsonObject.add("onekey", jsonOneKey)
|
||||
// jsonObject.addProperty("bind", false)
|
||||
|
||||
jsonOneKey.addProperty("gyuid", gyuid)
|
||||
jsonOneKey.addProperty("token", token)
|
||||
|
|
@ -273,19 +250,26 @@ class LoginViewModel : BaseViewModel() {
|
|||
requestLogin(jsonObject)
|
||||
}
|
||||
|
||||
|
||||
fun initWXApi(context: Context) {
|
||||
api = WXAPIFactory.createWXAPI(context, Constants.WechatAppId)
|
||||
}
|
||||
|
||||
fun loginWithWechat(context: Context) {
|
||||
fun loginWithWechat(context: Context, wxApi:IWXAPI) {
|
||||
if (isPolicyAgreement.value) {
|
||||
doWxAuth(context)
|
||||
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 微信授权码
|
||||
|
|
@ -295,6 +279,7 @@ class LoginViewModel : BaseViewModel() {
|
|||
return
|
||||
}
|
||||
isLoading.value = true // 开始加载
|
||||
PreferenceUtil.saveWxCode(wechatCode)
|
||||
|
||||
// 调用 API 获取数据
|
||||
val jsonWx = JsonObject()
|
||||
|
|
@ -308,18 +293,6 @@ class LoginViewModel : BaseViewModel() {
|
|||
requestLogin(jsonObject)
|
||||
}
|
||||
|
||||
//获取微信授权
|
||||
private fun doWxAuth(context: Context) {
|
||||
if (!api.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"
|
||||
api.sendReq(req)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付宝登录
|
||||
|
|
@ -393,19 +366,60 @@ class LoginViewModel : BaseViewModel() {
|
|||
requestLogin(jsonObject)
|
||||
}
|
||||
|
||||
//请求登录
|
||||
private fun requestLogin(jsonObject: JsonObject){
|
||||
mLaunch {
|
||||
val response = ApiManager.serviceVo.login(jsonObject.toString().toRequestBody())
|
||||
if (response.status) {
|
||||
loginState.value = response
|
||||
val userEntity = response.data
|
||||
userEntity.isLogin = true
|
||||
val loginInfoEntity = response.data
|
||||
loginInfoEntity.isLogin = true
|
||||
//记录登录数据
|
||||
PreferenceUtil.saveUserInfo(userEntity)
|
||||
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,"退出登录")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,19 @@ package com.img.rabbit.viewmodel.`interface`
|
|||
|
||||
import com.img.rabbit.bean.response.AlipayParamEntity
|
||||
import com.img.rabbit.bean.response.CaptchaCodeEntity
|
||||
import com.img.rabbit.bean.response.UserEntity
|
||||
import com.img.rabbit.bean.response.UploadFileEntity
|
||||
import com.img.rabbit.bean.response.LoginInfoEntity
|
||||
import com.img.rabbit.bean.response.UserConfigEntity
|
||||
import com.img.rabbit.bean.response.UserInfoEntity
|
||||
import com.img.rabbit.provider.api.ResultVo
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface ServiceVo {
|
||||
|
|
@ -52,12 +58,49 @@ interface ServiceVo {
|
|||
* 登录
|
||||
*/
|
||||
@POST("/api/user/login")
|
||||
suspend fun login(@Body requestBody: RequestBody): ResultVo<UserEntity>
|
||||
suspend fun login(@Body requestBody: RequestBody): ResultVo<LoginInfoEntity>
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@POST("/mapi/user/logout")
|
||||
@POST("/api/user/logout")
|
||||
suspend fun logout(): ResultVo<Any>
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
@POST("/api/user/destroy")
|
||||
suspend fun userDestroy(): ResultVo<Any>
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
@GET("/api/user")
|
||||
suspend fun userInfo(): ResultVo<UserInfoEntity>
|
||||
|
||||
/**
|
||||
* 获取账号列表
|
||||
*/
|
||||
@POST("/api/user/account")
|
||||
suspend fun account(): ResultVo<Any>//@Query("scene") scene: String="account"
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
@Multipart
|
||||
@POST("/api/user/upload")
|
||||
suspend fun upload(@Part part: MultipartBody.Part, @Query("scene") scene: String): ResultVo<UploadFileEntity>
|
||||
|
||||
/**
|
||||
*删除上传文件
|
||||
*/
|
||||
@DELETE("/api/user/upload")
|
||||
suspend fun deleteFile(@Query("id") id: String): ResultVo<Any>
|
||||
|
||||
/**
|
||||
* 意见反馈
|
||||
*/
|
||||
@POST("/api/user/feedback")
|
||||
suspend fun feedback(@Body requestBody: RequestBody): ResultVo<Any>
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M512,42.7a469.3,469.3 0,1 0,469.3 469.3A469.3,469.3 0,0 0,512 42.7zM512,906.7a394.7,394.7 0,1 1,394.7 -394.7,395.1 395.1,0 0,1 -394.7,394.7z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M670.4,300.8l-154.7,154.7a5.3,5.3 0,0 1,-7.6 0l-154.7,-154.7a5.3,5.3 0,0 0,-7.5 0l-45.2,45.3a5.3,5.3 0,0 0,0 7.5l154.7,154.7a5.3,5.3 0,0 1,0 7.6l-154.7,154.7a5.3,5.3 0,0 0,0 7.5l45.3,45.3a5.3,5.3 0,0 0,7.5 0l154.7,-154.7a5.3,5.3 0,0 1,7.6 0l154.7,154.7a5.3,5.3 0,0 0,7.5 0l45.3,-45.3a5.3,5.3 0,0 0,0 -7.5l-154.7,-154.7a5.3,5.3 0,0 1,0 -7.6l154.7,-154.7a5.3,5.3 0,0 0,0 -7.5l-45.3,-45.3a5.3,5.3 0,0 0,-7.6 0z"/>
|
||||
</vector>
|
||||
|
|
@ -25,10 +25,26 @@
|
|||
<external-media-path
|
||||
name="external_media_path"
|
||||
path="." />
|
||||
<!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录,否则微信分身保存的图片,就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg,在小米6的手机上微信分身有这个crash,华为没有
|
||||
-->
|
||||
<!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录,否则微信分身保存的图片,就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg,在小米6的手机上微信分身有这个crash,华为没有-->
|
||||
<root-path
|
||||
name="root-path"
|
||||
path="" />
|
||||
|
||||
<!-- 添加根目录访问权限 -->
|
||||
<external-cache-path
|
||||
name="external_cache"
|
||||
path="." />
|
||||
|
||||
<!-- 兼容旧版本 -->
|
||||
<cache-path
|
||||
name="internal_cache"
|
||||
path="." />
|
||||
|
||||
|
||||
<external-files-path
|
||||
name="external_files"
|
||||
path="." />
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="." />
|
||||
</paths>
|
||||
Loading…
Reference in New Issue