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()) } }