parent
1168061f72
commit
d558fde6f3
|
|
@ -4,10 +4,10 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-02-27T07:54:46.218482200Z">
|
<DropdownSelection timestamp="2026-02-28T08:59:56.877010800Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=JRBI89BIE6AI5TG6" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=Y5DELZR46DZTCI9D" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ android {
|
||||||
applicationId = "com.img.rabbit"
|
applicationId = "com.img.rabbit"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 2
|
versionCode = 1
|
||||||
versionName = "1.1"
|
versionName = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
setManifestPlaceholders(mapOf(
|
setManifestPlaceholders(mapOf(
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32"
|
android:maxSdkVersion="32"
|
||||||
tools:ignore="SelectedPhotoAccess" />
|
tools:ignore="ScopedStorage,SelectedPhotoAccess" />
|
||||||
<uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE"
|
<uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32"
|
android:maxSdkVersion="32"
|
||||||
tools:ignore="SelectedPhotoAccess" />
|
tools:ignore="SelectedPhotoAccess" />
|
||||||
|
|
@ -27,6 +27,13 @@
|
||||||
<!--开关wifi状态,解决国内机型移动网络权限问题需要-->
|
<!--开关wifi状态,解决国内机型移动网络权限问题需要-->
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||||
|
|
||||||
|
<!-- 安装应用权限 -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
|
||||||
|
tools:ignore="RequestInstallPackagesPolicy" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.INSTALL_PACKAGES"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<package android:name="com.eg.android.AlipayGphone" /> <!-- 支付宝 -->
|
<package android:name="com.eg.android.AlipayGphone" /> <!-- 支付宝 -->
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.img.rabbit
|
package com.img.rabbit
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
|
@ -32,8 +35,10 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -52,16 +57,19 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.img.rabbit.config.Constants
|
import com.img.rabbit.config.Constants
|
||||||
import com.img.rabbit.config.Constants.agreementUrl
|
import com.img.rabbit.config.Constants.agreementUrl
|
||||||
import com.img.rabbit.config.Constants.privacyUrl
|
import com.img.rabbit.config.Constants.privacyUrl
|
||||||
import com.img.rabbit.pages.LoginScreen
|
import com.img.rabbit.pages.LoginScreen
|
||||||
import com.img.rabbit.pages.LoginScreenType
|
import com.img.rabbit.pages.LoginScreenType
|
||||||
import com.img.rabbit.pages.MainScreen
|
import com.img.rabbit.pages.MainScreen
|
||||||
|
import com.img.rabbit.pages.dialog.UpdateDialog
|
||||||
import com.img.rabbit.provider.storage.GlobalStateManager
|
import com.img.rabbit.provider.storage.GlobalStateManager
|
||||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
import com.img.rabbit.provider.storage.PreferenceUtil.saveBDVID
|
import com.img.rabbit.provider.storage.PreferenceUtil.saveBDVID
|
||||||
import com.img.rabbit.utils.ChannelUtils
|
import com.img.rabbit.utils.ChannelUtils
|
||||||
|
import com.img.rabbit.utils.UpdateUtils
|
||||||
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||||
import com.img.rabbit.viewmodel.LoginViewModel
|
import com.img.rabbit.viewmodel.LoginViewModel
|
||||||
|
|
@ -75,7 +83,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class, ExperimentalPermissionsApi::class)
|
||||||
@SuppressLint("UnrememberedMutableState", "CoroutineCreationDuringComposition")
|
@SuppressLint("UnrememberedMutableState", "CoroutineCreationDuringComposition")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// 必须在 super.onCreate 之前调用
|
// 必须在 super.onCreate 之前调用
|
||||||
|
|
@ -86,7 +94,7 @@ class MainActivity : ComponentActivity() {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
var showUpdateDialog by remember { mutableStateOf(false) }
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val splashViewModel: SplashViewModel = viewModel()
|
val splashViewModel: SplashViewModel = viewModel()
|
||||||
val generalViewModel: GeneralViewModel = viewModel()
|
val generalViewModel: GeneralViewModel = viewModel()
|
||||||
|
|
@ -95,7 +103,9 @@ class MainActivity : ComponentActivity() {
|
||||||
var showSplash by remember { mutableStateOf(false) }
|
var showSplash by remember { mutableStateOf(false) }
|
||||||
var globalLogin by mutableStateOf(GlobalStateManager(context).globalLoginNotifyFlow().collectAsState(initial = false))
|
var globalLogin by mutableStateOf(GlobalStateManager(context).globalLoginNotifyFlow().collectAsState(initial = false))
|
||||||
var globalLogout by mutableStateOf(GlobalStateManager(context).globalLogoutNotifyFlow().collectAsState(initial = false))
|
var globalLogout by mutableStateOf(GlobalStateManager(context).globalLogoutNotifyFlow().collectAsState(initial = false))
|
||||||
|
var globalBind by mutableStateOf(GlobalStateManager(context).globalBindNotifyFlow().collectAsState(initial = false))
|
||||||
var globalUnBind by mutableStateOf(GlobalStateManager(context).globalUnBindNotifyFlow().collectAsState(initial = false))
|
var globalUnBind by mutableStateOf(GlobalStateManager(context).globalUnBindNotifyFlow().collectAsState(initial = false))
|
||||||
|
var globalUpdate by mutableStateOf(GlobalStateManager(context).globalUpdateNotifyFlow().collectAsState(initial = false))
|
||||||
|
|
||||||
LaunchedEffect(generalViewModel.agreementStatus.value) {
|
LaunchedEffect(generalViewModel.agreementStatus.value) {
|
||||||
if (generalViewModel.agreementStatus.value == true){
|
if (generalViewModel.agreementStatus.value == true){
|
||||||
|
|
@ -117,6 +127,13 @@ class MainActivity : ComponentActivity() {
|
||||||
splashScreen.setKeepOnScreenCondition {
|
splashScreen.setKeepOnScreenCondition {
|
||||||
splashViewModel.isLoading.value // 当为 true 时,启动页不消失
|
splashViewModel.isLoading.value // 当为 true 时,启动页不消失
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//更新用户配置与用户信息
|
||||||
|
if(globalLogin.value == true || globalBind.value == true || globalUnBind.value == true || globalLogout.value == true){
|
||||||
|
loginViewModel.requestUserConfig()
|
||||||
|
loginViewModel.requestUserInfo()
|
||||||
|
}
|
||||||
|
|
||||||
// 登录成功后,2秒后自动更新状态
|
// 登录成功后,2秒后自动更新状态
|
||||||
if(globalLogin.value == true){
|
if(globalLogin.value == true){
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
|
|
@ -135,6 +152,15 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 绑定成功后,2秒后自动更新状态
|
||||||
|
if(globalBind.value == true){
|
||||||
|
GlobalScope.launch {
|
||||||
|
//延迟2秒,方便处理多有事件都收到通知
|
||||||
|
delay(2*1000)
|
||||||
|
GlobalStateManager(context).storeGlobalBindNotify(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 解绑成功后,2秒后自动更新状态
|
// 解绑成功后,2秒后自动更新状态
|
||||||
if(globalUnBind.value == true){
|
if(globalUnBind.value == true){
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
|
|
@ -195,15 +221,43 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isStartDownload = mutableStateOf(false)
|
||||||
if(showUpdateDialog){
|
val progressState = mutableFloatStateOf(0f)
|
||||||
|
if(globalUpdate.value == true){
|
||||||
UpdateDialog(
|
UpdateDialog(
|
||||||
title = "新版本,更新提示",
|
title = PreferenceUtil.getUserConfig()?.config?.versionEntity?.title?:"新版本,更新提示",
|
||||||
newVersion = "1.0.0",
|
newVersion = "V${PreferenceUtil.getUserConfig()?.config?.versionEntity?.version}",
|
||||||
desc = "修复了一些问题,新增了一些功能",
|
desc = PreferenceUtil.getUserConfig()?.config?.versionEntity?.description?:"",
|
||||||
url = "https://www.baidu.com",
|
url = PreferenceUtil.getUserConfig()?.config?.versionEntity?.url?:"",
|
||||||
){ state ->
|
scope = scope,
|
||||||
showUpdateDialog = state
|
isStartDown = isStartDownload,
|
||||||
|
downProgress = progressState
|
||||||
|
){ state, isCancel, url ->
|
||||||
|
if(isCancel) {
|
||||||
|
scope.launch {
|
||||||
|
GlobalStateManager(context).storeGlobalUpdateNotify(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!isCancel){
|
||||||
|
isStartDownload.value = true
|
||||||
|
UpdateUtils.download(
|
||||||
|
scope = scope,
|
||||||
|
url = url,
|
||||||
|
onProgress = {progress->
|
||||||
|
progressState.floatValue = progress.toFloat()/100f
|
||||||
|
},
|
||||||
|
onFinish = {isSuccess, filePath ->
|
||||||
|
if(isSuccess){
|
||||||
|
filePath?.let {
|
||||||
|
UpdateUtils.install(context,it)
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
GlobalStateManager(context).storeGlobalUpdateNotify(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -490,185 +544,6 @@ 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)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreenPreview() {
|
fun MainScreenPreview() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.img.rabbit.bean.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@kotlinx.serialization.Serializable
|
||||||
|
class ServiceWxLinkEntity {
|
||||||
|
var corpid = ""
|
||||||
|
|
||||||
|
@SerializedName("kf.address")
|
||||||
|
var address = ""
|
||||||
|
}
|
||||||
|
|
@ -106,10 +106,6 @@ fun LoginScreen(navController: NavHostController? = null, generalViewModel: Gene
|
||||||
if (loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token != null) {
|
if (loginViewModel.loginState.value !=null && loginViewModel.loginState.value?.data?.token != null) {
|
||||||
//登录成功
|
//登录成功
|
||||||
PreferenceUtil.saveAccessToken(loginViewModel.loginState.value?.data?.token)
|
PreferenceUtil.saveAccessToken(loginViewModel.loginState.value?.data?.token)
|
||||||
// 获取用户配置
|
|
||||||
loginViewModel.requestUserConfig()
|
|
||||||
// 获取用户信息
|
|
||||||
loginViewModel.requestUserInfo()
|
|
||||||
|
|
||||||
loginViewModel.setLogin(true)
|
loginViewModel.setLogin(true)
|
||||||
//更新登录状态
|
//更新登录状态
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,263 @@
|
||||||
|
package com.img.rabbit.pages.dialog
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
|
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.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
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 com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
|
import com.img.rabbit.R
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
@SuppressLint("UnrememberedMutableState")
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
@Composable
|
||||||
|
fun UpdateDialog(
|
||||||
|
title: String = "发现新版本~",
|
||||||
|
newVersion: String = "V1.0.0",
|
||||||
|
desc: String = "修复了一些问题,新增了一些功能",
|
||||||
|
url: String,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
isStartDown: MutableState<Boolean>,
|
||||||
|
downProgress: MutableState<Float>,
|
||||||
|
onStatusChange: (state: Boolean, isCancel: Boolean, url:String) -> Unit
|
||||||
|
){
|
||||||
|
val storagePermissionState = rememberPermissionState(
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
Manifest.permission.READ_MEDIA_IMAGES // Android 13+ 使用具体媒体权限
|
||||||
|
} else {
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE // 旧版本使用常规存储权限
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 = 26.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()
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = desc,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = Color(0xFF767676),
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.padding(12.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 18.dp, end = 18.dp, bottom = 20.dp)
|
||||||
|
) {
|
||||||
|
if(isStartDown.value){
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
// 使用 LinearProgressIndicator 显示确定性进度
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { downProgress.value }, // 使用 Lambda 更新进度0~1
|
||||||
|
modifier = Modifier.fillMaxWidth().height(8.dp)
|
||||||
|
)
|
||||||
|
Text(text = "下载进度: ${(downProgress.value * 100).toInt()}%")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
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, true, url)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
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() }
|
||||||
|
) {
|
||||||
|
// 执行下载更新
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
// isStartDown.value = true
|
||||||
|
onStatusChange(false, false, url)
|
||||||
|
}else{
|
||||||
|
when {
|
||||||
|
// 情况 A:已获得权限
|
||||||
|
storagePermissionState.status.isGranted -> {
|
||||||
|
// isStartDown.value = true
|
||||||
|
onStatusChange(false, false, url)
|
||||||
|
}
|
||||||
|
// 情况 B:需要向用户解释(之前拒绝过,但未勾选“不再询问”)
|
||||||
|
storagePermissionState.status.shouldShowRationale -> {
|
||||||
|
storagePermissionState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
// 情况 C:从未请求过权限或已被彻底拒绝
|
||||||
|
else -> {
|
||||||
|
storagePermissionState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"立即更新",
|
||||||
|
color = Color(0xFFC2FF43),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,8 +53,7 @@ import com.img.rabbit.provider.storage.PreferenceUtil
|
||||||
import com.img.rabbit.utils.AppUpdate
|
import com.img.rabbit.utils.AppUpdate
|
||||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||||
import com.img.rabbit.viewmodel.LoginViewModel
|
import com.img.rabbit.viewmodel.LoginViewModel
|
||||||
import kotlinx.coroutines.GlobalScope
|
import com.img.rabbit.viewmodel.MineViewModel
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -62,12 +61,14 @@ import kotlinx.coroutines.launch
|
||||||
@Composable
|
@Composable
|
||||||
fun MineScreen(
|
fun MineScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
|
viewModel: MineViewModel = viewModel(),
|
||||||
generalViewModel: GeneralViewModel,
|
generalViewModel: GeneralViewModel,
|
||||||
) {
|
) {
|
||||||
val TAG = "Rabbit_Mine"
|
val TAG = "Rabbit_Mine"
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var globalLogin by mutableStateOf(GlobalStateManager(context).globalLoginNotifyFlow().collectAsState(initial = false))
|
var globalLogin by mutableStateOf(GlobalStateManager(context).globalLoginNotifyFlow().collectAsState(initial = false))
|
||||||
|
var globalBind by mutableStateOf(GlobalStateManager(context).globalBindNotifyFlow().collectAsState(initial = false))
|
||||||
var globalUnBind by mutableStateOf(GlobalStateManager(context).globalUnBindNotifyFlow().collectAsState(initial = false))
|
var globalUnBind by mutableStateOf(GlobalStateManager(context).globalUnBindNotifyFlow().collectAsState(initial = false))
|
||||||
|
|
||||||
val vipMember by remember { mutableStateOf(false) }
|
val vipMember by remember { mutableStateOf(false) }
|
||||||
|
|
@ -85,7 +86,7 @@ fun MineScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
//刷新用户信息
|
//刷新用户信息
|
||||||
if(globalLogin.value == true || globalUnBind.value == true){
|
if(globalLogin.value == true || globalBind.value == true || globalUnBind.value == true){
|
||||||
scope.launch {
|
scope.launch {
|
||||||
delay(300)
|
delay(300)
|
||||||
userInfo = PreferenceUtil.getUserInfo()
|
userInfo = PreferenceUtil.getUserInfo()
|
||||||
|
|
@ -161,7 +162,7 @@ fun MineScreen(
|
||||||
// 跳转登录页面
|
// 跳转登录页面
|
||||||
navController.navigate("login?type=${LoginViewModel.JumpLoginType.NORMAL.type}")
|
navController.navigate("login?type=${LoginViewModel.JumpLoginType.NORMAL.type}")
|
||||||
} else {
|
} else {
|
||||||
//TODO 已登录,跳转个人信息页面
|
// 已登录,跳转个人信息页面
|
||||||
//navController.navigate("userInfo")
|
//navController.navigate("userInfo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -219,10 +220,11 @@ fun MineScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VIP bar
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
/*
|
||||||
|
// VIP bar
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -300,6 +302,7 @@ fun MineScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
//功能项
|
//功能项
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -381,10 +384,11 @@ fun MineScreen(
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
// 隐藏TabBar
|
if (!generalViewModel.api.isWXAppInstalled) {
|
||||||
generalViewModel.setNavigationBarVisible(false)
|
Toast.makeText(context, "未安装微信客户端", Toast.LENGTH_SHORT).show()
|
||||||
// 跳转在线客服页面
|
}else if(userInfo != null){
|
||||||
navController.navigate("onlineService")
|
viewModel.requestServiceLink(context, generalViewModel.api)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
@ -444,7 +448,9 @@ fun MineScreen(
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
//提示执行更新
|
//提示执行更新
|
||||||
//startUpdate(result, fragment)
|
//startUpdate(result, fragment)
|
||||||
Log.i(TAG, "checkUpdate: 有新版本, tips = $tips")
|
scope.launch {
|
||||||
|
GlobalStateManager(context).storeGlobalUpdateNotify(true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, tips, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, tips, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,6 @@ fun AccountBindScreen(navController: NavHostController, viewModel: AccountBindVi
|
||||||
|
|
||||||
LaunchedEffect(viewModel.unBindState.value) {
|
LaunchedEffect(viewModel.unBindState.value) {
|
||||||
if(viewModel.unBindState.value != null){
|
if(viewModel.unBindState.value != null){
|
||||||
// 获取用户配置
|
|
||||||
loginViewModel.requestUserConfig()
|
|
||||||
// 获取用户信息
|
|
||||||
loginViewModel.requestUserInfo()
|
|
||||||
|
|
||||||
Toast.makeText(context, "解绑成功!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "解绑成功!", Toast.LENGTH_SHORT).show()
|
||||||
viewModel.unBindState.value = null
|
viewModel.unBindState.value = null
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,6 @@ fun AccountManagerScreen(navController: NavHostController, viewModel: AccountMan
|
||||||
if (viewModel.switchState.value != null && viewModel.switchState.value?.data?.token != null) {
|
if (viewModel.switchState.value != null && viewModel.switchState.value?.data?.token != null) {
|
||||||
// 切换账号成功
|
// 切换账号成功
|
||||||
PreferenceUtil.saveAccessToken(viewModel.switchState.value?.data?.token)
|
PreferenceUtil.saveAccessToken(viewModel.switchState.value?.data?.token)
|
||||||
// 获取用户配置
|
|
||||||
loginViewModel.requestUserConfig()
|
|
||||||
// 获取用户信息
|
|
||||||
loginViewModel.requestUserInfo()
|
|
||||||
|
|
||||||
loginViewModel.setLogin(true)
|
loginViewModel.setLogin(true)
|
||||||
//更新登录状态
|
//更新登录状态
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,9 @@ fun BindScreen(navController: NavHostController, viewModel: BindViewModel = view
|
||||||
LaunchedEffect(viewModel.bindState.value) {
|
LaunchedEffect(viewModel.bindState.value) {
|
||||||
if (viewModel.bindState.value != null && viewModel.bindState.value?.data?.token != null) {
|
if (viewModel.bindState.value != null && viewModel.bindState.value?.data?.token != null) {
|
||||||
Toast.makeText(context, "绑定成功!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "绑定成功!", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
GlobalStateManager(context).storeGlobalBindNotify(true)
|
||||||
|
navController.popBackStack()
|
||||||
}else if (viewModel.bindState.value != null){
|
}else if (viewModel.bindState.value != null){
|
||||||
Toast.makeText(context, "绑定失败!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "绑定失败!", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ class GlobalStateManager(
|
||||||
private val GLOBAL_WX_AUTHORIZATION = stringPreferencesKey("global_wx_authorization")
|
private val GLOBAL_WX_AUTHORIZATION = stringPreferencesKey("global_wx_authorization")
|
||||||
private val GLOBAL_LOGIN_NOTIFY = booleanPreferencesKey("global_login_notify")
|
private val GLOBAL_LOGIN_NOTIFY = booleanPreferencesKey("global_login_notify")
|
||||||
private val GLOBAL_LOGOUT_NOTIFY = booleanPreferencesKey("global_logout_notify")
|
private val GLOBAL_LOGOUT_NOTIFY = booleanPreferencesKey("global_logout_notify")
|
||||||
|
private val GLOBAL_BIND_NOTIFY = booleanPreferencesKey("global_bind_notify")
|
||||||
private val GLOBAL_UNBIND_NOTIFY = booleanPreferencesKey("global_unbind_notify")
|
private val GLOBAL_UNBIND_NOTIFY = booleanPreferencesKey("global_unbind_notify")
|
||||||
|
private val GLOBAL_UPDATE_NOTIFY = booleanPreferencesKey("global_update_notify")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun storeGlobalLoading(value: Boolean) {
|
suspend fun storeGlobalLoading(value: Boolean) {
|
||||||
|
|
@ -63,6 +65,18 @@ class GlobalStateManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun storeGlobalBindNotify(value: Boolean) {
|
||||||
|
context.storeData.edit { preferences ->
|
||||||
|
preferences[GLOBAL_BIND_NOTIFY] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun globalBindNotifyFlow(): Flow<Boolean?> {
|
||||||
|
return context.storeData.data.map {
|
||||||
|
preferences ->
|
||||||
|
preferences[GLOBAL_BIND_NOTIFY]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun storeGlobalUnBindNotify(value: Boolean) {
|
suspend fun storeGlobalUnBindNotify(value: Boolean) {
|
||||||
context.storeData.edit { preferences ->
|
context.storeData.edit { preferences ->
|
||||||
preferences[GLOBAL_UNBIND_NOTIFY] = value
|
preferences[GLOBAL_UNBIND_NOTIFY] = value
|
||||||
|
|
@ -88,4 +102,18 @@ class GlobalStateManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun storeGlobalUpdateNotify(value: Boolean) {
|
||||||
|
context.storeData.edit { preferences ->
|
||||||
|
preferences[GLOBAL_UPDATE_NOTIFY] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun globalUpdateNotifyFlow(): Flow<Boolean?> {
|
||||||
|
return context.storeData.data.map {
|
||||||
|
preferences ->
|
||||||
|
preferences[GLOBAL_UPDATE_NOTIFY]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -48,4 +48,9 @@ object AppUpdate {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getFileNameFromUrl(url: String): String {
|
||||||
|
return url.substring(url.lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,385 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import okhttp3.*
|
||||||
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import java.io.*
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class DownLoadUtils private constructor() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "DownLoadUtils"
|
||||||
|
private val downLoadHttpUtils: DownLoadUtils by lazy {
|
||||||
|
DownLoadUtils()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Synchronized
|
||||||
|
fun getInstance(): DownLoadUtils {
|
||||||
|
return downLoadHttpUtils
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var downloadSizeInfo = mutableMapOf<String, Long>()
|
||||||
|
private var cancelledList = mutableListOf<String>()
|
||||||
|
|
||||||
|
private var buffSize = 4096//建议设置为2048
|
||||||
|
fun setBuffSize(size: Int): DownLoadUtils {
|
||||||
|
this.buffSize = size
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var interceptor: Interceptor? = null
|
||||||
|
fun setInterceptor(interceptor: Interceptor?): DownLoadUtils {
|
||||||
|
this.interceptor = interceptor
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var readTimeOut = 30L
|
||||||
|
fun setReadTImeOut(read: Long): DownLoadUtils {
|
||||||
|
this.readTimeOut = read
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var writeTimeout = 30L
|
||||||
|
fun setWriteTimeOut(write: Long): DownLoadUtils {
|
||||||
|
this.writeTimeout = write
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var connectTimeout = 30L
|
||||||
|
fun setConnectTimeOut(connect: Long): DownLoadUtils {
|
||||||
|
this.connectTimeout = connect
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = ""
|
||||||
|
fun setFilePath(path: String): DownLoadUtils {
|
||||||
|
this.filePath = path
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fileName = ""
|
||||||
|
fun setFileName(name: String): DownLoadUtils {
|
||||||
|
this.fileName = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var deleteWhenException = true
|
||||||
|
fun setDeleteWhenException(dele: Boolean): DownLoadUtils {
|
||||||
|
this.deleteWhenException = dele
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestBuilder: Request.Builder = Request.Builder()
|
||||||
|
private var urlBuilder: HttpUrl.Builder? = null
|
||||||
|
|
||||||
|
private val okHttpClient = lazy {
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.readTimeout(readTimeOut, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(writeTimeout, TimeUnit.SECONDS)
|
||||||
|
.connectTimeout(connectTimeout, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(interceptor ?: LoggingInterceptor())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
private var actionGetTotal: (total: Long) -> Unit? = { _ -> }
|
||||||
|
private var actionProgress: (position: Long) -> Unit? = { _ -> }
|
||||||
|
private var actionSuccess: (file: File) -> Unit? = { _ -> }
|
||||||
|
private var actionCancel: () -> Unit? = { }
|
||||||
|
private var actionFail: (msg: String) -> Unit? = { _ -> }
|
||||||
|
|
||||||
|
fun setActionCallBack(
|
||||||
|
actionGetTotal: (total: Long) -> Unit,
|
||||||
|
actionProgress: (position: Long) -> Unit,
|
||||||
|
actionSuccess: (file: File) -> Unit,
|
||||||
|
actionFail: (msg: String) -> Unit,
|
||||||
|
): DownLoadUtils {
|
||||||
|
this.actionGetTotal = actionGetTotal
|
||||||
|
this.actionProgress = actionProgress
|
||||||
|
this.actionSuccess = actionSuccess
|
||||||
|
this.actionFail = actionFail
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private var downCallBack: DownCallBack? = null
|
||||||
|
fun setDownCallBack(callBack: DownCallBack): DownLoadUtils {
|
||||||
|
this.downCallBack = callBack
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initUrl(url: String, params: Map<String, String>?): DownLoadUtils {
|
||||||
|
urlBuilder = url.toHttpUrlOrNull()?.newBuilder()
|
||||||
|
if (params.isNullOrEmpty()) {
|
||||||
|
return this
|
||||||
|
} else {
|
||||||
|
for ((k, v) in params) {
|
||||||
|
checkName(k)
|
||||||
|
urlBuilder?.setQueryParameter(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addHeader(map: Map<String, String>): DownLoadUtils {
|
||||||
|
requestBuilder.headers(map.toHeaders())
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkName(name: String) {
|
||||||
|
require(name.isNotEmpty()) { "name is empty" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun down() {
|
||||||
|
if (urlBuilder == null) {
|
||||||
|
throw IllegalStateException("url not init")
|
||||||
|
} else {
|
||||||
|
doDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doDown() {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
Log.i(TAG, "startTime=$startTime")
|
||||||
|
val url = urlBuilder?.build()
|
||||||
|
if (url == null) {
|
||||||
|
doException("url is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isDowning(filePath + fileName)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancelledList.remove(fileName)
|
||||||
|
|
||||||
|
val currentLen = downloadSizeInfo[fileName] ?: 0L
|
||||||
|
if (isCanContinueDownload(url.toUrl(), currentLen)) {
|
||||||
|
requestBuilder.removeHeader("RANGE")
|
||||||
|
requestBuilder.addHeader("RANGE", "bytes=${currentLen}-")
|
||||||
|
}
|
||||||
|
val request = requestBuilder.url(url).tag(filePath + fileName).build()
|
||||||
|
|
||||||
|
var `is`: InputStream? = null
|
||||||
|
var raf: RandomAccessFile? = null
|
||||||
|
var file: File? = null
|
||||||
|
try {
|
||||||
|
val response = okHttpClient.value.newCall(request).execute()
|
||||||
|
val total = response.body?.contentLength() ?: 0
|
||||||
|
doGetTotal(currentLen + total)
|
||||||
|
|
||||||
|
val buf = ByteArray(buffSize)
|
||||||
|
var len: Int
|
||||||
|
|
||||||
|
file = if (fileName.isEmpty()) {
|
||||||
|
File(filePath)
|
||||||
|
} else {
|
||||||
|
val fileDir = File(filePath)
|
||||||
|
if (!fileDir.exists() || !fileDir.isDirectory) {
|
||||||
|
fileDir.mkdirs()
|
||||||
|
}
|
||||||
|
File(filePath, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
`is` = response.body?.byteStream() ?: FileInputStream("")
|
||||||
|
raf = RandomAccessFile(file, "rw")
|
||||||
|
raf.seek(currentLen)
|
||||||
|
|
||||||
|
var sum: Long = currentLen
|
||||||
|
while (`is`.read(buf).also { len = it } != -1) {
|
||||||
|
if (isCancelled()) throw CancellationException()
|
||||||
|
raf.write(buf, 0, len)
|
||||||
|
sum += len.toLong()
|
||||||
|
downloadSizeInfo[fileName] = sum
|
||||||
|
Log.e(TAG, "download progress : $sum")
|
||||||
|
doProgress(sum)
|
||||||
|
}
|
||||||
|
Log.e(TAG, "download success")
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw FileNotFoundException("file create err,not exists")
|
||||||
|
} else {
|
||||||
|
doSuccess(file)
|
||||||
|
}
|
||||||
|
Log.e(TAG, "totalTime=" + (System.currentTimeMillis() - startTime))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (deleteWhenException && file?.exists() == true) file.delete()
|
||||||
|
Log.e(TAG, "download failed : " + e.message)
|
||||||
|
doException(e.message.toString())
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
`is`?.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
raf?.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCanContinueDownload(url: URL, start: Long): Boolean { //是否支持断点下载
|
||||||
|
val requestBuilder = Request.Builder()
|
||||||
|
requestBuilder.addHeader("RANGE", "bytes=$start-")
|
||||||
|
requestBuilder.addHeader("Connection", "close")
|
||||||
|
val request: Request = requestBuilder.url(url).head().build()
|
||||||
|
val response: Response = okHttpClient.value.newCall(request).execute()
|
||||||
|
return if (response.isSuccessful) {
|
||||||
|
if (response.code == 206) { //支持
|
||||||
|
response.close()
|
||||||
|
true
|
||||||
|
} else { //不支持
|
||||||
|
response.close()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.close()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isDowning(tag: String): Boolean {
|
||||||
|
for (call in okHttpClient.value.dispatcher.runningCalls()) {
|
||||||
|
if (call.request().tag() == tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCancelled(): Boolean {
|
||||||
|
return cancelledList.contains(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
cancel(filePath + fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(tag: String) {
|
||||||
|
cancelledList.add(fileName)
|
||||||
|
if (okHttpClient.value.dispatcher.runningCalls().isNotEmpty()) {
|
||||||
|
for (call in okHttpClient.value.dispatcher.runningCalls()) {
|
||||||
|
if (call.request().tag() == tag) {
|
||||||
|
call.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doException(err: String) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (downCallBack == null) {
|
||||||
|
actionFail.invoke(err)
|
||||||
|
} else {
|
||||||
|
downCallBack?.fail(err)
|
||||||
|
}
|
||||||
|
mainThread = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doSuccess(file: File?) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (file == null) {
|
||||||
|
doException("file not exit")
|
||||||
|
} else {
|
||||||
|
if (downCallBack == null) {
|
||||||
|
actionSuccess.invoke(file)
|
||||||
|
} else {
|
||||||
|
downCallBack?.success(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainThread = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doGetTotal(total: Long) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (downCallBack == null) {
|
||||||
|
actionGetTotal.invoke(total)
|
||||||
|
} else {
|
||||||
|
downCallBack?.total(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainThread = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doProgress(progress: Long) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (downCallBack == null) {
|
||||||
|
actionProgress.invoke(progress)
|
||||||
|
} else {
|
||||||
|
downCallBack?.progress(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doCancel() {
|
||||||
|
runOnUiThread {
|
||||||
|
if (downCallBack == null) {
|
||||||
|
actionCancel.invoke()
|
||||||
|
} else {
|
||||||
|
downCallBack?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainThread = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mainThread: Handler? = null
|
||||||
|
private fun runOnUiThread(action: () -> Unit) {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) { // If we finish marking off of the main thread, we need to
|
||||||
|
// actually do it on the main thread to ensure correct ordering.
|
||||||
|
if (mainThread == null) {
|
||||||
|
mainThread = Handler(Looper.getMainLooper())
|
||||||
|
}
|
||||||
|
mainThread?.post {
|
||||||
|
action.invoke()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
action.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingInterceptor : Interceptor {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request: Request = chain.request()
|
||||||
|
val startTime = System.nanoTime()
|
||||||
|
Log.d(
|
||||||
|
TAG, String.format(
|
||||||
|
"Sending request %s on %s%n%s",
|
||||||
|
request.url, chain.connection(), request.headers
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val response: Response = chain.proceed(request)
|
||||||
|
val endTime = System.nanoTime()
|
||||||
|
Log.d(
|
||||||
|
TAG, String.format(
|
||||||
|
"Received response for %s in %.1fms%n%s",
|
||||||
|
response.request.url, (endTime - startTime) / 1e6, response.headers
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "LoggingInterceptor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownCallBack {
|
||||||
|
fun success(file: File)
|
||||||
|
fun fail(str: String)
|
||||||
|
fun progress(position: Long)
|
||||||
|
fun total(total: Long)
|
||||||
|
fun cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
package com.img.rabbit.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.StatFs;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.img.rabbit.provider.utils.HeadParamUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings({"ResultOfMethodCallIgnored", "CallToPrintStackTrace"})
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
private static FileUtils instance;
|
||||||
|
private static String packageName = "devcon";
|
||||||
|
|
||||||
|
// 文件缓存路径
|
||||||
|
private String CACHE_DIR;
|
||||||
|
|
||||||
|
// 下载目录
|
||||||
|
private File downloadDir;
|
||||||
|
// 缓存目录
|
||||||
|
private File cacheDir;
|
||||||
|
// 图片缓存目录
|
||||||
|
private File cacheImageDir;
|
||||||
|
|
||||||
|
private File cacheOriginalImageDir;
|
||||||
|
|
||||||
|
private File cacheEditImageDir;
|
||||||
|
|
||||||
|
private File cachePuzzleImageDir;
|
||||||
|
|
||||||
|
public static FileUtils getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (FileUtils.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new FileUtils(Objects.requireNonNull(HeadParamUtils.INSTANCE.getApplicationContext()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileUtils(Context context) {
|
||||||
|
CACHE_DIR = Objects.requireNonNull(context.getExternalFilesDir(null)).getAbsolutePath() + File.separator + packageName + File.separator;
|
||||||
|
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||||
|
cacheDir = new File(CACHE_DIR, "/cache");
|
||||||
|
} else {
|
||||||
|
cacheDir = context.getCacheDir();
|
||||||
|
}
|
||||||
|
if (!cacheDir.exists())
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
cacheImageDir = new File(cacheDir, "/image/");
|
||||||
|
if (!cacheImageDir.exists())
|
||||||
|
cacheImageDir.mkdirs();
|
||||||
|
|
||||||
|
cacheOriginalImageDir = new File(cacheDir, "/originalImage/");
|
||||||
|
if (!cacheOriginalImageDir.exists())
|
||||||
|
cacheOriginalImageDir.mkdirs();
|
||||||
|
|
||||||
|
cacheEditImageDir = new File(cacheDir, "/cacheEditImage/");
|
||||||
|
if (!cacheEditImageDir.exists())
|
||||||
|
cacheEditImageDir.mkdirs();
|
||||||
|
|
||||||
|
cachePuzzleImageDir = new File(cacheDir, "/cachePuzzleImage/");
|
||||||
|
if (!cachePuzzleImageDir.exists())
|
||||||
|
cachePuzzleImageDir.mkdirs();
|
||||||
|
|
||||||
|
downloadDir = new File(cacheDir, "/download/");
|
||||||
|
if (!downloadDir.exists())
|
||||||
|
downloadDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCACHE_DIR() {
|
||||||
|
return CACHE_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存目录
|
||||||
|
*/
|
||||||
|
public File getCacheDir() {
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下载目录
|
||||||
|
*/
|
||||||
|
public File getCacheDownLoderDir() {
|
||||||
|
return downloadDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存图片目录
|
||||||
|
*/
|
||||||
|
public File getCacheImageDir() {
|
||||||
|
return cacheImageDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 水印照片编辑后的路径
|
||||||
|
*/
|
||||||
|
public File getCacheEditImageDir() {
|
||||||
|
return cacheEditImageDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getCacheOriginalImageDir() {
|
||||||
|
return cacheOriginalImageDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼图路径
|
||||||
|
*/
|
||||||
|
public File getCachePuzzleImageDir() {
|
||||||
|
return cachePuzzleImageDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个临时图片文件
|
||||||
|
*/
|
||||||
|
public File newTempImageFile() {
|
||||||
|
return new File(cacheImageDir, System.currentTimeMillis() + ".jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否安装SD卡
|
||||||
|
*/
|
||||||
|
public static boolean checkSaveLocationExists() {
|
||||||
|
String sDCardStatus = Environment.getExternalStorageState();
|
||||||
|
boolean status;
|
||||||
|
status = sDCardStatus.equals(Environment.MEDIA_MOUNTED);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定目录下文件及目录
|
||||||
|
*/
|
||||||
|
public static void deleteFolderFile(String filePath) {
|
||||||
|
if (!TextUtils.isEmpty(filePath)) {
|
||||||
|
try {
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (file.isDirectory()) {// 处理目录
|
||||||
|
File[] files = file.listFiles();
|
||||||
|
for (int i = 0; i < Objects.requireNonNull(files).length; i++) {
|
||||||
|
deleteFolderFile(files[i].getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!file.isDirectory()) {// 如果是文件,删除
|
||||||
|
file.delete();
|
||||||
|
} else {// 目录
|
||||||
|
if (Objects.requireNonNull(file.listFiles()).length == 0) {// 目录下没有文件或者目录,删除
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("FileUtils", Objects.requireNonNull(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*/
|
||||||
|
public boolean deleteFile(String path) {
|
||||||
|
boolean status;
|
||||||
|
SecurityManager checker = new SecurityManager();
|
||||||
|
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
File newPath = new File(path);
|
||||||
|
checker.checkDelete(newPath.toString());
|
||||||
|
if (newPath.isFile()) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
newPath.delete();
|
||||||
|
status = true;
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
Log.e("FileUtils", Objects.requireNonNull(se.getMessage()));
|
||||||
|
status = false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
status = false;
|
||||||
|
} else
|
||||||
|
status = false;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录文件大小
|
||||||
|
*/
|
||||||
|
public static long getDirSize(File dir) {
|
||||||
|
if (dir == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!dir.isDirectory()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
long dirSize = 0;
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
for (File file : Objects.requireNonNull(files)) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
dirSize += file.length();
|
||||||
|
} else if (file.isDirectory()) {
|
||||||
|
dirSize += file.length();
|
||||||
|
dirSize += getDirSize(file); // 递归调用继续统计
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定文件大小
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public static long getFileSize(File file) throws Exception {
|
||||||
|
long size = 0;
|
||||||
|
if (file.exists()) {
|
||||||
|
FileInputStream fis = null;
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
size = fis.available();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Log.e("获取文件大小", "文件不存在!");
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定文件夹
|
||||||
|
*/
|
||||||
|
public static long getFileSizes(File f) throws Exception {
|
||||||
|
long size = 0;
|
||||||
|
File[] flist = f.listFiles();
|
||||||
|
for (int i = 0; i < Objects.requireNonNull(flist).length; i++) {
|
||||||
|
if (flist[i].isDirectory()) {
|
||||||
|
size = size + getFileSizes(flist[i]);
|
||||||
|
} else {
|
||||||
|
size = size + getFileSize(flist[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换文件大小
|
||||||
|
*/
|
||||||
|
public static String toFileSize(long fileS) {
|
||||||
|
DecimalFormat df = new DecimalFormat("#.00");
|
||||||
|
String fileSizeString;
|
||||||
|
String wrongSize = "0M";
|
||||||
|
if (fileS == 0) {
|
||||||
|
return wrongSize;
|
||||||
|
}
|
||||||
|
if (fileS < 1024) {
|
||||||
|
fileSizeString = df.format((double) fileS) + "B";
|
||||||
|
} else if (fileS < 1048576) {
|
||||||
|
fileSizeString = df.format((double) fileS / 1024) + "K";
|
||||||
|
} else if (fileS < 1073741824) {
|
||||||
|
fileSizeString = df.format((double) fileS / 1048576) + "M";
|
||||||
|
} else {
|
||||||
|
fileSizeString = df.format((double) fileS / 1073741824) + "G";
|
||||||
|
}
|
||||||
|
return fileSizeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断文件是否存在
|
||||||
|
public boolean fileIsExists(String strFile) {
|
||||||
|
try {
|
||||||
|
File f = new File(strFile);
|
||||||
|
if (!f.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//外部存储空间
|
||||||
|
public static long getExternalStorageSpace() {
|
||||||
|
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||||
|
StatFs externalStatFs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||||
|
long externalBlockSize = externalStatFs.getBlockSizeLong();
|
||||||
|
long externalTotalSize = externalStatFs.getBlockCountLong() * externalBlockSize;
|
||||||
|
long externalAvailableSize = externalStatFs.getAvailableBlocksLong() * externalBlockSize;
|
||||||
|
Log.d("FileUtils", "当前外部空间总大小----" + externalTotalSize);
|
||||||
|
Log.d("FileUtils", "当前外部空间可用大小----" + externalAvailableSize);
|
||||||
|
return externalAvailableSize;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开图库
|
||||||
|
*/
|
||||||
|
public void openGallery(Context context) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addCategory(Intent.CATEGORY_APP_GALLERY);
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.img.rabbit.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object UpdateUtils {
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun download(scope: CoroutineScope, url: String, onProgress:(progress:Int)-> Unit, onFinish:(isSuccess: Boolean, filePath: String?)-> Unit) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
var totalProgress = 0L
|
||||||
|
DownLoadUtils.getInstance()
|
||||||
|
.setReadTImeOut(10L)
|
||||||
|
.setDeleteWhenException(false)
|
||||||
|
.initUrl(url, null)
|
||||||
|
.setFilePath(FileUtils.getInstance().cacheDownLoderDir.absolutePath)
|
||||||
|
.setFileName(AppUpdate.getFileNameFromUrl(url))
|
||||||
|
.setActionCallBack(
|
||||||
|
{ totalProgress = it },
|
||||||
|
{
|
||||||
|
val percent = it.toDouble() / totalProgress.toDouble() * 100
|
||||||
|
val curProgress = percent.toInt()
|
||||||
|
onProgress(curProgress)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onFinish(true, it.absolutePath)
|
||||||
|
}, {
|
||||||
|
onFinish(false, null)
|
||||||
|
}).down()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install(context: Context, apkFilePath: String) {
|
||||||
|
try {
|
||||||
|
val apkFile = File(FileUtils.getInstance().cacheDownLoderDir, AppUpdate.getFileNameFromUrl(apkFilePath))
|
||||||
|
if (!apkFile.exists()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val apkUri = FileProvider.getUriForFile(context, context.packageName + ".fileProvider", apkFile)
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -93,7 +93,8 @@ class LoginViewModel : BaseViewModel() {
|
||||||
fun requestUserConfig(){
|
fun requestUserConfig(){
|
||||||
mLaunch {
|
mLaunch {
|
||||||
val oaid = MMKVUtils.getString("oaid") ?: ""
|
val oaid = MMKVUtils.getString("oaid") ?: ""
|
||||||
val response = ApiManager.serviceVo.getUserConfig(oaid, Build.VERSION.SDK_INT, "", DeviceIdentifier.getAndroidID(applicationContext), MMKVUtils.getString("gt_cid") ?: "")
|
//val response = ApiManager.serviceVo.getUserConfig(oaid, Build.VERSION.SDK_INT, "", DeviceIdentifier.getAndroidID(applicationContext), MMKVUtils.getString("gt_cid") ?: "")
|
||||||
|
val response = ApiManager.serviceVo.getUserConfig()
|
||||||
if (response.status) {
|
if (response.status) {
|
||||||
PreferenceUtil.saveXToken(response.data.token)
|
PreferenceUtil.saveXToken(response.data.token)
|
||||||
PreferenceUtil.setTimeDiff(response.data.nowtime.toLong() - System.currentTimeMillis() / 1000)
|
PreferenceUtil.setTimeDiff(response.data.nowtime.toLong() - System.currentTimeMillis() / 1000)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.img.rabbit.viewmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.img.rabbit.bean.response.ServiceWxLinkEntity
|
||||||
|
import com.img.rabbit.provider.api.ApiManager
|
||||||
|
import com.tencent.mm.opensdk.constants.Build
|
||||||
|
import com.tencent.mm.opensdk.modelbiz.WXOpenCustomerServiceChat
|
||||||
|
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||||
|
|
||||||
|
class MineViewModel : BaseViewModel() {
|
||||||
|
private val TAG = "MineViewModel"
|
||||||
|
|
||||||
|
//请求客服连接
|
||||||
|
fun requestServiceLink(context: Context,api: IWXAPI){
|
||||||
|
mLaunch {
|
||||||
|
val response = ApiManager.serviceVo.wxService()
|
||||||
|
if (response.status) {
|
||||||
|
contactClientService(response.data,context,api)
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
isLoading.value = false // 加载完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun contactClientService(data: ServiceWxLinkEntity,context: Context,api: IWXAPI) {
|
||||||
|
if (api.wxAppSupportAPI >= Build.SUPPORT_OPEN_CUSTOMER_SERVICE_CHAT) {
|
||||||
|
val req = WXOpenCustomerServiceChat.Req()
|
||||||
|
req.corpId = data.corpid
|
||||||
|
req.url = data.address
|
||||||
|
api.sendReq(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.img.rabbit.bean.response.AlipayParamEntity
|
||||||
import com.img.rabbit.bean.response.CaptchaCodeEntity
|
import com.img.rabbit.bean.response.CaptchaCodeEntity
|
||||||
import com.img.rabbit.bean.response.UploadFileEntity
|
import com.img.rabbit.bean.response.UploadFileEntity
|
||||||
import com.img.rabbit.bean.response.LoginInfoEntity
|
import com.img.rabbit.bean.response.LoginInfoEntity
|
||||||
|
import com.img.rabbit.bean.response.ServiceWxLinkEntity
|
||||||
import com.img.rabbit.bean.response.UserConfigEntity
|
import com.img.rabbit.bean.response.UserConfigEntity
|
||||||
import com.img.rabbit.bean.response.UserInfoEntity
|
import com.img.rabbit.bean.response.UserInfoEntity
|
||||||
import com.img.rabbit.provider.api.ResultVo
|
import com.img.rabbit.provider.api.ResultVo
|
||||||
|
|
@ -38,11 +39,11 @@ interface ServiceVo {
|
||||||
*/
|
*/
|
||||||
@GET("/api/user/config")
|
@GET("/api/user/config")
|
||||||
suspend fun getUserConfig(
|
suspend fun getUserConfig(
|
||||||
@Query("oaid") oaid: String,
|
// @Query("oaid") oaid: String,
|
||||||
@Query("os_version") osVersion: Int,
|
// @Query("os_version") osVersion: Int,
|
||||||
@Query("ua") ua: String,
|
// @Query("ua") ua: String,
|
||||||
@Query("imei") imei: String,
|
// @Query("imei") imei: String,
|
||||||
@Query("cid") cid: String,
|
// @Query("cid") cid: String,
|
||||||
): ResultVo<UserConfigEntity>
|
): ResultVo<UserConfigEntity>
|
||||||
|
|
||||||
@GET("api/alipay/app_param")
|
@GET("api/alipay/app_param")
|
||||||
|
|
@ -50,6 +51,7 @@ interface ServiceVo {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送验证码
|
* 发送验证码
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
@POST("api/user/code")
|
@POST("api/user/code")
|
||||||
suspend fun sendCode(@Body requestBody: RequestBody): ResultVo<CaptchaCodeEntity>
|
suspend fun sendCode(@Body requestBody: RequestBody): ResultVo<CaptchaCodeEntity>
|
||||||
|
|
@ -103,4 +105,10 @@ interface ServiceVo {
|
||||||
*/
|
*/
|
||||||
@POST("/api/user/feedback")
|
@POST("/api/user/feedback")
|
||||||
suspend fun feedback(@Body requestBody: RequestBody): ResultVo<Any>
|
suspend fun feedback(@Body requestBody: RequestBody): ResultVo<Any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系客服(获取客服连接)
|
||||||
|
*/
|
||||||
|
@GET("/api/weixin/service")
|
||||||
|
suspend fun wxService(): ResultVo<ServiceWxLinkEntity>
|
||||||
}
|
}
|
||||||
|
|
@ -47,4 +47,5 @@
|
||||||
<cache-path
|
<cache-path
|
||||||
name="cache"
|
name="cache"
|
||||||
path="." />
|
path="." />
|
||||||
|
|
||||||
</paths>
|
</paths>
|
||||||
Loading…
Reference in New Issue