manager: Fixed an issue where ksud failed to release properly during the first installation.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -8,27 +9,40 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import com.sukisu.ultra.ui.activity.component.BottomBar
|
||||
import com.sukisu.ultra.ui.activity.util.*
|
||||
import com.sukisu.ultra.ui.component.InstallConfirmationDialog
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.install
|
||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.ui.webui.initPlatform
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import com.sukisu.ultra.ui.activity.component.BottomBar
|
||||
import com.sukisu.ultra.ui.activity.util.*
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var superUserViewModel: SuperUserViewModel
|
||||
private lateinit var homeViewModel: HomeViewModel
|
||||
internal val settingsStateFlow = MutableStateFlow(SettingsState())
|
||||
|
||||
data class SettingsState(
|
||||
@@ -36,6 +50,11 @@ class MainActivity : ComponentActivity() {
|
||||
val showKpmInfo: Boolean = false
|
||||
)
|
||||
|
||||
private var showConfirmationDialog = mutableStateOf(false)
|
||||
private var pendingZipFiles = mutableStateOf<List<ZipFileInfo>>(emptyList())
|
||||
|
||||
private lateinit var themeChangeObserver: ThemeChangeContentObserver
|
||||
|
||||
// 标记避免重复初始化
|
||||
private var isInitialized = false
|
||||
|
||||
@@ -46,8 +65,11 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
try {
|
||||
// 应用主题配置
|
||||
ThemeUtils.applyFullThemeConfiguration(this)
|
||||
// 确保应用正确的语言设置
|
||||
LocaleUtils.applyLanguageSetting(this)
|
||||
|
||||
// 应用自定义 DPI
|
||||
DisplayUtils.applyCustomDpi(this)
|
||||
|
||||
// Enable edge to edge
|
||||
enableEdgeToEdge()
|
||||
@@ -60,16 +82,155 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
// 使用标记控制初始化流程
|
||||
if (!isInitialized) {
|
||||
lifecycleScope.launch {
|
||||
ActivityInitializer.initialize(this@MainActivity, settingsStateFlow)
|
||||
}
|
||||
ThemeUtils.registerThemeChangeObserver(this)
|
||||
initializeViewModels()
|
||||
initializeData()
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
// Check if launched with a ZIP file
|
||||
val zipUri: ArrayList<Uri>? = when (intent?.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
uri?.let { arrayListOf(it) }
|
||||
}
|
||||
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
}
|
||||
|
||||
else -> when {
|
||||
intent?.data != null -> arrayListOf(intent.data!!)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
||||
intent.getParcelableArrayListExtra("uris", Uri::class.java)
|
||||
}
|
||||
else -> {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra("uris")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
MainScreenContent()
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val currentDestination = navController.currentBackStackEntryAsState().value?.destination
|
||||
|
||||
val bottomBarRoutes = remember {
|
||||
BottomBarDestination.entries.map { it.direction.route }.toSet()
|
||||
}
|
||||
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
|
||||
InstallConfirmationDialog(
|
||||
show = showConfirmationDialog.value,
|
||||
zipFiles = pendingZipFiles.value,
|
||||
onConfirm = { confirmedFiles ->
|
||||
showConfirmationDialog.value = false
|
||||
UltraActivityUtils.navigateToFlashScreen(this, confirmedFiles, navigator)
|
||||
},
|
||||
onDismiss = {
|
||||
showConfirmationDialog.value = false
|
||||
pendingZipFiles.value = emptyList()
|
||||
finish()
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
if (!zipUri.isNullOrEmpty()) {
|
||||
// 检测 ZIP 文件类型并显示确认对话框
|
||||
lifecycleScope.launch {
|
||||
UltraActivityUtils.detectZipTypeAndShowConfirmation(this@MainActivity, zipUri) { infos ->
|
||||
if (infos.isNotEmpty()) {
|
||||
pendingZipFiles.value = infos
|
||||
showConfirmationDialog.value = true
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val showBottomBar = when (currentDestination?.route) {
|
||||
ExecuteModuleActionScreenDestination.route -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
initPlatform()
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackBarHostState
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
AnimatedBottomBar.AnimatedBottomBarWrapper(
|
||||
showBottomBar = showBottomBar,
|
||||
content = { BottomBar(navController) }
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
) { innerPadding ->
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root as NavHostGraphSpec,
|
||||
navController = navController,
|
||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||
// If the target is a detail page (not a bottom navigation page), slide in from the right
|
||||
if (targetState.destination.route !in bottomBarRoutes) {
|
||||
slideInHorizontally(initialOffsetX = { it })
|
||||
} else {
|
||||
// Otherwise (switching between bottom navigation pages), use fade in
|
||||
fadeIn(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||
// If navigating from the home page (bottom navigation page) to a detail page, slide out to the left
|
||||
if (initialState.destination.route in bottomBarRoutes && targetState.destination.route !in bottomBarRoutes) {
|
||||
slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut()
|
||||
} else {
|
||||
// Otherwise (switching between bottom navigation pages), use fade out
|
||||
fadeOut(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||
// If returning to the home page (bottom navigation page), slide in from the left
|
||||
if (targetState.destination.route in bottomBarRoutes) {
|
||||
slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()
|
||||
} else {
|
||||
// Otherwise (e.g., returning between multiple detail pages), use default fade in
|
||||
fadeIn(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||
// If returning from a detail page (not a bottom navigation page), scale down and fade out
|
||||
if (initialState.destination.route !in bottomBarRoutes) {
|
||||
scaleOut(targetScale = 0.9f) + fadeOut()
|
||||
} else {
|
||||
// Otherwise, use default fade out
|
||||
fadeOut(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -77,66 +238,33 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainScreenContent() {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val currentDestination = navController.currentBackStackEntryAsState().value?.destination
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
private fun initializeViewModels() {
|
||||
superUserViewModel = SuperUserViewModel()
|
||||
homeViewModel = HomeViewModel()
|
||||
|
||||
// 处理ZIP文件
|
||||
var zipUri by remember { mutableStateOf<ArrayList<Uri>?>(null) }
|
||||
// 设置主题变化监听器
|
||||
themeChangeObserver = ThemeUtils.registerThemeChangeObserver(this)
|
||||
}
|
||||
|
||||
// 在 LaunchedEffect 中处理 ZIP 文件
|
||||
LaunchedEffect(Unit) {
|
||||
zipUri = ZipFileManager.handleZipFiles(intent)
|
||||
}
|
||||
|
||||
InstallConfirmationDialog(
|
||||
show = ZipFileManager.showConfirmationDialog.value,
|
||||
zipFiles = ZipFileManager.pendingZipFiles.value,
|
||||
onConfirm = { confirmedFiles ->
|
||||
ZipFileManager.navigateToFlashScreen(
|
||||
this@MainActivity,
|
||||
confirmedFiles,
|
||||
navigator,
|
||||
lifecycleScope
|
||||
)
|
||||
ZipFileManager.clearZipFileState()
|
||||
},
|
||||
onDismiss = {
|
||||
ZipFileManager.clearZipFileState()
|
||||
finish()
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
zipUri?.let { uris ->
|
||||
ZipFileManager.detectZipTypeAndShowConfirmation(this@MainActivity, uris)
|
||||
private fun initializeData() {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
superUserViewModel.fetchAppList()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
val showBottomBar = NavigationUtils.shouldShowBottomBar(currentDestination?.route)
|
||||
// 数据刷新协程
|
||||
DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
|
||||
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackBarHostState
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
AnimatedBottomBar.AnimatedBottomBarWrapper(
|
||||
showBottomBar = showBottomBar,
|
||||
content = { BottomBar(navController) }
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
) { innerPadding ->
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root as NavHostGraphSpec,
|
||||
navController = navController,
|
||||
defaultTransitions = NavigationUtils.createNavHostAnimations()
|
||||
)
|
||||
}
|
||||
// 初始化主题相关设置
|
||||
ThemeUtils.initializeThemeSettings(this, settingsStateFlow)
|
||||
|
||||
val isManager = Natives.becomeManager(packageName)
|
||||
if (isManager) {
|
||||
install()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,8 +285,12 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
private fun refreshData() {
|
||||
lifecycleScope.launch {
|
||||
ViewModelManager.refreshViewModelData()
|
||||
DataRefreshUtils.refreshData(lifecycleScope)
|
||||
try {
|
||||
superUserViewModel.fetchAppList()
|
||||
DataRefreshUtils.refreshData(lifecycleScope)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +305,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
try {
|
||||
ThemeUtils.unregisterThemeChangeObserver(this)
|
||||
ThemeUtils.unregisterThemeChangeObserver(this, themeChangeObserver)
|
||||
super.onDestroy()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.sukisu.ultra.ui.activity.util.AppData.getKpmVersionUse
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
@SuppressLint("ContextCastToActivity")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.sukisu.ultra.ui.activity.util
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
|
||||
object NavigationUtils {
|
||||
|
||||
/**
|
||||
* 获取底部导航栏路由集合
|
||||
*/
|
||||
fun getBottomBarRoutes(): Set<String> {
|
||||
return BottomBarDestination.entries.map { it.direction.route }.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该显示底部导航栏
|
||||
*/
|
||||
fun shouldShowBottomBar(currentRoute: String?): Boolean {
|
||||
return when (currentRoute) {
|
||||
ExecuteModuleActionScreenDestination.route -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导航动画样式
|
||||
*/
|
||||
fun createNavHostAnimations(): NavHostAnimatedDestinationStyle {
|
||||
val bottomBarRoutes = getBottomBarRoutes()
|
||||
|
||||
return object : NavHostAnimatedDestinationStyle() {
|
||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||
// If the target is a detail page (not a bottom navigation page), slide in from the right
|
||||
if (targetState.destination.route !in bottomBarRoutes) {
|
||||
slideInHorizontally(initialOffsetX = { it })
|
||||
} else {
|
||||
// Otherwise (switching between bottom navigation pages), use fade in
|
||||
fadeIn(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||
// If navigating from the home page (bottom navigation page) to a detail page, slide out to the left
|
||||
if (initialState.destination.route in bottomBarRoutes && targetState.destination.route !in bottomBarRoutes) {
|
||||
slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut()
|
||||
} else {
|
||||
// Otherwise (switching between bottom navigation pages), use fade out
|
||||
fadeOut(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||
// If returning to the home page (bottom navigation page), slide in from the left
|
||||
if (targetState.destination.route in bottomBarRoutes) {
|
||||
slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()
|
||||
} else {
|
||||
// Otherwise (e.g., returning between multiple detail pages), use default fade in
|
||||
fadeIn(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
|
||||
override val popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||
// If returning from a detail page (not a bottom navigation page), scale down and fade out
|
||||
if (initialState.destination.route !in bottomBarRoutes) {
|
||||
scaleOut(targetScale = 0.9f) + fadeOut()
|
||||
} else {
|
||||
// Otherwise, use default fade out
|
||||
fadeOut(animationSpec = tween(340))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.sukisu.ultra.ui.activity.util
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
@@ -19,16 +20,8 @@ class ThemeChangeContentObserver(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题管理工具类
|
||||
*/
|
||||
object ThemeUtils {
|
||||
|
||||
private var themeChangeObserver: ThemeChangeContentObserver? = null
|
||||
|
||||
/**
|
||||
* 初始化主题设置
|
||||
*/
|
||||
fun initializeThemeSettings(activity: MainActivity, settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>) {
|
||||
val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val isFirstRun = prefs.getBoolean("is_first_run", true)
|
||||
@@ -53,9 +46,6 @@ object ThemeUtils {
|
||||
CardConfig.load(activity.applicationContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册主题变化观察者
|
||||
*/
|
||||
fun registerThemeChangeObserver(activity: MainActivity): ThemeChangeContentObserver {
|
||||
val contentObserver = ThemeChangeContentObserver(Handler(activity.mainLooper)) {
|
||||
activity.runOnUiThread {
|
||||
@@ -67,28 +57,18 @@ object ThemeUtils {
|
||||
}
|
||||
|
||||
activity.contentResolver.registerContentObserver(
|
||||
android.provider.Settings.System.getUriFor("ui_night_mode"),
|
||||
Settings.System.getUriFor("ui_night_mode"),
|
||||
false,
|
||||
contentObserver
|
||||
)
|
||||
|
||||
themeChangeObserver = contentObserver
|
||||
return contentObserver
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销主题变化观察者
|
||||
*/
|
||||
fun unregisterThemeChangeObserver(activity: MainActivity) {
|
||||
themeChangeObserver?.let { observer ->
|
||||
activity.contentResolver.unregisterContentObserver(observer)
|
||||
}
|
||||
themeChangeObserver = null
|
||||
fun unregisterThemeChangeObserver(activity: MainActivity, observer: ThemeChangeContentObserver) {
|
||||
activity.contentResolver.unregisterContentObserver(observer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity暂停时的主题处理
|
||||
*/
|
||||
fun onActivityPause(activity: MainActivity) {
|
||||
CardConfig.save(activity.applicationContext)
|
||||
activity.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
|
||||
@@ -97,39 +77,21 @@ object ThemeUtils {
|
||||
ThemeConfig.preventBackgroundRefresh = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity恢复时的主题处理
|
||||
*/
|
||||
fun onActivityResume() {
|
||||
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||
loadCustomBackground()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用完整的主题配置到Activity
|
||||
*/
|
||||
fun applyFullThemeConfiguration(activity: MainActivity) {
|
||||
// 确保应用正确的语言设置
|
||||
LocaleUtils.applyLanguageSetting(activity)
|
||||
|
||||
// 应用自定义 DPI
|
||||
DisplayUtils.applyCustomDpi(activity)
|
||||
}
|
||||
|
||||
private fun loadThemeMode() {
|
||||
// 主题模式加载逻辑
|
||||
}
|
||||
|
||||
private fun loadThemeColors() {
|
||||
// 主题颜色加载逻辑
|
||||
}
|
||||
|
||||
private fun loadDynamicColorState() {
|
||||
// 动态颜色状态加载逻辑
|
||||
}
|
||||
|
||||
private fun loadCustomBackground() {
|
||||
// 自定义背景加载逻辑
|
||||
}
|
||||
}
|
||||
@@ -2,29 +2,18 @@ package com.sukisu.ultra.ui.activity.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.component.ZipFileDetector
|
||||
import com.sukisu.ultra.ui.component.ZipFileInfo
|
||||
import com.sukisu.ultra.ui.component.ZipType
|
||||
import com.sukisu.ultra.ui.screen.FlashIt
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.ui.webui.initPlatform
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -32,8 +21,18 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.ui.component.ZipFileDetector
|
||||
import com.sukisu.ultra.ui.component.ZipFileInfo
|
||||
import com.sukisu.ultra.ui.component.ZipType
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
import com.sukisu.ultra.ui.screen.FlashIt
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.core.content.edit
|
||||
|
||||
object AnimatedBottomBar {
|
||||
@Composable
|
||||
@@ -51,9 +50,58 @@ object AnimatedBottomBar {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据管理工具类
|
||||
*/
|
||||
object UltraActivityUtils {
|
||||
|
||||
suspend fun detectZipTypeAndShowConfirmation(
|
||||
activity: MainActivity,
|
||||
zipUris: ArrayList<Uri>,
|
||||
onResult: (List<ZipFileInfo>) -> Unit
|
||||
) {
|
||||
val infos = ZipFileDetector.detectAndParseZipFiles(activity, zipUris)
|
||||
withContext(Dispatchers.Main) { onResult(infos) }
|
||||
}
|
||||
|
||||
fun navigateToFlashScreen(
|
||||
activity: MainActivity,
|
||||
zipFiles: List<ZipFileInfo>,
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
activity.lifecycleScope.launch {
|
||||
val moduleUris = zipFiles.filter { it.type == ZipType.MODULE }.map { it.uri }
|
||||
val kernelUris = zipFiles.filter { it.type == ZipType.KERNEL }.map { it.uri }
|
||||
|
||||
when {
|
||||
kernelUris.isNotEmpty() && moduleUris.isEmpty() -> {
|
||||
if (kernelUris.size == 1 && rootAvailable()) {
|
||||
navigator.navigate(
|
||||
InstallScreenDestination(
|
||||
preselectedKernelUri = kernelUris.first().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
setAutoExitAfterFlash(activity)
|
||||
}
|
||||
|
||||
moduleUris.isNotEmpty() -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(ArrayList(moduleUris))
|
||||
)
|
||||
)
|
||||
setAutoExitAfterFlash(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAutoExitAfterFlash(activity: Context) {
|
||||
activity.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
.edit {
|
||||
putBoolean("auto_exit_after_flash", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AppData {
|
||||
object DataRefreshManager {
|
||||
// 私有状态流
|
||||
@@ -136,160 +184,7 @@ object AppData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ZIP文件处理工具类
|
||||
*/
|
||||
object ZipFileManager {
|
||||
val showConfirmationDialog = mutableStateOf(false)
|
||||
val pendingZipFiles = mutableStateOf<List<ZipFileInfo>>(emptyList())
|
||||
|
||||
/**
|
||||
* 处理传入的ZIP文件URI
|
||||
*/
|
||||
fun handleZipFiles(intent: Intent?): ArrayList<Uri>? {
|
||||
return when (intent?.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
uri?.let { arrayListOf(it) }
|
||||
}
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
}
|
||||
else -> when {
|
||||
intent?.data != null -> arrayListOf(intent.data!!)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
||||
intent?.getParcelableArrayListExtra("uris", Uri::class.java)
|
||||
}
|
||||
else -> {
|
||||
@Suppress("DEPRECATION")
|
||||
(intent?.getParcelableArrayListExtra("uris"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测ZIP文件类型并显示确认对话框
|
||||
*/
|
||||
suspend fun detectZipTypeAndShowConfirmation(context: Context, zipUris: ArrayList<Uri>) {
|
||||
try {
|
||||
val zipFileInfos = ZipFileDetector.detectAndParseZipFiles(context, zipUris)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (zipFileInfos.isNotEmpty()) {
|
||||
pendingZipFiles.value = zipFileInfos
|
||||
showConfirmationDialog.value = true
|
||||
} else {
|
||||
(context as MainActivity).finish()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
(context as MainActivity).finish()
|
||||
}
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航到内核刷写界面
|
||||
*/
|
||||
fun navigateToFlashScreen(
|
||||
context: Context,
|
||||
zipFiles: List<ZipFileInfo>,
|
||||
navigator: DestinationsNavigator,
|
||||
scope: LifecycleCoroutineScope
|
||||
) {
|
||||
scope.launch {
|
||||
val moduleUris = zipFiles.filter { it.type == ZipType.MODULE }.map { it.uri }
|
||||
val kernelUris = zipFiles.filter { it.type == ZipType.KERNEL }.map { it.uri }
|
||||
|
||||
when {
|
||||
// 内核文件
|
||||
kernelUris.isNotEmpty() && moduleUris.isEmpty() -> {
|
||||
if (kernelUris.size == 1 && rootAvailable()) {
|
||||
navigator.navigate(
|
||||
InstallScreenDestination(
|
||||
preselectedKernelUri = kernelUris.first().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
setAutoExitAfterFlash(context)
|
||||
}
|
||||
// 模块文件
|
||||
moduleUris.isNotEmpty() -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(ArrayList(moduleUris))
|
||||
)
|
||||
)
|
||||
setAutoExitAfterFlash(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内核刷写后自动退出
|
||||
*/
|
||||
private fun setAutoExitAfterFlash(context: Context) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit {
|
||||
putBoolean("auto_exit_after_flash", true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理ZIP文件状态
|
||||
*/
|
||||
fun clearZipFileState() {
|
||||
showConfirmationDialog.value = false
|
||||
pendingZipFiles.value = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewModel管理工具类
|
||||
*/
|
||||
object ViewModelManager {
|
||||
lateinit var superUserViewModel: SuperUserViewModel
|
||||
lateinit var homeViewModel: HomeViewModel
|
||||
|
||||
/**
|
||||
* 初始化ViewModel
|
||||
*/
|
||||
fun initializeViewModels() {
|
||||
superUserViewModel = SuperUserViewModel()
|
||||
homeViewModel = HomeViewModel()
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新ViewModel数据
|
||||
*/
|
||||
suspend fun refreshViewModelData() {
|
||||
try {
|
||||
superUserViewModel.fetchAppList()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据刷新工具类
|
||||
*/
|
||||
object DataRefreshUtils {
|
||||
|
||||
fun startDataRefreshCoroutine(scope: LifecycleCoroutineScope) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
while (isActive) {
|
||||
@@ -323,48 +218,7 @@ object DataRefreshUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity初始化工具类
|
||||
*/
|
||||
object ActivityInitializer {
|
||||
/**
|
||||
* 初始化Activity的所有组件
|
||||
*/
|
||||
suspend fun initialize(activity: MainActivity, settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>) {
|
||||
// 初始化ViewModel
|
||||
ViewModelManager.initializeViewModels()
|
||||
|
||||
// 初始化数据
|
||||
initializeData(activity, settingsStateFlow)
|
||||
|
||||
// 初始化平台
|
||||
initPlatform()
|
||||
}
|
||||
|
||||
private suspend fun initializeData(activity: MainActivity, settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>) {
|
||||
// 获取应用列表
|
||||
ViewModelManager.refreshViewModelData()
|
||||
|
||||
// 启动数据刷新协程
|
||||
DataRefreshUtils.startDataRefreshCoroutine(activity.lifecycleScope)
|
||||
DataRefreshUtils.startSettingsMonitorCoroutine(activity.lifecycleScope, activity, settingsStateFlow)
|
||||
|
||||
// 初始化主题相关设置
|
||||
ThemeUtils.initializeThemeSettings(activity, settingsStateFlow)
|
||||
|
||||
// 安装管理器
|
||||
val isManager = Natives.becomeManager(activity.packageName)
|
||||
if (isManager) {
|
||||
install()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示设置工具类
|
||||
*/
|
||||
object DisplayUtils {
|
||||
|
||||
fun applyCustomDpi(context: Context) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val customDpi = prefs.getInt("app_dpi", 0)
|
||||
@@ -384,11 +238,7 @@ object DisplayUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 语言本地化工具类
|
||||
*/
|
||||
object LocaleUtils {
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun applyLanguageSetting(context: Context) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
Reference in New Issue
Block a user