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

671 lines
30 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.img.rabbit
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
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.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.padding
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.foundation.text.ClickableText
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
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.mutableFloatStateOf
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
import androidx.compose.ui.draw.scale
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.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.img.rabbit.config.Constants
import com.img.rabbit.config.Constants.agreementUrl
import com.img.rabbit.config.Constants.privacyUrl
import com.img.rabbit.pages.LoadingCallback
import com.img.rabbit.pages.LoginScreen
import com.img.rabbit.pages.LoginScreenType
import com.img.rabbit.pages.MainScreen
import com.img.rabbit.pages.dialog.TipsUniMpDialog
import com.img.rabbit.pages.dialog.TipsUniMpToPageDialog
import com.img.rabbit.pages.dialog.UpdateDialog
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.AppEventBus
import com.img.rabbit.utils.AppUpdate
import com.img.rabbit.utils.ChannelUtils
import com.img.rabbit.utils.FileUtils
import com.img.rabbit.utils.LoginBindEvent
import com.img.rabbit.utils.UniAppUtils
import com.img.rabbit.utils.UniMpUpdate
import com.img.rabbit.utils.UpdateUtils
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
import com.img.rabbit.viewmodel.GeneralViewModel
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.delay
import kotlinx.coroutines.launch
import kotlin.system.exitProcess
class MainActivity : ComponentActivity(), LoadingCallback {
private lateinit var generalViewModel: GeneralViewModel
@OptIn(DelicateCoroutinesApi::class, ExperimentalPermissionsApi::class)
@SuppressLint("UnrememberedMutableState", "CoroutineCreationDuringComposition")
override fun onCreate(savedInstanceState: Bundle?) {
// 必须在 super.onCreate 之前调用
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// 启用Edge-to-Edge模式沉浸模式
enableEdgeToEdge()
setContent {
val coroutineScope = rememberCoroutineScope()
var showSplash by remember { mutableStateOf(false) }
val context = LocalContext.current
val loginViewModel: LoginViewModel = viewModel()
val splashViewModel: SplashViewModel = viewModel()
generalViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(application))[GeneralViewModel::class.java]
var updateAppNotify by mutableStateOf(GlobalStateManager(context).globalUpdateNotifyFlow().collectAsState(initial = false))
val updateUniDownloadNotify by GlobalStateManager(context).globalUniDownloadNotifyFlow().collectAsState(initial = false)
val updateUserConfigNotify by GlobalStateManager(context).globalUserConfigNotifyFlow().collectAsState(initial = false)
val updateUniUpdateNotify by GlobalStateManager(context).globalUniUpdateNotifyFlow().collectAsState(initial = false)
val updateGlobalLoadingNotify by GlobalStateManager(context).isGlobalLoadingFlow().collectAsState(initial = false)
// 设置启动页显示条件
splashScreen.setKeepOnScreenCondition {
splashViewModel.isLoading.value // 当为 true 时,启动页不消失
}
LaunchedEffect(generalViewModel.agreementStatus.value) {
if (generalViewModel.agreementStatus.value == true){
saveBDVID()
//获取服务器时间
generalViewModel.getServerTime()
}
}
LaunchedEffect(generalViewModel.serverTime.value) {
if (generalViewModel.serverTime.value != null){
// 获取用户配置
loginViewModel.requestUserConfig()
initUM()
}
}
LaunchedEffect(loginViewModel.userConfigResult.value) {
if (loginViewModel.userConfigResult.value != null){
// 用户配置信息获取成功
loginViewModel.requestUserInfo()
loginViewModel.userConfigResult.value = null
}
}
// 处理全局事件类似与EventBus订阅
LaunchedEffect(Unit) {
AppEventBus.events.collect { event ->
when (event) {
is LoginBindEvent.Login -> {
if(!event.isLogin){
loginViewModel.reset()
}
loginViewModel.requestUserConfig()
}
is LoginBindEvent.Bind -> {
loginViewModel.requestUserConfig()
}
}
}
}
//还原全局用户配置通知
LaunchedEffect(updateUserConfigNotify) {
delay(300)
if(updateUserConfigNotify == true){
coroutineScope.launch {
GlobalStateManager(context).storeGlobalUserConfigNotify(false)
}
}
}
AppTheme {
SplashScreenContent{
//未同意提示政策弹窗
if (generalViewModel.agreementStatus.value == false){
//同意继续
PrivacyPolicyScreen(
viewModel = loginViewModel,
) { isAllowPrivacyPolicy ->
if (isAllowPrivacyPolicy) {
generalViewModel.setIsAgreement(true)
loginViewModel.setIsPolicyAgreement(true)
showSplash = true
} else {
// 不同意隐私协议政策,直接退出应用
(context as MainActivity).finish()
// 强制退出应用进程
exitProcess(0)
}
}
}else{
showSplash = true
}
if(showSplash){ //|| globalLogout.value == true){
// 全局注销,重新登录
// if(globalLogout.value == true){
// loginViewModel = viewModel()
// loginViewModel.requestUserConfig()
// }
val token = PreferenceUtil.getAccessToken()
// 未登录,显示登录页
if (token.isNullOrEmpty() && loginViewModel.userConfigResult.value == null) {
// 同意隐私协议政策,检验是否有一键登录权限
loginViewModel.oneKeyLoginForGeTuiSdk(context as Activity) { isAllowShowOneKeyScreen ->
if (isAllowShowOneKeyScreen) {
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_ONE_KEY
} else {
// 检验是否有一键登录权限失败,显示验证码登录
loginViewModel.loginScreenType.value = LoginScreenType.LOGIN_CAPTCHA
}
}
// 显示登录页
LoginScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel, isVisibilityBreak = false)
} else {
//已登录,显示主页面
MainScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
}
}
//提示下载小程序资源(在跳转指定页面时,未下载资源需要提示)
val progressWGTToPageState = mutableFloatStateOf(0f)
if(updateUniDownloadNotify == true){
UniAppUtils.currentDownloadUniMp?.let {
UniAppUtils.downloadReleaseWgt(
coroutineScope,
it,
onProgress = { uniState, progress ->
when (uniState) {
UniMpUpdate.DOWNLOAD_START -> {
//资源开始下载
progressWGTToPageState.floatValue = 0f
Log.i("HomeScreen","DOWNLOAD_START")
}
UniMpUpdate.DOWNLOAD_FINISH -> {
//资源下载完成
progressWGTToPageState.floatValue = 1f
Log.i("HomeScreen","DOWNLOAD_FINISH")
coroutineScope.launch {
GlobalStateManager(context).storeGlobalUniDownloadNotify(false)
}
}
UniMpUpdate.DOWNLOAD_FAIL -> {
//资源下载失败
progressWGTToPageState.floatValue = -1f
Log.i("HomeScreen","DOWNLOAD_FAIL")
coroutineScope.launch {
GlobalStateManager(context).storeGlobalUniDownloadNotify(false)
}
}
else -> {
//资源下载进度
if(progress != null){
progressWGTToPageState.floatValue = progress
Log.i("HomeScreen","DOWNLOAD_PROGRESS:$progress")
}
}
}
},
onRelease = {
//资源下载完成后,启动小程序到指定位置
val uniMpEntity = UniAppUtils.currentDownloadUniMp
if(uniMpEntity!=null){
UniAppUtils.startUniMpPage(context = context, uniMpId = uniMpEntity.unimp_id, uniMpType = uniMpEntity.unimp_type, pagePath = UniAppUtils.currentUniMpJumpPatch?:"")
}
}
)
TipsUniMpToPageDialog(
title = "下载资源",
content1 = "需要下载完资源才能运行,请稍后...",
content2 = null,
downProgress = progressWGTToPageState
)
}
}
//UniApp更新提示
val isDownloadingWGT = mutableStateOf(false)
val progressWGTState = mutableFloatStateOf(0f)
if(updateUniUpdateNotify == true){
UniAppUtils.currentUpdateUniMp?.let {
TipsUniMpDialog(
title = "资源包更新",
content1 = "是否确定更新资源包",
content2 = null,
cancel = "取消",
confirm = "确定",
scope = coroutineScope,
data = it,
isStartDown = isDownloadingWGT,
downProgress = progressWGTState,
onStatusChange = { isUpdateFinish, isCancel, data ->
if(!isUpdateFinish && !isCancel && data != null){
isDownloadingWGT.value = true
}else{
coroutineScope.launch { GlobalStateManager(context).storeGlobalUniUpdateNotify(false) }
}
}
)
}
}
//App更新提示
val isStartDownload = mutableStateOf(false)
val progressState = mutableFloatStateOf(0f)
if(updateAppNotify.value == true){
UpdateDialog(
title = PreferenceUtil.getUserConfig()?.config?.versionEntity?.title?:"新版本,更新提示",
newVersion = "V${PreferenceUtil.getUserConfig()?.config?.versionEntity?.version}",
desc = PreferenceUtil.getUserConfig()?.config?.versionEntity?.description?:"",
url = PreferenceUtil.getUserConfig()?.config?.versionEntity?.url?:"",
scope = coroutineScope,
isStartDown = isStartDownload,
downProgress = progressState
){ state, isCancel, url ->
if(isCancel) {
coroutineScope.launch {
GlobalStateManager(context).storeGlobalUpdateNotify(state)
}
}
if(!isCancel){
isStartDownload.value = true
UpdateUtils.download(
scope = coroutineScope,
url = url,
filePath = FileUtils.getInstance().cacheDownLoadDir.absolutePath,
fileName = AppUpdate.getFileNameFromUrl(url),
onProgress = {progress->
progressState.floatValue = progress.toFloat()/100f
},
onFinish = {isSuccess, filePath ->
if(isSuccess){
filePath?.let {
UpdateUtils.install(context,it)
}
coroutineScope.launch {
GlobalStateManager(context).storeGlobalUpdateNotify(state)
}
}
}
)
}
}
}
//全局加载提示
if (updateGlobalLoadingNotify == true) {
Log.i("HomeScreen","isStartOn--->${System.currentTimeMillis()}")
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f))
) {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
// 模拟加载过程2秒后关闭启动页
LaunchedEffect(Unit) {
delay(500L)
splashViewModel.setLoading(false)
}
}
}
/**
* 初始化友盟
*/
private fun initUM() {
UMConfigure.preInit(applicationContext, Constants.UmengAppkey, ChannelUtils.getChannel(applicationContext))
UMConfigure.init(this, Constants.UmengAppkey, ChannelUtils.getChannel(applicationContext), UMConfigure.DEVICE_TYPE_PHONE, "")
MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO)
}
override fun showLoading() {
Log.i("HomeScreen","isStartOn--->${System.currentTimeMillis()}")
lifecycleScope.launch {
GlobalStateManager(this@MainActivity).storeGlobalLoading(true)
}
}
override fun hideLoading() {
Log.i("HomeScreen","isStartOff--->${System.currentTimeMillis()}")
lifecycleScope.launch {
delay(3000L)
GlobalStateManager(this@MainActivity).storeGlobalLoading(false)
}
}
}
@Composable
fun AppTheme(content: @Composable () -> Unit) {
// 使用Material3主题
MaterialTheme {
content()
}
}
@Composable
fun SplashScreenContent(
onAnimationFinished: @Composable () -> Unit // 改为Composable函数类型
) {
val scale = remember { Animatable(0f) }
var animationFinished by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
scale.animateTo(
targetValue = 1f,
animationSpec = tween(durationMillis = 800)
)
animationFinished = true
}
Box(modifier = Modifier.fillMaxSize()) {
if (!animationFinished) {
// 显示启动页动画
Image(
painter = painterResource(id = R.mipmap.ic_splash_mask),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.scale(scale.value)
)
Image(
painter = painterResource(id = R.mipmap.ic_splash_logo),
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.wrapContentSize()
.scale(scale.value)
)
} else {
// 动画完成后显示主界面
onAnimationFinished()
}
}
}
@Composable
private fun PrivacyPolicyScreen(viewModel: LoginViewModel, onAgreementChange: (Boolean) -> Unit) {
val context = LocalContext.current
Box(
modifier = Modifier.fillMaxSize()
){
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xCC000000))
){
Box(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 38.dp, vertical = 213.dp)
.align(Alignment.Center)
.background(Color.White, shape = RoundedCornerShape(26.dp))
){
Image(
painter = painterResource(id = R.mipmap.ic_privacy_policy_top_mask),
contentDescription = null,
modifier = Modifier
.fillMaxSize(),
alignment = Alignment.TopCenter
)
Column(
modifier = Modifier
.padding(top = 36.dp)
.align(Alignment.TopCenter)
) {
Text(
text = "用户协议与隐私政策",
modifier = Modifier
.wrapContentSize()
.align(Alignment.CenterHorizontally),
fontWeight = FontWeight.Normal,
fontSize = 18.sp,
color = Color(0xFF1A1A1A)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(24.dp)
)
val agreement = "请您务必审慎阅读、充分理解《服务协议》与《隐私政策》各条款,包括但不限于:为了更好的向您提供服务,我们需要访问您的相册、相机等。您可以阅读《隐私政策》了解详细信息。如果您同意,请点击下面同意按钮开始接受我们的服务。"
val annotatedText = buildAnnotatedString {
append(agreement)
val startIndexForService = agreement.indexOf("《服务协议》")
val startIndexPrivacy1 = agreement.indexOf("《隐私政策》")
val startIndexPrivacy2 = agreement.lastIndexOf("《隐私政策》")
val serviceLength = "《服务协议》".length
val privacyLength = "《服务协议》".length
// 高亮显示 "《服务协议》"
addStyle(
style = SpanStyle(
color = Color(0xFF0066CC),
textDecoration = TextDecoration.None
),
start = startIndexForService, // "《服务协议》" 开始下标13
end = startIndexForService + serviceLength // "《服务协议》" 结束下标19
)
addStringAnnotation(
tag = "AGREEMENT",
annotation = "service_agreement",
start = startIndexForService,
end = startIndexForService + serviceLength
)
// 高亮显示 "《隐私政策》"
addStyle(
style = SpanStyle(
color = Color(0xFF0066CC),
textDecoration = TextDecoration.None
),
start = startIndexPrivacy1, // "《隐私政策》" 开始下标20
end = startIndexPrivacy1 + privacyLength // "《隐私政策》" 结束下标26
)
addStringAnnotation(
tag = "PRIVACY",
annotation = "privacy_policy",
start = startIndexPrivacy1,
end = startIndexPrivacy1 + privacyLength
)
// 高亮显示 "《隐私政策》"
addStyle(
style = SpanStyle(
color = Color(0xFF0066CC),
textDecoration = TextDecoration.None
),
start = startIndexPrivacy2, // "《隐私政策》" 开始下标
end = startIndexPrivacy2 + privacyLength // "《隐私政策》" 结束下标
)
addStringAnnotation(
tag = "PRIVACY",
annotation = "privacy_policy",
start = startIndexPrivacy2,
end = startIndexPrivacy2 + privacyLength
)
}
@Suppress("DEPRECATION")
ClickableText(
text = annotatedText,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.align(Alignment.CenterHorizontally)
.padding(horizontal = 12.dp),
style = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
color = Color(0xFF1A1A1A)
),
onClick = { offset ->
val agreementAnnotation = annotatedText.getStringAnnotations("AGREEMENT", offset, offset).firstOrNull()
val privacyAnnotation = annotatedText.getStringAnnotations("PRIVACY", offset, offset).firstOrNull()
when {
agreementAnnotation != null -> {
openAgreement(context, "服务协议", agreementUrl, false)
}
privacyAnnotation != null -> {
openAgreement(context, "隐私政策", privacyUrl, false)
}
}
}
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.align(Alignment.BottomCenter)
) {
//同意按钮,用户协议与隐私政策
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 33.dp, end = 33.dp)
.background(
Color(0xFF252525),
shape = RoundedCornerShape(359.dp),
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onAgreementChange(true)
}
) {
Text(
"同意",
color = Color(0xFFC2FF43),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.padding(vertical = 12.dp)
.align(Alignment.Center)
)
}
//不同按钮,意用户协议与隐私政策
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 33.dp, end = 33.dp)
.background(
Color(0x00000000),
shape = RoundedCornerShape(359.dp),
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onAgreementChange(false)
viewModel.setIsPolicyAgreement(false)
}
) {
Text(
"不同意",
color = Color(0xFFAAAAAA),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight()
.padding(vertical = 12.dp)
.align(Alignment.Center)
)
}
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
AppTheme {
MainScreen(generalViewModel = viewModel(), loginViewModel = viewModel())
}
}