|
@@ -0,0 +1,519 @@
|
|
|
|
|
+package com.xuqm.sample
|
|
|
|
|
+
|
|
|
|
|
+import android.content.BroadcastReceiver
|
|
|
|
|
+import android.content.Context
|
|
|
|
|
+import android.content.Intent
|
|
|
|
|
+import android.content.IntentFilter
|
|
|
|
|
+import android.os.Bundle
|
|
|
|
|
+import androidx.activity.ComponentActivity
|
|
|
|
|
+import androidx.activity.compose.BackHandler
|
|
|
|
|
+import androidx.activity.compose.setContent
|
|
|
|
|
+import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
|
|
+import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
+import androidx.compose.foundation.layout.Column
|
|
|
|
|
+import androidx.compose.foundation.layout.PaddingValues
|
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
|
|
+import androidx.compose.foundation.lazy.LazyColumn
|
|
|
|
|
+import androidx.compose.material3.Button
|
|
|
|
|
+import androidx.compose.material3.Card
|
|
|
|
|
+import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
|
+import androidx.compose.material3.AlertDialog
|
|
|
|
|
+import androidx.compose.material3.MaterialTheme
|
|
|
|
|
+import androidx.compose.material3.Scaffold
|
|
|
|
|
+import androidx.compose.material3.Text
|
|
|
|
|
+import androidx.compose.material3.TopAppBar
|
|
|
|
|
+import androidx.compose.material3.TextButton
|
|
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
|
|
+import androidx.compose.runtime.DisposableEffect
|
|
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
|
|
+import androidx.compose.runtime.mutableLongStateOf
|
|
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
+import androidx.compose.runtime.remember
|
|
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
|
|
+import androidx.compose.ui.Modifier
|
|
|
|
|
+import androidx.compose.ui.unit.dp
|
|
|
|
|
+import androidx.lifecycle.Lifecycle
|
|
|
|
|
+import androidx.lifecycle.LifecycleEventObserver
|
|
|
|
|
+import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
|
|
|
+import androidx.lifecycle.lifecycleScope
|
|
|
|
|
+import com.xuqm.sdk.CoreSDK
|
|
|
|
|
+import com.xuqm.sdk.compose.components.AccordionGroup
|
|
|
|
|
+import com.xuqm.sdk.compose.components.FeatureCard
|
|
|
|
|
+import com.xuqm.sdk.plugin.PluginPackageManager
|
|
|
|
|
+import com.xuqm.sdk.ui.ToastCenter
|
|
|
|
|
+import com.xuqm.sdk.update.DownloadState
|
|
|
|
|
+import com.xuqm.sdk.update.StoragePath
|
|
|
|
|
+import com.xuqm.sdk.update.UpdateInfo
|
|
|
|
|
+import com.xuqm.sdk.update.VersionCheckResult
|
|
|
|
|
+import com.xuqm.sdk.update.VersionCheckStrategy
|
|
|
|
|
+import com.xuqm.sdk.utils.DateTimeUtils
|
|
|
|
|
+import com.xuqm.sample.update.UpdateRepository
|
|
|
|
|
+import com.xuqm.szyx.SzyxSDK
|
|
|
|
|
+import com.xuqm.szyx.auth.LoginSession
|
|
|
|
|
+import com.xuqm.szyx.auth.UserSessionManager
|
|
|
|
|
+import com.xuqm.szyx.login.SzyxLoginActivity
|
|
|
|
|
+import kotlinx.coroutines.Job
|
|
|
|
|
+import kotlinx.coroutines.launch
|
|
|
|
|
+
|
|
|
|
|
+class MainActivity : ComponentActivity() {
|
|
|
|
|
+
|
|
|
|
|
+ companion object {
|
|
|
|
|
+ private const val PLUGIN_PACKAGE_NAME = "com.xuqm.plugin.ui"
|
|
|
|
|
+ private const val PLUGIN_ENTRY_ACTIVITY = "com.xuqm.plugin.ui.PluginUiActivity"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private val sessionState = mutableStateOf<LoginSession?>(null)
|
|
|
|
|
+ private val pluginInstalledState = mutableStateOf(false)
|
|
|
|
|
+ private val pluginDownloadState = mutableStateOf<DownloadState>(DownloadState.Idle)
|
|
|
|
|
+ private val appDownloadState = mutableStateOf<DownloadState>(DownloadState.Idle)
|
|
|
|
|
+ private val pendingAppUpdateState = mutableStateOf<UpdateInfo?>(null)
|
|
|
|
|
+
|
|
|
|
|
+ private var pluginDownloadTaskId: String? = null
|
|
|
|
|
+ private var appDownloadTaskId: String? = null
|
|
|
|
|
+ private var pluginDownloadJob: Job? = null
|
|
|
|
|
+ private var appDownloadJob: Job? = null
|
|
|
|
|
+ private var loginPromptedOnLaunch = false
|
|
|
|
|
+ private var reloadPluginAfterInstall = false
|
|
|
|
|
+ private val updateRepository by lazy { UpdateRepository(BuildConfig.UPDATE_SERVER_BASE_URL) }
|
|
|
|
|
+ private var currentPluginUpdateInfo: PluginPackageManager.PluginUpdateInfo? = null
|
|
|
|
|
+
|
|
|
|
|
+ private val packageChangedReceiver = object : BroadcastReceiver() {
|
|
|
|
|
+ override fun onReceive(context: Context?, intent: Intent?) {
|
|
|
|
|
+ val packageName = intent?.data?.schemeSpecificPart ?: return
|
|
|
|
|
+ if (packageName == PLUGIN_PACKAGE_NAME) {
|
|
|
|
|
+ refreshState()
|
|
|
|
|
+ ToastCenter.show("plugin-ui 安装状态已更新")
|
|
|
|
|
+ if (intent.action == Intent.ACTION_PACKAGE_ADDED && reloadPluginAfterInstall) {
|
|
|
|
|
+ reloadPluginAfterInstall = false
|
|
|
|
|
+ val pluginUpdateInfo = currentPluginUpdateInfo
|
|
|
|
|
+ CoreSDK.pluginPackageManager().reloadPlugin(
|
|
|
|
|
+ packageName = PLUGIN_PACKAGE_NAME,
|
|
|
|
|
+ entryActivity = pluginUpdateInfo?.entryActivity ?: PLUGIN_ENTRY_ACTIVITY,
|
|
|
|
|
+ extras = pluginUpdateInfo?.extras ?: mapOf("hostPackageName" to this@MainActivity.packageName),
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private val loginLauncher =
|
|
|
|
|
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
|
|
|
+ refreshSession()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
+ super.onCreate(savedInstanceState)
|
|
|
|
|
+
|
|
|
|
|
+ CoreSDK.init(this, CoreSDK.SDKConfig(debugMode = true))
|
|
|
|
|
+ SzyxSDK.init(
|
|
|
|
|
+ this,
|
|
|
|
|
+ SzyxSDK.Config(
|
|
|
|
|
+ baseUrl = "https://dev.51trust.com/",
|
|
|
|
|
+ clientId = "2000111111110002",
|
|
|
|
|
+ hostAppPackageName = packageName,
|
|
|
|
|
+ debugMode = true,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ ToastCenter.init(this)
|
|
|
|
|
+ refreshState()
|
|
|
|
|
+ ensureLoginOnLaunch()
|
|
|
|
|
+ registerPackageChangeReceiver()
|
|
|
|
|
+
|
|
|
|
|
+ setContent {
|
|
|
|
|
+ MaterialTheme {
|
|
|
|
|
+ SampleHome(
|
|
|
|
|
+ session = sessionState.value,
|
|
|
|
|
+ pluginInstalled = pluginInstalledState.value,
|
|
|
|
|
+ pluginDownloadState = pluginDownloadState.value,
|
|
|
|
|
+ appDownloadState = appDownloadState.value,
|
|
|
|
|
+ pendingAppUpdate = pendingAppUpdateState.value,
|
|
|
|
|
+ onOpenLogin = ::openLogin,
|
|
|
|
|
+ onOpenPlugin = ::openPlugin,
|
|
|
|
|
+ onInstallPlugin = ::downloadPlugin,
|
|
|
|
|
+ onCancelPluginDownload = ::cancelPluginDownload,
|
|
|
|
|
+ onUpdateApp = ::downloadApp,
|
|
|
|
|
+ onConfirmAppUpdate = ::confirmDownloadAppUpdate,
|
|
|
|
|
+ onDismissAppUpdate = ::dismissAppUpdateDialog,
|
|
|
|
|
+ onCancelAppDownload = ::cancelAppDownload,
|
|
|
|
|
+ onRetryCheckPlugin = ::refreshState,
|
|
|
|
|
+ onExitApp = { finish() },
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onDestroy() {
|
|
|
|
|
+ pluginDownloadJob?.cancel()
|
|
|
|
|
+ appDownloadJob?.cancel()
|
|
|
|
|
+ runCatching { unregisterReceiver(packageChangedReceiver) }
|
|
|
|
|
+ super.onDestroy()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onResume() {
|
|
|
|
|
+ super.onResume()
|
|
|
|
|
+ refreshState()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun ensureLoginOnLaunch() {
|
|
|
|
|
+ if (sessionState.value == null && !loginPromptedOnLaunch) {
|
|
|
|
|
+ loginPromptedOnLaunch = true
|
|
|
|
|
+ openLogin()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun refreshSession() {
|
|
|
|
|
+ sessionState.value = UserSessionManager.getSession()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun refreshState() {
|
|
|
|
|
+ refreshSession()
|
|
|
|
|
+ pluginInstalledState.value = CoreSDK.pluginPackageManager().isPluginInstalled(PLUGIN_PACKAGE_NAME)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun openLogin() {
|
|
|
|
|
+ loginLauncher.launch(Intent(this, SzyxLoginActivity::class.java))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun registerPackageChangeReceiver() {
|
|
|
|
|
+ val filter = IntentFilter().apply {
|
|
|
|
|
+ addAction(Intent.ACTION_PACKAGE_ADDED)
|
|
|
|
|
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
|
|
|
|
|
+ addDataScheme("package")
|
|
|
|
|
+ }
|
|
|
|
|
+ ContextCompat.registerReceiver(
|
|
|
|
|
+ this,
|
|
|
|
|
+ packageChangedReceiver,
|
|
|
|
|
+ filter,
|
|
|
|
|
+ ContextCompat.RECEIVER_NOT_EXPORTED,
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun openPlugin() {
|
|
|
|
|
+ val session = sessionState.value
|
|
|
|
|
+ if (session == null) {
|
|
|
|
|
+ ToastCenter.show("请先登录")
|
|
|
|
|
+ openLogin()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val pluginManager = CoreSDK.pluginPackageManager()
|
|
|
|
|
+ pluginManager.cacheCurrentUser(
|
|
|
|
|
+ userId = session.loginModel.userId,
|
|
|
|
|
+ sessionId = session.loginModel.sessionId,
|
|
|
|
|
+ clientId = SzyxSDK.requireConfig().clientId,
|
|
|
|
|
+ extraData = mapOf("phone" to session.phone),
|
|
|
|
|
+ )
|
|
|
|
|
+ val launched = pluginManager.startPlugin(
|
|
|
|
|
+ packageName = PLUGIN_PACKAGE_NAME,
|
|
|
|
|
+ entryActivity = PLUGIN_ENTRY_ACTIVITY,
|
|
|
|
|
+ extras = mapOf("hostPackageName" to packageName),
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!launched) {
|
|
|
|
|
+ ToastCenter.show("未检测到已安装的 plugin-ui,请先下载安装")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun downloadPlugin() {
|
|
|
|
|
+ lifecycleScope.launch {
|
|
|
|
|
+ updateRepository.fetchLatestPluginUpdate(PLUGIN_PACKAGE_NAME)
|
|
|
|
|
+ .onSuccess { remoteUpdate ->
|
|
|
|
|
+ val pluginUpdateInfo = remoteUpdate.copy(
|
|
|
|
|
+ entryActivity = remoteUpdate.entryActivity ?: PLUGIN_ENTRY_ACTIVITY,
|
|
|
|
|
+ extras = mapOf("hostPackageName" to packageName),
|
|
|
|
|
+ )
|
|
|
|
|
+ val checkResult = CoreSDK.pluginPackageManager().checkPluginUpdate(
|
|
|
|
|
+ packageName = pluginUpdateInfo.packageName,
|
|
|
|
|
+ remoteVersionCode = pluginUpdateInfo.versionCode,
|
|
|
|
|
+ remoteVersionName = pluginUpdateInfo.versionName,
|
|
|
|
|
+ strategy = VersionCheckStrategy.VERSION_CODE_OR_NAME,
|
|
|
|
|
+ )
|
|
|
|
|
+ if (checkResult is VersionCheckResult.UpToDate) {
|
|
|
|
|
+ ToastCenter.show("当前插件已是最新版本 ${checkResult.current.versionName}(${checkResult.current.versionCode})")
|
|
|
|
|
+ return@onSuccess
|
|
|
|
|
+ }
|
|
|
|
|
+ currentPluginUpdateInfo = pluginUpdateInfo
|
|
|
|
|
+ val taskId = CoreSDK.pluginPackageManager().downloadPlugin(
|
|
|
|
|
+ updateInfo = pluginUpdateInfo,
|
|
|
|
|
+ fileName = "plugin-ui-release.apk",
|
|
|
|
|
+ storagePath = StoragePath.EXTERNAL_FILES,
|
|
|
|
|
+ )
|
|
|
|
|
+ pluginDownloadTaskId = taskId
|
|
|
|
|
+ observePluginDownload(taskId)
|
|
|
|
|
+ }
|
|
|
|
|
+ .onFailure {
|
|
|
|
|
+ ToastCenter.show(it.message ?: "获取插件更新配置失败")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun cancelPluginDownload() {
|
|
|
|
|
+ val taskId = pluginDownloadTaskId ?: return
|
|
|
|
|
+ CoreSDK.downloadManager().cancel(taskId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun downloadApp() {
|
|
|
|
|
+ lifecycleScope.launch {
|
|
|
|
|
+ updateRepository.fetchLatestAppUpdate(packageName)
|
|
|
|
|
+ .onSuccess { updateInfo ->
|
|
|
|
|
+ when (
|
|
|
|
|
+ val checkResult = CoreSDK.appUpdater().checkUpdate(
|
|
|
|
|
+ updateInfo = updateInfo,
|
|
|
|
|
+ strategy = VersionCheckStrategy.VERSION_CODE_OR_NAME,
|
|
|
|
|
+ )
|
|
|
|
|
+ ) {
|
|
|
|
|
+ is VersionCheckResult.NeedUpdate -> {
|
|
|
|
|
+ pendingAppUpdateState.value = updateInfo
|
|
|
|
|
+ }
|
|
|
|
|
+ is VersionCheckResult.UpToDate -> {
|
|
|
|
|
+ ToastCenter.show("当前已是最新版本 ${checkResult.current.versionName}(${checkResult.current.versionCode})")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .onFailure {
|
|
|
|
|
+ ToastCenter.show(it.message ?: "获取 App 更新配置失败")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun confirmDownloadAppUpdate() {
|
|
|
|
|
+ val updateInfo = pendingAppUpdateState.value ?: return
|
|
|
|
|
+ pendingAppUpdateState.value = null
|
|
|
|
|
+ val taskId = CoreSDK.appUpdater().downloadUpdate(
|
|
|
|
|
+ updateInfo = updateInfo,
|
|
|
|
|
+ fileName = "sample-app-update.apk",
|
|
|
|
|
+ storagePath = StoragePath.EXTERNAL_FILES,
|
|
|
|
|
+ )
|
|
|
|
|
+ appDownloadTaskId = taskId
|
|
|
|
|
+ observeAppDownload(taskId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun cancelAppDownload() {
|
|
|
|
|
+ val taskId = appDownloadTaskId ?: return
|
|
|
|
|
+ CoreSDK.downloadManager().cancel(taskId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun dismissAppUpdateDialog() {
|
|
|
|
|
+ pendingAppUpdateState.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun observePluginDownload(taskId: String) {
|
|
|
|
|
+ pluginDownloadJob?.cancel()
|
|
|
|
|
+ pluginDownloadJob = lifecycleScope.launch {
|
|
|
|
|
+ CoreSDK.downloadManager().observe(taskId)?.collect { state ->
|
|
|
|
|
+ pluginDownloadState.value = state
|
|
|
|
|
+ when (state) {
|
|
|
|
|
+ is DownloadState.Success -> {
|
|
|
|
|
+ ToastCenter.show("插件下载完成,准备重新加载")
|
|
|
|
|
+ reloadPluginAfterInstall = true
|
|
|
|
|
+ if (!CoreSDK.pluginPackageManager().loadPlugin(state.file)) {
|
|
|
|
|
+ reloadPluginAfterInstall = false
|
|
|
|
|
+ ToastCenter.show("插件加载拉起失败")
|
|
|
|
|
+ }
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ pluginDownloadTaskId = null
|
|
|
|
|
+ pluginDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ is DownloadState.Error -> {
|
|
|
|
|
+ ToastCenter.show("插件下载失败: ${state.message}")
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ pluginDownloadTaskId = null
|
|
|
|
|
+ pluginDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+ DownloadState.Cancelled -> {
|
|
|
|
|
+ ToastCenter.show("插件下载已取消")
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ pluginDownloadTaskId = null
|
|
|
|
|
+ pluginDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+ else -> Unit
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun observeAppDownload(taskId: String) {
|
|
|
|
|
+ appDownloadJob?.cancel()
|
|
|
|
|
+ appDownloadJob = lifecycleScope.launch {
|
|
|
|
|
+ CoreSDK.downloadManager().observe(taskId)?.collect { state ->
|
|
|
|
|
+ appDownloadState.value = state
|
|
|
|
|
+ when (state) {
|
|
|
|
|
+ is DownloadState.Success -> {
|
|
|
|
|
+ ToastCenter.show("安装包下载完成,准备安装")
|
|
|
|
|
+ if (!CoreSDK.appUpdater().installApk(state.file)) {
|
|
|
|
|
+ ToastCenter.show("应用安装拉起失败")
|
|
|
|
|
+ }
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ appDownloadTaskId = null
|
|
|
|
|
+ appDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ is DownloadState.Error -> {
|
|
|
|
|
+ ToastCenter.show("应用下载失败: ${state.message}")
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ appDownloadTaskId = null
|
|
|
|
|
+ appDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+ DownloadState.Cancelled -> {
|
|
|
|
|
+ ToastCenter.show("应用下载已取消")
|
|
|
|
|
+ CoreSDK.downloadManager().clear(taskId)
|
|
|
|
|
+ appDownloadTaskId = null
|
|
|
|
|
+ appDownloadJob?.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+ else -> Unit
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@OptIn(ExperimentalMaterial3Api::class)
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun SampleHome(
|
|
|
|
|
+ session: LoginSession?,
|
|
|
|
|
+ pluginInstalled: Boolean,
|
|
|
|
|
+ pluginDownloadState: DownloadState,
|
|
|
|
|
+ appDownloadState: DownloadState,
|
|
|
|
|
+ pendingAppUpdate: UpdateInfo?,
|
|
|
|
|
+ onOpenLogin: () -> Unit,
|
|
|
|
|
+ onOpenPlugin: () -> Unit,
|
|
|
|
|
+ onInstallPlugin: () -> Unit,
|
|
|
|
|
+ onCancelPluginDownload: () -> Unit,
|
|
|
|
|
+ onUpdateApp: () -> Unit,
|
|
|
|
|
+ onConfirmAppUpdate: () -> Unit,
|
|
|
|
|
+ onDismissAppUpdate: () -> Unit,
|
|
|
|
|
+ onCancelAppDownload: () -> Unit,
|
|
|
|
|
+ onRetryCheckPlugin: () -> Unit,
|
|
|
|
|
+ onExitApp: () -> Unit,
|
|
|
|
|
+) {
|
|
|
|
|
+ val lifecycleOwner = LocalLifecycleOwner.current
|
|
|
|
|
+ var lastBackPressedAt by remember { mutableLongStateOf(0L) }
|
|
|
|
|
+
|
|
|
|
|
+ DisposableEffect(lifecycleOwner) {
|
|
|
|
|
+ val observer = LifecycleEventObserver { _, event ->
|
|
|
|
|
+ if (event == Lifecycle.Event.ON_RESUME) {
|
|
|
|
|
+ onRetryCheckPlugin()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ lifecycleOwner.lifecycle.addObserver(observer)
|
|
|
|
|
+ onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BackHandler(enabled = session == null) {
|
|
|
|
|
+ val now = System.currentTimeMillis()
|
|
|
|
|
+ if (now - lastBackPressedAt < 2_000L) {
|
|
|
|
|
+ onExitApp()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ lastBackPressedAt = now
|
|
|
|
|
+ ToastCenter.show("未登录,双击返回退出应用")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Scaffold(
|
|
|
|
|
+ topBar = { TopAppBar(title = { Text("Sample Host") }) },
|
|
|
|
|
+ ) { innerPadding ->
|
|
|
|
|
+ pendingAppUpdate?.let { updateInfo ->
|
|
|
|
|
+ AlertDialog(
|
|
|
|
|
+ onDismissRequest = onDismissAppUpdate,
|
|
|
|
|
+ title = { Text(updateInfo.title) },
|
|
|
|
|
+ text = {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ "发现新版本 ${updateInfo.versionName}\n\n${updateInfo.changelog.ifBlank { "检测到可用更新,是否立即下载?" }}",
|
|
|
|
|
+ )
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmButton = {
|
|
|
|
|
+ TextButton(onClick = onConfirmAppUpdate) {
|
|
|
|
|
+ Text("立即更新")
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ dismissButton = {
|
|
|
|
|
+ TextButton(onClick = onDismissAppUpdate) {
|
|
|
|
|
+ Text("稍后再说")
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ LazyColumn(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxSize()
|
|
|
|
|
+ .padding(innerPadding),
|
|
|
|
|
+ contentPadding = PaddingValues(16.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ item {
|
|
|
|
|
+ Card(modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier.padding(16.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text("当前时间: ${DateTimeUtils.now()}")
|
|
|
|
|
+ Text("登录状态: ${session?.phone ?: "未登录"}")
|
|
|
|
|
+ Text("插件安装状态: ${if (pluginInstalled) "已安装" else "未安装或当前宿主不可见"}")
|
|
|
|
|
+ Text("插件下载: ${pluginDownloadState.toDisplayText()}")
|
|
|
|
|
+ Text("应用下载: ${appDownloadState.toDisplayText()}")
|
|
|
|
|
+ Button(onClick = onOpenLogin, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Text(if (session == null) "打开登录页" else "重新登录")
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(
|
|
|
|
|
+ onClick = onOpenPlugin,
|
|
|
|
|
+ enabled = session != null,
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text("启动 plugin-ui")
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(onClick = onInstallPlugin, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Text("下载并安装 plugin-ui")
|
|
|
|
|
+ }
|
|
|
|
|
+ if (pluginDownloadState is DownloadState.Starting || pluginDownloadState is DownloadState.Progress) {
|
|
|
|
|
+ Button(onClick = onCancelPluginDownload, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Text("取消插件下载")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(onClick = onUpdateApp, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Text("检查 App 更新")
|
|
|
|
|
+ }
|
|
|
|
|
+ if (appDownloadState is DownloadState.Starting || appDownloadState is DownloadState.Progress) {
|
|
|
|
|
+ Button(onClick = onCancelAppDownload, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Text("取消应用下载")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ item {
|
|
|
|
|
+ AccordionGroup(title = "当前方案", initiallyExpanded = true) {
|
|
|
|
|
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
|
+ Text("1. 未登录启动时直接进入 lib-szyx 登录页")
|
|
|
|
|
+ Text("2. 插件和应用更新都在应用内直接下载,并输出实时进度")
|
|
|
|
|
+ Text("3. plugin-ui 通过宿主共享缓存拿到 sessionId 和 userId")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ item {
|
|
|
|
|
+ FeatureCard(
|
|
|
|
|
+ title = "插件结构",
|
|
|
|
|
+ description = "宿主 sample-app + 业务插件 plugin-ui,二者共享 commonsdk-core / commonsdk-compose / lib-szyx。",
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private fun DownloadState.toDisplayText(): String {
|
|
|
|
|
+ return when (this) {
|
|
|
|
|
+ DownloadState.Idle -> "未开始"
|
|
|
|
|
+ DownloadState.Starting -> "准备下载"
|
|
|
|
|
+ is DownloadState.Progress -> {
|
|
|
|
|
+ val progressText = if (progress >= 0) "$progress%" else "未知进度"
|
|
|
|
|
+ "$progressText (${downloadedBytes}/${totalBytes.coerceAtLeast(0)})"
|
|
|
|
|
+ }
|
|
|
|
|
+ is DownloadState.Success -> "已完成: ${file.name}"
|
|
|
|
|
+ DownloadState.Cancelled -> "已取消"
|
|
|
|
|
+ is DownloadState.Error -> "失败: $message"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|