parent
8dfffa0c95
commit
5278050b1c
|
|
@ -56,6 +56,7 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.img.rabbit.components.GlobalToast
|
||||
import com.img.rabbit.config.Constants
|
||||
import com.img.rabbit.config.Constants.agreementUrl
|
||||
import com.img.rabbit.config.Constants.privacyUrl
|
||||
|
|
@ -406,7 +407,10 @@ class MainActivity : ComponentActivity(), LoadingCallback {
|
|||
fun AppTheme(content: @Composable () -> Unit) {
|
||||
// 使用Material3主题
|
||||
MaterialTheme {
|
||||
// 界面内容
|
||||
content()
|
||||
// 全局居中Toast
|
||||
GlobalToast()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
package com.img.rabbit.components
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.snapshots.Snapshot
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object CenterToast {
|
||||
private var _message = mutableStateOf("")
|
||||
private var _isVisible = mutableStateOf(false)
|
||||
|
||||
val message: State<String> = _message
|
||||
val isVisible: State<Boolean> = _isVisible
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private var job: Job? = null
|
||||
|
||||
fun show(msg: String) {
|
||||
job?.cancel()
|
||||
|
||||
_message.value = msg
|
||||
_isVisible.value = true
|
||||
|
||||
job = scope.launch {
|
||||
delay(2000)
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
_isVisible.value = false
|
||||
Snapshot.sendApplyNotifications()
|
||||
}
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GlobalToast() {
|
||||
val isVisible = CenterToast.isVisible.value
|
||||
val message = CenterToast.message.value
|
||||
|
||||
val alpha by animateFloatAsState(
|
||||
targetValue = if (isVisible) 1f else 0f,
|
||||
animationSpec = tween(durationMillis = 300),
|
||||
label = "toastAlpha"
|
||||
)
|
||||
|
||||
if (alpha > 0f) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(alpha = alpha)
|
||||
.zIndex(999f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Surface(
|
||||
color = Color.Black.copy(alpha = 0.8f),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = message,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -2,12 +2,15 @@ package com.img.rabbit.pages
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
|
@ -36,6 +39,7 @@ import androidx.navigation.NavType
|
|||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.navArgument
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.components.CenterToast
|
||||
import com.img.rabbit.pages.screen.HomeScreen
|
||||
import com.img.rabbit.pages.screen.MineScreen
|
||||
import com.img.rabbit.pages.screen.make.CutoutScreen
|
||||
|
|
@ -92,7 +96,7 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
//延迟500ms,确保页面初始化完成后,再跳转网络错误页面
|
||||
delay(500)
|
||||
|
||||
Toast.makeText(context, "网络已断开,请检查网络设置!", Toast.LENGTH_SHORT).show()
|
||||
CenterToast.show("网络已断开,请检查网络设置!")
|
||||
/*
|
||||
navController.navigate("netError")
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
|
|
@ -110,6 +114,9 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
Scaffold(
|
||||
bottomBar = {
|
||||
if (isNavigationBarVisible) {
|
||||
Column(
|
||||
modifier = Modifier.navigationBarsPadding()
|
||||
) {
|
||||
Box(modifier = Modifier.height(52.dp)) {
|
||||
NavigationBar(
|
||||
containerColor = Color.White,
|
||||
|
|
@ -148,6 +155,7 @@ fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewMode
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
|
|||
|
|
@ -140,10 +140,7 @@ fun HomeScreen(
|
|||
//大于9时,每排展示3个,否则每排展示4个
|
||||
val columnsCount = if((homeIconConfig?.size?:0) >= 9){ 3 }else{ 4 }
|
||||
val rows = homeIconConfig?.chunked(columnsCount)?:emptyList()
|
||||
|
||||
item {
|
||||
Column(
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_top_mask),
|
||||
contentDescription = null,
|
||||
|
|
@ -153,7 +150,23 @@ fun HomeScreen(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-17).dp)
|
||||
.offset(y = (-18).dp)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||
)
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth().height(18.dp))
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Column(
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-36).dp)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||
|
|
@ -440,7 +453,11 @@ fun HomeScreen(
|
|||
}
|
||||
items(rows) {rowItems ->
|
||||
//通过配置展示-AD2
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-36).dp)
|
||||
) {
|
||||
for (item in rowItems) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -504,6 +521,11 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-36).dp)
|
||||
) {
|
||||
//选尺寸制作
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_1_size),
|
||||
|
|
@ -768,8 +790,14 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
//选证件制作
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-36).dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_2_certificate),
|
||||
contentDescription = null,
|
||||
|
|
@ -1240,8 +1268,14 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
//其他
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-36).dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_3_other),
|
||||
contentDescription = null,
|
||||
|
|
@ -1444,6 +1478,7 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class ResponseInterceptor : Interceptor {
|
|||
var decryString: String?
|
||||
try {
|
||||
decryString = AESpkcs7paddingUtil.decryptNormal(decrybody, Constants.AESDecrypt)
|
||||
Log.e("ResponseInterceptor", "解密后返回的字符串为---->$decryString")
|
||||
Log.w("ResponseInterceptor", "解密后返回的字符串为---->$decryString")
|
||||
|
||||
//这里可以通过code处理token过去过期的情况
|
||||
//val decCode = JSONObject(decryString).optInt("code")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.dcloud.android.downloader.domain.DownloadInfo
|
||||
import com.github.gzuliyujiang.oaid.DeviceIdentifier
|
||||
import com.google.gson.Gson
|
||||
import com.img.rabbit.BuildConfig
|
||||
import com.img.rabbit.bean.response.UniVersionEntity
|
||||
import com.img.rabbit.components.CenterToast
|
||||
import com.img.rabbit.config.Constants
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil
|
||||
import com.img.rabbit.provider.storage.PreferenceUtil.getBDVID
|
||||
|
|
@ -20,14 +27,22 @@ import io.dcloud.feature.sdk.Interface.IUniMP
|
|||
import io.dcloud.feature.unimp.config.UniMPOpenConfiguration
|
||||
import io.dcloud.feature.unimp.config.UniMPReleaseConfiguration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.jvm.java
|
||||
|
||||
|
||||
object UniAppUtils {
|
||||
private const val TAG = "UniAppUtils"
|
||||
/**
|
||||
* 所有运行的UniMp小程序实体
|
||||
*/
|
||||
|
|
@ -139,10 +154,10 @@ object UniAppUtils {
|
|||
private fun startUniMp(context: Context, uniVersion: UniVersionEntity, onResult:(loading: Boolean) -> Unit){
|
||||
val uniMp = _uniMpFlow.value[uniVersion.unimp_id]//uniMpPair[uniVersion.unimp_id]
|
||||
if(uniMp?.isRuning == true){
|
||||
Log.i("UniAppUtils", "startUniMp: 运行中...")
|
||||
Log.i(TAG, "startUniMp: 运行中...")
|
||||
uniMp.showUniMP()
|
||||
}else{
|
||||
Log.i("UniAppUtils", "startUniMp: 重新加载...")
|
||||
Log.i(TAG, "startUniMp: 重新加载...")
|
||||
val configuration = getUniMPOpenConfiguration()
|
||||
if(uniVersion.unimp_type == "wx"){
|
||||
configuration.splashClass = UniMPWxSplashView::class.java
|
||||
|
|
@ -200,23 +215,28 @@ object UniAppUtils {
|
|||
|
||||
//释放资源
|
||||
private fun releaseWgt(uniMpId: String, onReleaseWgt: (isSuccess: Boolean) -> Unit) {
|
||||
val appBasePath = DCUniMPSDK.getInstance().getAppBasePath(applicationContext)
|
||||
val deleteSuccess = File(appBasePath, uniMpId).deleteRecursively()
|
||||
if(deleteSuccess){
|
||||
val wgtName = String.format("%s.wgt", uniMpId)
|
||||
val wgtFile = File(FileUtils.getInstance().cacheUniAppDir.absolutePath, wgtName)
|
||||
val uniMPReleaseConfiguration = UniMPReleaseConfiguration().apply {
|
||||
wgtPath = wgtFile.path
|
||||
password = "6462"////没有密码可以不写
|
||||
}
|
||||
DCUniMPSDK.getInstance().releaseWgtToRunPath(uniMpId, uniMPReleaseConfiguration
|
||||
) { code, _ ->
|
||||
DCUniMPSDK.getInstance().releaseWgtToRunPath(uniMpId, uniMPReleaseConfiguration) { code, _ ->
|
||||
if (code == 1) {
|
||||
//释放wgt完成
|
||||
onReleaseWgt(true)
|
||||
} else {
|
||||
//释放wgt失败
|
||||
Toast.makeText(applicationContext, "小程序加载失败,请清除缓存后重试!", Toast.LENGTH_SHORT).show()
|
||||
CenterToast.show("小程序加载失败,请清除缓存后重试!")
|
||||
onReleaseWgt(false)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
CenterToast.show("资源释放失败,请手动删除小程序运行文件!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -242,36 +262,22 @@ object UniAppUtils {
|
|||
* 下载并释放资源(但不会启动)
|
||||
*/
|
||||
fun downloadReleaseWgt(scope: CoroutineScope, uniVersion: UniVersionEntity, onProgress:(state: UniMpUpdate, progress: Float?) -> Unit,onRelease:(isSuccess: Boolean) -> Unit){
|
||||
val uniMpID = uniVersion.unimp_id
|
||||
val wgtName = String.format("%s.wgt", uniMpID)
|
||||
val wgtFile = File(FileUtils.getInstance().cacheUniAppDir.absolutePath, wgtName)
|
||||
|
||||
val uniMpId = uniVersion.unimp_id
|
||||
onProgress(UniMpUpdate.DOWNLOAD_START, 0f)
|
||||
downloadUniMp(scope, uniVersion){uniState, _, progress ->
|
||||
onProgress(UniMpUpdate.DOWNLOAD_LOADING, progress)
|
||||
if(uniState == UniMpUpdate.DOWNLOAD_FINISH){
|
||||
PreferenceUtil.saveWgtVersion(uniVersion.unimp_id, uniVersion.version)
|
||||
PreferenceUtil.saveWgtVersion(uniMpId, uniVersion.version)
|
||||
onProgress(UniMpUpdate.DOWNLOAD_FINISH, 1f)
|
||||
scope.launch {
|
||||
val uniMPReleaseConfiguration = UniMPReleaseConfiguration().apply {
|
||||
wgtPath = wgtFile.path
|
||||
password = "6462"////没有密码可以不写
|
||||
}
|
||||
DCUniMPSDK.getInstance().releaseWgtToRunPath(uniMpID, uniMPReleaseConfiguration
|
||||
) { code, _ ->
|
||||
if (code == 1) {
|
||||
//释放wgt完成
|
||||
Log.i("UniAppUtils", "下载并释放wgt完成!")
|
||||
releaseWgt(uniMpId){ isSuccess ->
|
||||
if(isSuccess){
|
||||
onRelease(true)
|
||||
}else{
|
||||
//释放wgt失败
|
||||
Log.i("UniAppUtils", "释放wgt失败...")
|
||||
onRelease(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if(uniState == UniMpUpdate.DOWNLOAD_FAIL){
|
||||
Log.i("UniAppUtils", "下载wgt失败...")
|
||||
Log.i(TAG, "下载wgt失败...")
|
||||
onProgress(UniMpUpdate.DOWNLOAD_FAIL, -1f)
|
||||
}
|
||||
}
|
||||
|
|
@ -287,26 +293,52 @@ object UniAppUtils {
|
|||
val uniMpID = uniVersion.unimp_id
|
||||
val wgtName = String.format("%s.wgt", uniMpID)
|
||||
val path = FileUtils.getInstance().cacheUniAppDir.absolutePath
|
||||
Log.i("UniAppUtils", "下载wgt---->downloadUniMp: $path/$wgtName ------>${uniVersion.url}")
|
||||
//先删除旧文件
|
||||
val oldFile = File(path, wgtName)
|
||||
if(oldFile.exists()){
|
||||
oldFile.delete()
|
||||
}
|
||||
onProgress(UniMpUpdate.DOWNLOAD_LOADING, null, 0.01f)
|
||||
|
||||
scope.launch {
|
||||
val isAvailable = isFileDownloadable(uniVersion.url)
|
||||
if(!isAvailable){
|
||||
Log.i(TAG, "下载失败,无效地址")
|
||||
onProgress(UniMpUpdate.DOWNLOAD_FAIL, null, -1f)
|
||||
CenterToast.show("下载失败...")
|
||||
}else{
|
||||
Log.i(TAG, "下载wgt---->downloadUniMp: $path/$wgtName ------>${uniVersion.url}")
|
||||
UpdateUtils.download(
|
||||
scope = scope,
|
||||
url = uniVersion.url,
|
||||
filePath = path,
|
||||
fileName = wgtName,
|
||||
onProgress = {progress->
|
||||
onProgress(UniMpUpdate.DOWNLOAD_LOADING, null, progress.toFloat()/100f)
|
||||
if(progress.toFloat()/100f>0.1f) {
|
||||
onProgress(
|
||||
UniMpUpdate.DOWNLOAD_LOADING,
|
||||
null,
|
||||
progress.toFloat() / 100f
|
||||
)
|
||||
}
|
||||
},
|
||||
onFinish = {isSuccess, filePath ->
|
||||
if(isSuccess){
|
||||
Log.i("UniAppUtils", "下载完成---->updateUniMp: $filePath")
|
||||
Log.i(TAG, "下载完成---->updateUniMp: $filePath")
|
||||
onProgress(UniMpUpdate.DOWNLOAD_FINISH, filePath, 1f)
|
||||
}else{
|
||||
Log.i("UniAppUtils", "下载失败---->updateUniMp: $filePath")
|
||||
Log.i(TAG, "下载失败---->updateUniMp: $filePath")
|
||||
onProgress(UniMpUpdate.DOWNLOAD_FAIL, filePath, -1f)
|
||||
|
||||
CenterToast.show("下载失败...")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun getUniMPOpenConfiguration(): UniMPOpenConfiguration{
|
||||
val stringBuilder = StringBuilder()
|
||||
|
|
@ -354,6 +386,36 @@ object UniAppUtils {
|
|||
req.miniprogramType = WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE// 可选打开 开发版,体验版和正式版
|
||||
api.sendReq(req)
|
||||
}
|
||||
|
||||
suspend fun isFileDownloadable(url: String): Boolean = withContext(Dispatchers.IO) {
|
||||
val client = OkHttpClient()
|
||||
val request = Request.Builder().url(url).build()
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
val fileSize = response.body.contentLength()
|
||||
//下载文件小于1KB,需要判断Url是否可靠
|
||||
if(fileSize < 1024){
|
||||
if (!response.isSuccessful) {
|
||||
val errorBody = response.body.string()
|
||||
if (response.header("Content-Type")?.contains("application/json") == true) {
|
||||
Log.i(TAG, "下载失败...$errorBody")
|
||||
}else{
|
||||
Log.i(TAG, "服务器错误...$errorBody")
|
||||
}
|
||||
return@withContext false
|
||||
}else{
|
||||
Log.i(TAG, "文件正常,可以下载!${response.body.string()}")
|
||||
return@withContext true
|
||||
}
|
||||
}else{
|
||||
return@withContext true
|
||||
}
|
||||
}catch (e: Exception) {
|
||||
Log.i(TAG, "异常:${e.message}")
|
||||
}
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class UniMpUpdate{
|
||||
|
|
|
|||
Loading…
Reference in New Issue