From 85291de02a1d265ebcf684bf6a5a21e67e982bc2 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sun, 12 Oct 2025 18:58:02 +0800 Subject: [PATCH] manager: Restructure the file directory to keep it clean --- .../java/com/sukisu/ultra/ui/MainActivity.kt | 326 +--- .../ultra/ui}/activity/component/BottomBar.kt | 13 +- .../ultra/ui/activity/util/MergedUtils.kt | 429 +++++ .../ultra/ui/activity/util/NavigationUtils.kt | 77 + .../ultra/ui}/activity/util/ThemeUtils.kt | 45 +- .../java/com/sukisu/ultra/ui/screen/Flash.kt | 2 + .../java/com/sukisu/ultra/ui/screen/Home.kt | 2 +- .../com/sukisu/ultra/ui/screen/Install.kt | 2 +- .../java/com/sukisu/ultra/ui/screen/Module.kt | 4 + .../com/sukisu/ultra/ui/screen/SuperUser.kt | 2 +- .../ultra/ui/{screen => susfs}/SuSFSConfig.kt | 50 +- .../component/SuSFSConfigDialogs.kt | 4 +- .../{ => susfs}/component/SuSFSConfigTabs.kt | 6 +- .../ultra/ui/{ => susfs}/util/SuSFSManager.kt | 17 +- .../ui/{ => susfs}/util/SuSFSModuleScripts.kt | 2 +- .../java/com/sukisu/ultra/ui/theme/Theme.kt | 4 +- .../component/ImageEditorDialog.kt | 13 +- .../ui/{ => theme}/util/BackgroundUtils.kt | 2 +- .../ui/util/{ => module}/ModuleModify.kt | 41 +- .../ultra/ui/util/{ => module}/ModuleUtils.kt | 2 +- .../{ => module}/ModuleVerificationManager.kt | 3 +- .../ultra/ui/viewmodel/ModuleViewModel.kt | 2 +- .../zakoui/activity/util/AnimatedBottomBar.kt | 20 - .../zako/zako/zakoui/activity/util/AppData.kt | 90 - .../zakoui/activity/util/DataRefreshUtils.kt | 46 - .../zako/zakoui/activity/util/DisplayUtils.kt | 24 - .../zako/zakoui/activity/util/LocaleUtils.kt | 48 - .../zako/zako/zakoui/screen/MoreSettings.kt | 1711 ----------------- .../screen/{ => kernelFlash}/KernelFlash.kt | 8 +- .../component/SlotSelectionDialog.kt | 2 +- .../kernelFlash/state/KernelFlashState.kt} | 2 +- .../screen/moreSettings/MoreSettings.kt | 716 +++++++ .../moreSettings/MoreSettingsHandlers.kt | 509 +++++ .../component/MoreSettingsComponents.kt | 201 ++ .../component/MoreSettingsDialogs.kt | 394 ++++ .../moreSettings/state/MoreSettingsState.kt | 149 ++ .../util/RestartActivityUtils.kt | 2 +- 37 files changed, 2705 insertions(+), 2265 deletions(-) rename manager/app/src/main/java/{zako/zako/zako/zakoui => com/sukisu/ultra/ui}/activity/component/BottomBar.kt (95%) create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/MergedUtils.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/NavigationUtils.kt rename manager/app/src/main/java/{zako/zako/zako/zakoui => com/sukisu/ultra/ui}/activity/util/ThemeUtils.kt (71%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{screen => susfs}/SuSFSConfig.kt (97%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => susfs}/component/SuSFSConfigDialogs.kt (99%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => susfs}/component/SuSFSConfigTabs.kt (99%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => susfs}/util/SuSFSManager.kt (99%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => susfs}/util/SuSFSModuleScripts.kt (99%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => theme}/component/ImageEditorDialog.kt (96%) rename manager/app/src/main/java/com/sukisu/ultra/ui/{ => theme}/util/BackgroundUtils.kt (98%) rename manager/app/src/main/java/com/sukisu/ultra/ui/util/{ => module}/ModuleModify.kt (92%) rename manager/app/src/main/java/com/sukisu/ultra/ui/util/{ => module}/ModuleUtils.kt (99%) rename manager/app/src/main/java/com/sukisu/ultra/ui/util/{ => module}/ModuleVerificationManager.kt (98%) delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AnimatedBottomBar.kt delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AppData.kt delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DataRefreshUtils.kt delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DisplayUtils.kt delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/LocaleUtils.kt delete mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt rename manager/app/src/main/java/zako/zako/zako/zakoui/screen/{ => kernelFlash}/KernelFlash.kt (98%) rename manager/app/src/main/java/{com/sukisu/ultra/ui => zako/zako/zako/zakoui/screen/kernelFlash}/component/SlotSelectionDialog.kt (99%) rename manager/app/src/main/java/zako/zako/zako/zakoui/{flash/KernelFlash.kt => screen/kernelFlash/state/KernelFlashState.kt} (99%) create mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettings.kt create mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt create mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsComponents.kt create mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsDialogs.kt create mode 100644 manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/state/MoreSettingsState.kt rename manager/app/src/main/java/{com/sukisu/ultra/ui => zako/zako/zako/zakoui/screen/moreSettings}/util/RestartActivityUtils.kt (96%) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt index eaea75c8..2e9b144e 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt @@ -1,7 +1,6 @@ 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 @@ -9,47 +8,27 @@ 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.generated.destinations.FlashScreenDestination -import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination import com.ramcosta.composedestinations.spec.NavHostGraphSpec import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator -import com.sukisu.ultra.Natives -import com.sukisu.ultra.ui.screen.BottomBarDestination +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.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.screen.FlashIt -import com.sukisu.ultra.ui.component.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import zako.zako.zako.zakoui.activity.component.BottomBar -import zako.zako.zako.zakoui.activity.util.* -import androidx.core.content.edit -import com.sukisu.ultra.ui.util.rootAvailable class MainActivity : ComponentActivity() { - private lateinit var superUserViewModel: SuperUserViewModel - private lateinit var homeViewModel: HomeViewModel + internal val settingsStateFlow = MutableStateFlow(SettingsState()) data class SettingsState( @@ -57,12 +36,7 @@ class MainActivity : ComponentActivity() { val showKpmInfo: Boolean = false ) - private var showConfirmationDialog = mutableStateOf(false) - private var pendingZipFiles = mutableStateOf>(emptyList()) - - private lateinit var themeChangeObserver: ThemeChangeContentObserver - - // 添加标记避免重复初始化 + // 标记避免重复初始化 private var isInitialized = false override fun attachBaseContext(newBase: Context) { @@ -72,11 +46,8 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { try { - // 确保应用正确的语言设置 - LocaleUtils.applyLanguageSetting(this) - - // 应用自定义 DPI - DisplayUtils.applyCustomDpi(this) + // 应用主题配置 + ThemeUtils.applyFullThemeConfiguration(this) // Enable edge to edge enableEdgeToEdge() @@ -89,146 +60,16 @@ class MainActivity : ComponentActivity() { // 使用标记控制初始化流程 if (!isInitialized) { - initializeViewModels() - initializeData() + lifecycleScope.launch { + ActivityInitializer.initialize(this@MainActivity, settingsStateFlow) + } + ThemeUtils.registerThemeChangeObserver(this) isInitialized = true } - // Check if launched with a ZIP file - val zipUri: ArrayList? = 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 { - 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 - navigateToFlashScreen(confirmedFiles, navigator) - }, - onDismiss = { - showConfirmationDialog.value = false - pendingZipFiles.value = emptyList() - finish() - } - ) - - LaunchedEffect(zipUri) { - if (!zipUri.isNullOrEmpty()) { - // 检测 ZIP 文件类型并显示确认对话框 - detectZipTypeAndShowConfirmation(zipUri) - } - } - - 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.() -> 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.() -> 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.() -> 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.() -> 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)) - } - } - } - ) - } - } + MainScreenContent() } } } catch (e: Exception) { @@ -236,96 +77,69 @@ class MainActivity : ComponentActivity() { } } - private suspend fun detectZipTypeAndShowConfirmation(zipUris: ArrayList) { - try { - val zipFileInfos = ZipFileDetector.detectAndParseZipFiles(this, zipUris) + @Composable + private fun MainScreenContent() { + val navController = rememberNavController() + val snackBarHostState = remember { SnackbarHostState() } + val currentDestination = navController.currentBackStackEntryAsState().value?.destination + val navigator = navController.rememberDestinationsNavigator() - withContext(Dispatchers.Main) { - if (zipFileInfos.isNotEmpty()) { - pendingZipFiles.value = zipFileInfos - showConfirmationDialog.value = true - } else { - finish() - } - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { + // 处理ZIP文件 + var zipUri by remember { mutableStateOf?>(null) } + + // 在 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() } - e.printStackTrace() + ) + + LaunchedEffect(zipUri) { + zipUri?.let { uris -> + ZipFileManager.detectZipTypeAndShowConfirmation(this@MainActivity, uris) + } } - } - private fun navigateToFlashScreen( - zipFiles: List, - navigator: com.ramcosta.composedestinations.navigation.DestinationsNavigator - ) { - lifecycleScope.launch { - val moduleUris = zipFiles.filter { it.type == ZipType.MODULE }.map { it.uri } - val kernelUris = zipFiles.filter { it.type == ZipType.KERNEL }.map { it.uri } + val showBottomBar = NavigationUtils.shouldShowBottomBar(currentDestination?.route) - when { - // 内核文件 - kernelUris.isNotEmpty() && moduleUris.isEmpty() -> { - if (kernelUris.size == 1 && rootAvailable()) { - navigator.navigate( - InstallScreenDestination( - preselectedKernelUri = kernelUris.first().toString() - ) - ) - } - setAutoExitAfterFlash() - } - // 模块文件 - moduleUris.isNotEmpty() -> { - navigator.navigate( - FlashScreenDestination( - FlashIt.FlashModules(ArrayList(moduleUris)) - ) + CompositionLocalProvider( + LocalSnackbarHost provides snackBarHostState + ) { + Scaffold( + bottomBar = { + AnimatedBottomBar.AnimatedBottomBarWrapper( + showBottomBar = showBottomBar, + content = { BottomBar(navController) } ) - setAutoExitAfterFlash() - } + }, + contentWindowInsets = WindowInsets(0, 0, 0, 0) + ) { innerPadding -> + DestinationsNavHost( + modifier = Modifier.padding(innerPadding), + navGraph = NavGraphs.root as NavHostGraphSpec, + navController = navController, + defaultTransitions = NavigationUtils.createNavHostAnimations() + ) } } } - private fun setAutoExitAfterFlash() { - val sharedPref = getSharedPreferences("kernel_flash_prefs", MODE_PRIVATE) - sharedPref.edit { - putBoolean("auto_exit_after_flash", true) - } - } - - private fun initializeViewModels() { - superUserViewModel = SuperUserViewModel() - homeViewModel = HomeViewModel() - - // 设置主题变化监听器 - themeChangeObserver = ThemeUtils.registerThemeChangeObserver(this) - } - - private fun initializeData() { - lifecycleScope.launch { - try { - superUserViewModel.fetchAppList() - } catch (e: Exception) { - e.printStackTrace() - } - } - - // 数据刷新协程 - DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope) - DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow) - - // 初始化主题相关设置 - ThemeUtils.initializeThemeSettings(this, settingsStateFlow) - - val isManager = Natives.becomeManager(packageName) - if (isManager) { - install() - } - } - override fun onResume() { try { super.onResume() @@ -343,12 +157,8 @@ class MainActivity : ComponentActivity() { private fun refreshData() { lifecycleScope.launch { - try { - superUserViewModel.fetchAppList() - DataRefreshUtils.refreshData(lifecycleScope) - } catch (e: Exception) { - e.printStackTrace() - } + ViewModelManager.refreshViewModelData() + DataRefreshUtils.refreshData(lifecycleScope) } } @@ -363,7 +173,7 @@ class MainActivity : ComponentActivity() { override fun onDestroy() { try { - ThemeUtils.unregisterThemeChangeObserver(this, themeChangeObserver) + ThemeUtils.unregisterThemeChangeObserver(this) super.onDestroy() } catch (e: Exception) { e.printStackTrace() @@ -378,4 +188,4 @@ class MainActivity : ComponentActivity() { e.printStackTrace() } } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/component/BottomBar.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/component/BottomBar.kt similarity index 95% rename from manager/app/src/main/java/zako/zako/zako/zakoui/activity/component/BottomBar.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/activity/component/BottomBar.kt index 14e0c11c..6cb363de 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/component/BottomBar.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/component/BottomBar.kt @@ -1,4 +1,4 @@ -package zako.zako.zako.zakoui.activity.component +package com.sukisu.ultra.ui.activity.component import android.annotation.SuppressLint import androidx.compose.foundation.layout.* @@ -17,12 +17,11 @@ import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import com.sukisu.ultra.Natives import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ui.MainActivity +import com.sukisu.ultra.ui.activity.util.* +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 zako.zako.zako.zakoui.activity.util.AppData -import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager -import zako.zako.zako.zakoui.activity.util.AppData.getKpmVersionUse @SuppressLint("ContextCastToActivity") @OptIn(ExperimentalMaterial3Api::class) @@ -40,9 +39,9 @@ fun BottomBar(navController: NavHostController) { val showKpmInfo = settings.showKpmInfo // 收集计数数据 - val superuserCount by DataRefreshManager.superuserCount.collectAsState() - val moduleCount by DataRefreshManager.moduleCount.collectAsState() - val kpmModuleCount by DataRefreshManager.kpmModuleCount.collectAsState() + val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState() + val moduleCount by AppData.DataRefreshManager.moduleCount.collectAsState() + val kpmModuleCount by AppData.DataRefreshManager.kpmModuleCount.collectAsState() NavigationBar( diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/MergedUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/MergedUtils.kt new file mode 100644 index 00000000..82bd4c2a --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/MergedUtils.kt @@ -0,0 +1,429 @@ +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.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 +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.* + +object AnimatedBottomBar { + @Composable + fun AnimatedBottomBarWrapper( + showBottomBar: Boolean, + content: @Composable () -> Unit + ) { + AnimatedVisibility( + visible = showBottomBar, + enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { it }) + fadeOut() + ) { + content() + } + } +} + +/** + * 应用数据管理工具类 + */ +object AppData { + object DataRefreshManager { + // 私有状态流 + private val _superuserCount = MutableStateFlow(0) + private val _moduleCount = MutableStateFlow(0) + private val _kpmModuleCount = MutableStateFlow(0) + + // 公开的只读状态流 + val superuserCount: StateFlow = _superuserCount.asStateFlow() + val moduleCount: StateFlow = _moduleCount.asStateFlow() + val kpmModuleCount: StateFlow = _kpmModuleCount.asStateFlow() + + /** + * 刷新所有数据计数 + */ + fun refreshData() { + _superuserCount.value = getSuperuserCountUse() + _moduleCount.value = getModuleCountUse() + _kpmModuleCount.value = getKpmModuleCountUse() + } + } + + /** + * 获取超级用户应用计数 + */ + fun getSuperuserCountUse(): Int { + return try { + if (!rootAvailable()) return 0 + getSuperuserCount() + } catch (_: Exception) { + 0 + } + } + + /** + * 获取模块计数 + */ + fun getModuleCountUse(): Int { + return try { + if (!rootAvailable()) return 0 + getModuleCount() + } catch (_: Exception) { + 0 + } + } + + /** + * 获取KPM模块计数 + */ + fun getKpmModuleCountUse(): Int { + return try { + if (!rootAvailable()) return 0 + val kpmVersion = getKpmVersionUse() + if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) return 0 + getKpmModuleCount() + } catch (_: Exception) { + 0 + } + } + + /** + * 获取KPM版本 + */ + fun getKpmVersionUse(): String { + return try { + if (!rootAvailable()) return "" + val version = getKpmVersion() + version.ifEmpty { "" } + } catch (e: Exception) { + "Error: ${e.message}" + } + } + + /** + * 检查是否是完整功能模式 + */ + fun isFullFeatured(packageName: String): Boolean { + val isManager = Natives.becomeManager(packageName) + return isManager && !Natives.requireNewKernel() && rootAvailable() + } +} + +/** + * ZIP文件处理工具类 + */ +object ZipFileManager { + val showConfirmationDialog = mutableStateOf(false) + val pendingZipFiles = mutableStateOf>(emptyList()) + + /** + * 处理传入的ZIP文件URI + */ + fun handleZipFiles(intent: Intent?): ArrayList? { + 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) { + 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, + 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) { + AppData.DataRefreshManager.refreshData() + delay(5000) + } + } + } + + fun startSettingsMonitorCoroutine( + scope: LifecycleCoroutineScope, + activity: MainActivity, + settingsStateFlow: MutableStateFlow + ) { + scope.launch(Dispatchers.IO) { + while (isActive) { + val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE) + settingsStateFlow.value = MainActivity.SettingsState( + isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false), + showKpmInfo = prefs.getBoolean("show_kpm_info", false) + ) + delay(1000) + } + } + } + + fun refreshData(scope: LifecycleCoroutineScope) { + scope.launch { + AppData.DataRefreshManager.refreshData() + } + } +} + +/** + * Activity初始化工具类 + */ +object ActivityInitializer { + /** + * 初始化Activity的所有组件 + */ + suspend fun initialize(activity: MainActivity, settingsStateFlow: MutableStateFlow) { + // 初始化ViewModel + ViewModelManager.initializeViewModels() + + // 初始化数据 + initializeData(activity, settingsStateFlow) + + // 初始化平台 + initPlatform() + } + + private suspend fun initializeData(activity: MainActivity, settingsStateFlow: MutableStateFlow) { + // 获取应用列表 + 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) + + if (customDpi > 0) { + try { + val resources = context.resources + val metrics = resources.displayMetrics + metrics.density = customDpi / 160f + @Suppress("DEPRECATION") + metrics.scaledDensity = customDpi / 160f + metrics.densityDpi = customDpi + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} + +/** + * 语言本地化工具类 + */ +object LocaleUtils { + + @SuppressLint("ObsoleteSdkInt") + fun applyLanguageSetting(context: Context) { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + val languageCode = prefs.getString("app_language", "") ?: "" + + if (languageCode.isNotEmpty()) { + val locale = Locale.forLanguageTag(languageCode) + Locale.setDefault(locale) + + val resources = context.resources + val config = Configuration(resources.configuration) + config.setLocale(locale) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.createConfigurationContext(config) + } else { + @Suppress("DEPRECATION") + resources.updateConfiguration(config, resources.displayMetrics) + } + } + } + + fun applyLocale(context: Context): Context { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + val languageCode = prefs.getString("app_language", "") ?: "" + + var newContext = context + if (languageCode.isNotEmpty()) { + val locale = Locale.forLanguageTag(languageCode) + Locale.setDefault(locale) + + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + newContext = context.createConfigurationContext(config) + } + + return newContext + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/NavigationUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/NavigationUtils.kt new file mode 100644 index 00000000..de9777ad --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/NavigationUtils.kt @@ -0,0 +1,77 @@ +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 { + 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.() -> 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.() -> 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.() -> 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.() -> 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)) + } + } + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/ThemeUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/ThemeUtils.kt similarity index 71% rename from manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/ThemeUtils.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/ThemeUtils.kt index 75ce7daf..b6851b07 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/ThemeUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/activity/util/ThemeUtils.kt @@ -1,4 +1,4 @@ -package zako.zako.zako.zakoui.activity.util +package com.sukisu.ultra.ui.activity.util import android.content.Context import android.database.ContentObserver @@ -19,8 +19,16 @@ class ThemeChangeContentObserver( } } +/** + * 主题管理工具类 + */ object ThemeUtils { + private var themeChangeObserver: ThemeChangeContentObserver? = null + + /** + * 初始化主题设置 + */ fun initializeThemeSettings(activity: MainActivity, settingsStateFlow: MutableStateFlow) { val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE) val isFirstRun = prefs.getBoolean("is_first_run", true) @@ -45,6 +53,9 @@ object ThemeUtils { CardConfig.load(activity.applicationContext) } + /** + * 注册主题变化观察者 + */ fun registerThemeChangeObserver(activity: MainActivity): ThemeChangeContentObserver { val contentObserver = ThemeChangeContentObserver(Handler(activity.mainLooper)) { activity.runOnUiThread { @@ -61,13 +72,23 @@ object ThemeUtils { contentObserver ) + themeChangeObserver = contentObserver return contentObserver } - fun unregisterThemeChangeObserver(activity: MainActivity, observer: ThemeChangeContentObserver) { - activity.contentResolver.unregisterContentObserver(observer) + /** + * 注销主题变化观察者 + */ + fun unregisterThemeChangeObserver(activity: MainActivity) { + themeChangeObserver?.let { observer -> + activity.contentResolver.unregisterContentObserver(observer) + } + themeChangeObserver = null } + /** + * Activity暂停时的主题处理 + */ fun onActivityPause(activity: MainActivity) { CardConfig.save(activity.applicationContext) activity.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit { @@ -76,21 +97,39 @@ 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() { + // 自定义背景加载逻辑 } } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt index e4808f34..838a5858 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt @@ -52,6 +52,8 @@ import java.io.File import java.text.SimpleDateFormat import java.util.* import androidx.core.content.edit +import com.sukisu.ultra.ui.util.module.ModuleOperationUtils +import com.sukisu.ultra.ui.util.module.ModuleUtils /** * @author ShirkNeko diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index b003040b..fe8e7142 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -50,7 +50,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardElevation -import com.sukisu.ultra.ui.util.SuSFSManager +import com.sukisu.ultra.ui.susfs.util.SuSFSManager import com.sukisu.ultra.ui.util.checkNewVersion import com.sukisu.ultra.ui.util.getSuSFS import com.sukisu.ultra.ui.util.module.LatestVersionInfo diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt index 2bdf085b..877e7042 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt @@ -48,7 +48,7 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import com.sukisu.ultra.R import com.sukisu.ultra.getKernelVersion import com.sukisu.ultra.ui.component.DialogHandle -import com.sukisu.ultra.ui.component.SlotSelectionDialog +import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog import com.sukisu.ultra.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.rememberCustomDialog import com.sukisu.ultra.ui.theme.CardConfig diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index 294e8a84..0c203cbd 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -75,6 +75,10 @@ import com.sukisu.ultra.ui.component.* import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardElevation import com.sukisu.ultra.ui.util.* +import com.sukisu.ultra.ui.util.module.ModuleModify +import com.sukisu.ultra.ui.util.module.ModuleOperationUtils +import com.sukisu.ultra.ui.util.module.ModuleUtils +import com.sukisu.ultra.ui.util.module.verifyModuleSignature import com.sukisu.ultra.ui.viewmodel.ModuleViewModel import com.sukisu.ultra.ui.webui.WebUIActivity import com.sukisu.ultra.ui.webui.WebUIXActivity diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt index 8b700771..49362f2a 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt @@ -53,7 +53,7 @@ import com.sukisu.ultra.R import com.sukisu.ultra.ui.component.FabMenuPresets import com.sukisu.ultra.ui.component.SearchAppBar import com.sukisu.ultra.ui.component.VerticalExpandableFab -import com.sukisu.ultra.ui.util.ModuleModify +import com.sukisu.ultra.ui.util.module.ModuleModify import com.sukisu.ultra.ui.viewmodel.AppCategory import com.sukisu.ultra.ui.viewmodel.SortType import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt similarity index 97% rename from manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt index e645aeee..2f15f14c 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt @@ -1,6 +1,7 @@ -package com.sukisu.ultra.ui.screen +package com.sukisu.ultra.ui.susfs import android.annotation.SuppressLint +import android.content.Context import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* @@ -25,11 +26,22 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.sukisu.ultra.R -import com.sukisu.ultra.ui.component.* +import com.sukisu.ultra.ui.susfs.component.AddAppPathDialog +import com.sukisu.ultra.ui.susfs.component.AddKstatStaticallyDialog +import com.sukisu.ultra.ui.susfs.component.AddPathDialog +import com.sukisu.ultra.ui.susfs.component.AddTryUmountDialog +import com.sukisu.ultra.ui.susfs.component.ConfirmDialog +import com.sukisu.ultra.ui.susfs.component.EnabledFeaturesContent +import com.sukisu.ultra.ui.susfs.component.KstatConfigContent +import com.sukisu.ultra.ui.susfs.component.PathSettingsContent +import com.sukisu.ultra.ui.susfs.component.SusLoopPathsContent +import com.sukisu.ultra.ui.susfs.component.SusMountsContent +import com.sukisu.ultra.ui.susfs.component.SusPathsContent +import com.sukisu.ultra.ui.susfs.component.TryUmountContent import com.sukisu.ultra.ui.theme.CardConfig -import com.sukisu.ultra.ui.util.SuSFSManager -import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion158 -import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion159 +import com.sukisu.ultra.ui.susfs.util.SuSFSManager +import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158 +import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion159 import com.sukisu.ultra.ui.util.isAbDevice import kotlinx.coroutines.launch import java.io.File @@ -628,8 +640,21 @@ fun SuSFSConfigScreen( isLoading = true val success = if (editingKstatConfig != null) { SuSFSManager.editKstatConfig( - context, editingKstatConfig!!, path, ino, dev, nlink, size, atime, atimeNsec, - mtime, mtimeNsec, ctime, ctimeNsec, blocks, blksize + context, + editingKstatConfig!!, + path, + ino, + dev, + nlink, + size, + atime, + atimeNsec, + mtime, + mtimeNsec, + ctime, + ctimeNsec, + blocks, + blksize ) } else { SuSFSManager.addKstatStatically( @@ -1251,7 +1276,11 @@ fun SuSFSConfigScreen( onToggleHideSusMountsForAllProcs = { hideForAll -> coroutineScope.launch { isLoading = true - if (SuSFSManager.setHideSusMountsForAllProcs(context, hideForAll)) { + if (SuSFSManager.setHideSusMountsForAllProcs( + context, + hideForAll + ) + ) { hideSusMountsForAllProcs = hideForAll } isLoading = false @@ -1282,7 +1311,8 @@ fun SuSFSConfigScreen( onToggleUmountForZygoteIsoService = { enabled -> coroutineScope.launch { isLoading = true - val success = SuSFSManager.setUmountForZygoteIsoService(context, enabled) + val success = + SuSFSManager.setUmountForZygoteIsoService(context, enabled) if (success) { umountForZygoteIsoService = enabled } @@ -1393,7 +1423,7 @@ private fun BasicSettingsContent( isLoading: Boolean, onAutoStartToggle: (Boolean) -> Unit, onShowSlotInfo: () -> Unit, - context: android.content.Context, + context: Context, onShowBackupDialog: () -> Unit, onShowRestoreDialog: () -> Unit, enableHideBl: Boolean, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt index 3282881a..41a0c4ce 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.component +package com.sukisu.ultra.ui.susfs.component import android.annotation.SuppressLint import android.content.pm.PackageInfo @@ -29,7 +29,7 @@ import coil.compose.AsyncImage import coil.request.ImageRequest import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.sukisu.ultra.R -import com.sukisu.ultra.ui.util.SuSFSManager +import com.sukisu.ultra.ui.susfs.util.SuSFSManager import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import kotlinx.coroutines.launch diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt index a8d94b3d..0ec7aef2 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.component +package com.sukisu.ultra.ui.susfs.component import android.annotation.SuppressLint import androidx.compose.foundation.layout.* @@ -18,8 +18,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.sukisu.ultra.R -import com.sukisu.ultra.ui.util.SuSFSManager -import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion158 +import com.sukisu.ultra.ui.susfs.util.SuSFSManager +import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158 import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel /** diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt index 30e24d14..70a68481 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt @@ -1,10 +1,11 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.susfs.util import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo +import android.os.Build import android.util.Log import android.widget.Toast import com.dergoogler.mmrl.platform.Platform.Companion.context @@ -19,9 +20,13 @@ import java.io.File import java.io.FileOutputStream import java.io.IOException import androidx.core.content.edit +import com.sukisu.ultra.ui.util.getRootShell +import com.sukisu.ultra.ui.util.getSuSFSVersion import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import org.json.JSONArray import org.json.JSONObject import java.text.SimpleDateFormat import java.util.* @@ -112,7 +117,7 @@ object SuSFSManager { configurationsJson.keys().forEach { key -> val value = configurationsJson.get(key) configurations[key] = when (value) { - is org.json.JSONArray -> { + is JSONArray -> { val set = mutableSetOf() for (i in 0 until value.length()) { set.add(value.getString(i)) @@ -304,7 +309,7 @@ object SuSFSManager { fun saveExecuteInPostFsData(context: Context, executeInPostFsData: Boolean) { getPrefs(context).edit { putBoolean(KEY_EXECUTE_IN_POST_FS_DATA, executeInPostFsData) } if (isAutoStartEnabled(context)) { - kotlinx.coroutines.CoroutineScope(Dispatchers.Default).launch { + CoroutineScope(Dispatchers.Default).launch { updateMagiskModule(context) } } @@ -552,7 +557,7 @@ object SuSFSManager { // 获取设备信息 private fun getDeviceInfo(): String { return try { - "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL} (${android.os.Build.VERSION.RELEASE})" + "${Build.MANUFACTURER} ${Build.MODEL} (${Build.VERSION.RELEASE})" } catch (_: Exception) { "Unknown Device" } @@ -1359,7 +1364,7 @@ object SuSFSManager { if (success) { saveAndroidDataPath(context, path) if (isAutoStartEnabled(context)) { - kotlinx.coroutines.CoroutineScope(Dispatchers.Default).launch { + CoroutineScope(Dispatchers.Default).launch { updateMagiskModule(context) } } @@ -1373,7 +1378,7 @@ object SuSFSManager { if (success) { saveSdcardPath(context, path) if (isAutoStartEnabled(context)) { - kotlinx.coroutines.CoroutineScope(Dispatchers.Default).launch { + CoroutineScope(Dispatchers.Default).launch { updateMagiskModule(context) } } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt index 1cf9cf82..1f2768f7 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.susfs.util import android.annotation.SuppressLint diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt index ad8cb631..6325c4c2 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt @@ -34,8 +34,8 @@ import androidx.core.content.edit import androidx.core.net.toUri import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter -import com.sukisu.ultra.ui.util.BackgroundTransformation -import com.sukisu.ultra.ui.util.saveTransformedBackground +import com.sukisu.ultra.ui.theme.util.BackgroundTransformation +import com.sukisu.ultra.ui.theme.util.saveTransformedBackground import java.io.File import java.io.FileOutputStream import java.io.InputStream diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt similarity index 96% rename from manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt index 4dd8b2e1..408c78bf 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/ImageEditorDialog.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.component +package com.sukisu.ultra.ui.theme.component import android.net.Uri import androidx.compose.animation.core.animateFloatAsState @@ -32,9 +32,10 @@ import androidx.compose.ui.window.DialogProperties import coil.compose.AsyncImage import coil.request.ImageRequest import com.sukisu.ultra.R -import com.sukisu.ultra.ui.util.BackgroundTransformation -import com.sukisu.ultra.ui.util.saveTransformedBackground +import com.sukisu.ultra.ui.theme.util.BackgroundTransformation +import com.sukisu.ultra.ui.theme.util.saveTransformedBackground import kotlinx.coroutines.launch +import kotlin.math.abs import kotlin.math.max @Composable @@ -67,9 +68,9 @@ fun ImageEditorDialog( ) val updateTransformation = remember { { newScale: Float, newOffsetX: Float, newOffsetY: Float -> - val scaleDiff = kotlin.math.abs(newScale - lastScale) - val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX) - val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY) + val scaleDiff = abs(newScale - lastScale) + val offsetXDiff = abs(newOffsetX - lastOffsetX) + val offsetYDiff = abs(newOffsetY - lastOffsetY) if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) { scale = newScale offsetX = newOffsetX diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/util/BackgroundUtils.kt similarity index 98% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/theme/util/BackgroundUtils.kt index 1f662214..daf089b7 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/BackgroundUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/util/BackgroundUtils.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.theme.util import android.content.ContentResolver import android.content.Context diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleModify.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleModify.kt similarity index 92% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleModify.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleModify.kt index 190ce9c4..c0f52b1f 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleModify.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleModify.kt @@ -1,16 +1,20 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.util.module +import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import com.sukisu.ultra.R +import com.sukisu.ultra.ui.util.reboot import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -159,7 +163,8 @@ object ModuleModify { val moduleDir = "/data/adb/modules" // 直接从用户选择的文件读取并解压 - val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir")) + val process = Runtime.getRuntime() + .exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir")) context.contentResolver.openInputStream(uri)?.use { input -> input.copyTo(process.outputStream) @@ -277,7 +282,11 @@ object ModuleModify { } } catch (e: Exception) { - Log.e("AllowlistRestore", context.getString(R.string.allowlist_restore_failed, ""), e) + Log.e( + "AllowlistRestore", + context.getString(R.string.allowlist_restore_failed, ""), + e + ) withContext(Dispatchers.Main) { snackBarHost.showSnackbar( context.getString(R.string.allowlist_restore_failed, e.message), @@ -292,11 +301,11 @@ object ModuleModify { fun rememberModuleBackupLauncher( context: Context, snackBarHost: SnackbarHostState, - scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + scope: CoroutineScope = rememberCoroutineScope() ) = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { + if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> scope.launch { backupModules(context, snackBarHost, uri) @@ -309,8 +318,8 @@ object ModuleModify { fun rememberModuleRestoreLauncher( context: Context, snackBarHost: SnackbarHostState, - scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() - ): androidx.activity.result.ActivityResultLauncher { + scope: CoroutineScope = rememberCoroutineScope() + ): ActivityResultLauncher { var showRestoreDialog by remember { mutableStateOf(false) } var restoreConfirmResult by remember { mutableStateOf?>(null) } @@ -330,7 +339,7 @@ object ModuleModify { return rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { + if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> scope.launch { val confirmResult = CompletableDeferred() @@ -353,11 +362,11 @@ object ModuleModify { fun rememberAllowlistBackupLauncher( context: Context, snackBarHost: SnackbarHostState, - scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + scope: CoroutineScope = rememberCoroutineScope() ) = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { + if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> scope.launch { backupAllowlist(context, snackBarHost, uri) @@ -370,10 +379,14 @@ object ModuleModify { fun rememberAllowlistRestoreLauncher( context: Context, snackBarHost: SnackbarHostState, - scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() - ): androidx.activity.result.ActivityResultLauncher { + scope: CoroutineScope = rememberCoroutineScope() + ): ActivityResultLauncher { var showAllowlistRestoreDialog by remember { mutableStateOf(false) } - var allowlistRestoreConfirmResult by remember { mutableStateOf?>(null) } + var allowlistRestoreConfirmResult by remember { + mutableStateOf?>( + null + ) + } // 显示允许列表恢复确认对话框 AllowlistRestoreConfirmationDialog( @@ -391,7 +404,7 @@ object ModuleModify { return rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { + if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> scope.launch { val confirmResult = CompletableDeferred() diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleUtils.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleUtils.kt index 230b99f5..5113b4ac 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleUtils.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.util.module import android.content.Context import android.content.Intent diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleVerificationManager.kt similarity index 98% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt rename to manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleVerificationManager.kt index 9e70c40b..41948295 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/module/ModuleVerificationManager.kt @@ -1,9 +1,10 @@ -package com.sukisu.ultra.ui.util +package com.sukisu.ultra.ui.util.module import android.content.Context import android.net.Uri import android.util.Log import com.sukisu.ultra.Natives +import com.sukisu.ultra.ui.util.getRootShell import java.io.File import java.io.FileOutputStream diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt index f8dfe2e0..91f66d8f 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch import com.sukisu.ultra.ui.util.HanziToPinyin import com.sukisu.ultra.ui.util.listModules import com.sukisu.ultra.ui.util.getRootShell -import com.sukisu.ultra.ui.util.ModuleVerificationManager +import com.sukisu.ultra.ui.util.module.ModuleVerificationManager import kotlinx.coroutines.withContext import org.json.JSONArray import org.json.JSONObject diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AnimatedBottomBar.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AnimatedBottomBar.kt deleted file mode 100644 index 3d364e48..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AnimatedBottomBar.kt +++ /dev/null @@ -1,20 +0,0 @@ -package zako.zako.zako.zakoui.activity.util - -import androidx.compose.animation.* -import androidx.compose.runtime.Composable - -object AnimatedBottomBar { - @Composable - fun AnimatedBottomBarWrapper( - showBottomBar: Boolean, - content: @Composable () -> Unit - ) { - AnimatedVisibility( - visible = showBottomBar, - enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), - exit = slideOutVertically(targetOffsetY = { it }) + fadeOut() - ) { - content() - } - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AppData.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AppData.kt deleted file mode 100644 index 50f51368..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AppData.kt +++ /dev/null @@ -1,90 +0,0 @@ -package zako.zako.zako.zakoui.activity.util - -import com.sukisu.ultra.Natives -import com.sukisu.ultra.ui.util.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -object AppData { - object DataRefreshManager { - // 私有状态流 - private val _superuserCount = MutableStateFlow(0) - private val _moduleCount = MutableStateFlow(0) - private val _kpmModuleCount = MutableStateFlow(0) - - // 公开的只读状态流 - val superuserCount: StateFlow = _superuserCount.asStateFlow() - val moduleCount: StateFlow = _moduleCount.asStateFlow() - val kpmModuleCount: StateFlow = _kpmModuleCount.asStateFlow() - - /** - * 刷新所有数据计数 - */ - fun refreshData() { - _superuserCount.value = getSuperuserCountUse() - _moduleCount.value = getModuleCountUse() - _kpmModuleCount.value = getKpmModuleCountUse() - } - - } - - /** - * 获取超级用户应用计数 - */ - fun getSuperuserCountUse(): Int { - return try { - if (!rootAvailable()) return 0 - getSuperuserCount() - } catch (_: Exception) { - 0 - } - } - - /** - * 获取模块计数 - */ - fun getModuleCountUse(): Int { - return try { - if (!rootAvailable()) return 0 - getModuleCount() - } catch (_: Exception) { - 0 - } - } - - /** - * 获取KPM模块计数 - */ - fun getKpmModuleCountUse(): Int { - return try { - if (!rootAvailable()) return 0 - val kpmVersion = getKpmVersionUse() - if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) return 0 - getKpmModuleCount() - } catch (_: Exception) { - 0 - } - } - - /** - * 获取KPM版本 - */ - fun getKpmVersionUse(): String { - return try { - if (!rootAvailable()) return "" - val version = getKpmVersion() - version.ifEmpty { "" } - } catch (e: Exception) { - "Error: ${e.message}" - } - } - - /** - * 检查是否是完整功能模式 - */ - fun isFullFeatured(packageName: String): Boolean { - val isManager = Natives.becomeManager(packageName) - return isManager && !Natives.requireNewKernel() && rootAvailable() - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DataRefreshUtils.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DataRefreshUtils.kt deleted file mode 100644 index e09b1949..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DataRefreshUtils.kt +++ /dev/null @@ -1,46 +0,0 @@ -package zako.zako.zako.zakoui.activity.util - -import android.content.Context -import androidx.lifecycle.LifecycleCoroutineScope -import com.sukisu.ultra.ui.MainActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager - -object DataRefreshUtils { - - fun startDataRefreshCoroutine(scope: LifecycleCoroutineScope) { - scope.launch(Dispatchers.IO) { - while (isActive) { - DataRefreshManager.refreshData() - delay(5000) - } - } - } - - fun startSettingsMonitorCoroutine( - scope: LifecycleCoroutineScope, - activity: MainActivity, - settingsStateFlow: MutableStateFlow - ) { - scope.launch(Dispatchers.IO) { - while (isActive) { - val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE) - settingsStateFlow.value = MainActivity.SettingsState( - isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false), - showKpmInfo = prefs.getBoolean("show_kpm_info", false) - ) - delay(1000) - } - } - } - - fun refreshData(scope: LifecycleCoroutineScope) { - scope.launch { - DataRefreshManager.refreshData() - } - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DisplayUtils.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DisplayUtils.kt deleted file mode 100644 index bd6c1282..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/DisplayUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package zako.zako.zako.zakoui.activity.util - -import android.content.Context - -object DisplayUtils { - - fun applyCustomDpi(context: Context) { - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - val customDpi = prefs.getInt("app_dpi", 0) - - if (customDpi > 0) { - try { - val resources = context.resources - val metrics = resources.displayMetrics - metrics.density = customDpi / 160f - @Suppress("DEPRECATION") - metrics.scaledDensity = customDpi / 160f - metrics.densityDpi = customDpi - } catch (e: Exception) { - e.printStackTrace() - } - } - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/LocaleUtils.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/LocaleUtils.kt deleted file mode 100644 index 50debbc8..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/LocaleUtils.kt +++ /dev/null @@ -1,48 +0,0 @@ -package zako.zako.zako.zakoui.activity.util - -import android.annotation.SuppressLint -import android.content.Context -import android.content.res.Configuration -import android.os.Build -import java.util.* - -object LocaleUtils { - - @SuppressLint("ObsoleteSdkInt") - fun applyLanguageSetting(context: Context) { - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - val languageCode = prefs.getString("app_language", "") ?: "" - - if (languageCode.isNotEmpty()) { - val locale = Locale.forLanguageTag(languageCode) - Locale.setDefault(locale) - - val resources = context.resources - val config = Configuration(resources.configuration) - config.setLocale(locale) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.createConfigurationContext(config) - } else { - @Suppress("DEPRECATION") - resources.updateConfiguration(config, resources.displayMetrics) - } - } - } - - fun applyLocale(context: Context): Context { - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - val languageCode = prefs.getString("app_language", "") ?: "" - - var newContext = context - if (languageCode.isNotEmpty()) { - val locale = Locale.forLanguageTag(languageCode) - Locale.setDefault(locale) - - val config = Configuration(context.resources.configuration) - config.setLocale(locale) - newContext = context.createConfigurationContext(config) - } - - return newContext - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt deleted file mode 100644 index 3dbd4f23..00000000 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt +++ /dev/null @@ -1,1711 +0,0 @@ -package zako.zako.zako.zakoui.screen - -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 android.widget.Toast -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.* -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.automirrored.filled.NavigateNext -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.content.edit -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.sukisu.ultra.Natives -import com.sukisu.ultra.R -import com.sukisu.ultra.ksuApp -import com.sukisu.ultra.ui.component.ImageEditorDialog -import com.sukisu.ultra.ui.component.KsuIsValid -import com.sukisu.ultra.ui.theme.* -import com.sukisu.ultra.ui.theme.CardConfig.cardElevation -import com.sukisu.ultra.ui.util.* -import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.util.* -import kotlin.math.roundToInt - -/** - * @author ShirkNeko - * @date 2025/5/31. - */ -private val SETTINGS_ITEM_HEIGHT = 56.dp -private val SETTINGS_GROUP_SPACING = 16.dp - -/** - * 保存卡片配置 - */ -fun saveCardConfig(context: Context) { - CardConfig.save(context) -} - -/** - * 更多设置屏幕 - */ -@SuppressLint("LocalContextConfigurationRead", "LocalContextResourcesRead", "ObsoleteSdkInt") -@OptIn(ExperimentalMaterial3Api::class) -@Destination -@Composable -fun MoreSettingsScreen( - navigator: DestinationsNavigator -) { - // 顶部滚动行为 - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() - val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) } - val systemIsDark = isSystemInDarkTheme() - - - // 主题模式选择 - var themeMode by remember { - mutableIntStateOf( - when(ThemeConfig.forceDarkMode) { - true -> 2 // 深色 - false -> 1 // 浅色 - null -> 0 // 跟随系统 - } - ) - } - - // 动态颜色开关状态 - var useDynamicColor by remember { - mutableStateOf(ThemeConfig.useDynamicColor) - } - - // 对话框显示状态 - var showThemeModeDialog by remember { mutableStateOf(false) } - var showLanguageDialog by remember { mutableStateOf(false) } - var showThemeColorDialog by remember { mutableStateOf(false) } - var showDpiConfirmDialog by remember { mutableStateOf(false) } - var showImageEditor by remember { mutableStateOf(false) } - - // 动态管理器配置状态 - var dynamicSignConfig by remember { mutableStateOf(null) } - var isDynamicSignEnabled by remember { mutableStateOf(false) } - var dynamicSignSize by remember { mutableStateOf("") } - var dynamicSignHash by remember { mutableStateOf("") } - var showDynamicSignDialog by remember { mutableStateOf(false) } - - // 主题模式选项 - val themeOptions = listOf( - stringResource(R.string.theme_follow_system), - stringResource(R.string.theme_light), - stringResource(R.string.theme_dark) - ) - - // 获取当前语言设置 - var currentLanguage by remember { - mutableStateOf(prefs.getString("app_language", "") ?: "") - } - - // 获取支持的语言列表 - val supportedLanguages = remember { - val languages = mutableListOf>() - languages.add("" to context.getString(R.string.language_follow_system)) - val locales = context.resources.configuration.locales - for (i in 0 until locales.size()) { - val locale = locales.get(i) - val code = locale.toLanguageTag() - if (!languages.any { it.first == code }) { - languages.add(code to locale.getDisplayName(locale)) - } - } - - val commonLocales = listOf( - Locale.forLanguageTag("en"), // 英语 - Locale.forLanguageTag("zh-CN"), // 简体中文 - Locale.forLanguageTag("zh-HK"), // 繁体中文(香港) - Locale.forLanguageTag("zh-TW"), // 繁体中文(台湾) - Locale.forLanguageTag("ja"), // 日语 - Locale.forLanguageTag("fr"), // 法语 - Locale.forLanguageTag("de"), // 德语 - Locale.forLanguageTag("es"), // 西班牙语 - Locale.forLanguageTag("it"), // 意大利语 - Locale.forLanguageTag("ru"), // 俄语 - Locale.forLanguageTag("pt"), // 葡萄牙语 - Locale.forLanguageTag("ko"), // 韩语 - Locale.forLanguageTag("vi") // 越南语 - ) - - for (locale in commonLocales) { - val code = locale.toLanguageTag() - if (!languages.any { it.first == code }) { - val config = Configuration(context.resources.configuration) - config.setLocale(locale) - try { - val testContext = context.createConfigurationContext(config) - testContext.getString(R.string.language_follow_system) - languages.add(code to locale.getDisplayName(locale)) - } catch (_: Exception) { - } - } - } - languages - } - - // 简洁模式开关状态 - var isSimpleMode by remember { - mutableStateOf(prefs.getBoolean("is_simple_mode", false)) - } - - // 隐藏内核版本号开关状态 - var isHideVersion by remember { - mutableStateOf(prefs.getBoolean("is_hide_version", false)) - } - - // 隐藏模块数量等信息开关状态 - var isHideOtherInfo by remember { - mutableStateOf(prefs.getBoolean("is_hide_other_info", false)) - } - - // 显示KPM开关状态 - var isShowKpmInfo by remember { - mutableStateOf(prefs.getBoolean("show_kpm_info", false)) - } - - // 隐藏 Zygisk 状态开关状态 - var isHideZygiskImplement by remember { - mutableStateOf(prefs.getBoolean("is_hide_zygisk_Implement", false)) - } - - // 隐藏SuSFS状态开关状态 - var isHideSusfsStatus by remember { - mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false)) - } - - // 隐藏链接状态开关状态 - var isHideLinkCard by remember { - mutableStateOf(prefs.getBoolean("is_hide_link_card", false)) - } - - // 隐藏标签行开关状态 - var isHideTagRow by remember { - mutableStateOf(prefs.getBoolean("is_hide_tag_row", false)) - } - - // 内核版本简洁模式开关状态 - var isKernelSimpleMode by remember { - mutableStateOf(prefs.getBoolean("is_kernel_simple_mode", false)) - } - - // 显示更多模块信息开关状态 - var showMoreModuleInfo by remember { - mutableStateOf(prefs.getBoolean("show_more_module_info", false)) - } - - // SELinux状态 - var selinuxEnabled by remember { - mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing") - } - - // 卡片配置状态 - var cardAlpha by rememberSaveable { mutableFloatStateOf(CardConfig.cardAlpha) } - var cardDim by rememberSaveable { mutableFloatStateOf(CardConfig.cardDim) } - var isCustomBackgroundEnabled by rememberSaveable { - mutableStateOf(ThemeConfig.customBackgroundUri != null) - } - - // 备用图标状态 - var useAltIcon by remember { mutableStateOf(prefs.getBoolean("use_alt_icon", false)) } - - // 图片选择状态 - var selectedImageUri by remember { mutableStateOf(null) } - - // DPI 设置 - val systemDpi = remember { context.resources.displayMetrics.densityDpi } - var currentDpi by remember { - mutableIntStateOf(prefs.getInt("app_dpi", systemDpi)) - } - var tempDpi by remember { mutableIntStateOf(currentDpi) } - var isDpiCustom by remember { mutableStateOf(true) } - - // 预设 DPI 选项 - val dpiPresets = mapOf( - stringResource(R.string.dpi_size_small) to 240, - stringResource(R.string.dpi_size_medium) to 320, - stringResource(R.string.dpi_size_large) to 420, - stringResource(R.string.dpi_size_extra_large) to 560 - ) - - // 主题色选项 - val themeColorOptions = listOf( - stringResource(R.string.color_default) to ThemeColors.Default, - stringResource(R.string.color_green) to ThemeColors.Green, - stringResource(R.string.color_purple) to ThemeColors.Purple, - stringResource(R.string.color_orange) to ThemeColors.Orange, - stringResource(R.string.color_pink) to ThemeColors.Pink, - stringResource(R.string.color_gray) to ThemeColors.Gray, - stringResource(R.string.color_yellow) to ThemeColors.Yellow - ) - - - // 更新简洁模式开关状态 - val onSimpleModeChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_simple_mode", newValue) } - isSimpleMode = newValue - } - - // 内核版本简洁模式开关状态 - val onKernelSimpleModeChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_kernel_simple_mode", newValue) } - isKernelSimpleMode = newValue - } - - // 隐藏内核版本号开关状态 - val onHideVersionChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_version", newValue) } - isHideVersion = newValue - } - - // 隐藏模块数量等信息开关状态 - val onHideOtherInfoChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_other_info", newValue) } - isHideOtherInfo = newValue - } - - // 更新显示KPM开关状态 - val onShowKpmInfoChange = { newValue: Boolean -> - prefs.edit { putBoolean("show_kpm_info", newValue) } - isShowKpmInfo = newValue - } - - // 隐藏SuSFS状态开关状态 - val onHideSusfsStatusChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_susfs_status", newValue) } - isHideSusfsStatus = newValue - } - - val onHideZygiskImplement = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_zygisk_Implement", newValue) } - isHideZygiskImplement = newValue - - } - - // 隐藏链接状态开关状态 - val onHideLinkCardChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_link_card", newValue) } - isHideLinkCard = newValue - } - - // 隐藏标签行开关状态 - val onHideTagRowChange = { newValue: Boolean -> - prefs.edit { putBoolean("is_hide_tag_row", newValue) } - isHideTagRow = newValue - } - - // 显示更多模块信息开关状态 - val onShowMoreModuleInfoChange = { newValue: Boolean -> - prefs.edit { putBoolean("show_more_module_info", newValue) } - showMoreModuleInfo = newValue - } - - // 备用图标开关状态 - val onUseAltIconChange = { newValue: Boolean -> - prefs.edit { putBoolean("use_alt_icon", newValue) } - useAltIcon = newValue - toggleLauncherIcon(context, newValue) - Toast.makeText(context, context.getString(R.string.icon_switched), Toast.LENGTH_SHORT).show() - } - - - // 获取DPI大小友好名称 - @Composable - fun getDpiFriendlyName(dpi: Int): String { - return when (dpi) { - 240 -> stringResource(R.string.dpi_size_small) - 320 -> stringResource(R.string.dpi_size_medium) - 420 -> stringResource(R.string.dpi_size_large) - 560 -> stringResource(R.string.dpi_size_extra_large) - else -> stringResource(R.string.dpi_size_custom) - } - } - - // 应用 DPI 设置 - val applyDpiSetting = { dpi: Int -> - if (dpi != currentDpi) { - // 保存到 SharedPreferences - prefs.edit { - putInt("app_dpi", dpi) - } - - // 只修改应用级别的DPI设置 - currentDpi = dpi - tempDpi = dpi - Toast.makeText( - context, - context.getString(R.string.dpi_applied_success, dpi), - Toast.LENGTH_SHORT - ).show() - - val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) - restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(restartIntent) - - showDpiConfirmDialog = false - } - } - - // 应用语言设置 - val applyLanguageSetting = { code: String -> - if (currentLanguage != code) { - prefs.edit { - putString("app_language", code) - commit() - } - - currentLanguage = code - - Toast.makeText( - context, - context.getString(R.string.language_changed), - Toast.LENGTH_SHORT - ).show() - - val locale = if (code.isEmpty()) Locale.getDefault() else Locale.forLanguageTag(code) - Locale.setDefault(locale) - val config = Configuration(context.resources.configuration) - config.setLocale(locale) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.createConfigurationContext(config) - } else { - @Suppress("DEPRECATION") - context.resources.updateConfiguration(config, context.resources.displayMetrics) - } - ksuApp.refreshCurrentActivity() - } - } - - // ========== 初始化 ========== - - // 初始化卡片配置 - LaunchedEffect(Unit) { - // 加载设置 - CardConfig.load(context) - cardAlpha = CardConfig.cardAlpha - cardDim = CardConfig.cardDim - isCustomBackgroundEnabled = ThemeConfig.customBackgroundUri != null - - // 设置主题模式 - themeMode = when (ThemeConfig.forceDarkMode) { - true -> 2 - false -> 1 - null -> 0 - } - - // 确保卡片样式跟随主题模式 - when (themeMode) { - 2 -> { // 深色 - CardConfig.isUserDarkModeEnabled = true - CardConfig.isUserLightModeEnabled = false - } - 1 -> { // 浅色 - CardConfig.isUserDarkModeEnabled = false - CardConfig.isUserLightModeEnabled = true - } - 0 -> { // 跟随系统 - CardConfig.isUserDarkModeEnabled = false - CardConfig.isUserLightModeEnabled = false - } - } - - // 如果启用了系统跟随且系统是深色模式,应用深色模式默认值 - if (themeMode == 0 && systemIsDark) { - CardConfig.setThemeDefaults(true) - } - - currentDpi = prefs.getInt("app_dpi", systemDpi) - tempDpi = currentDpi - - CardConfig.save(context) - } - - // 图片选择器 - val pickImageLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.GetContent() - ) { uri: Uri? -> - uri?.let { - selectedImageUri = it - showImageEditor = true - } - } - - // ========== UI 构建 ========== - - // 显示图片编辑对话框 - if (showImageEditor && selectedImageUri != null) { - ImageEditorDialog( - imageUri = selectedImageUri!!, - onDismiss = { - showImageEditor = false - selectedImageUri = null - }, - onConfirm = { transformedUri -> - context.saveAndApplyCustomBackground(transformedUri) - isCustomBackgroundEnabled = true - cardElevation = 0.dp - CardConfig.isCustomBackgroundEnabled = true - saveCardConfig(context) - showImageEditor = false - selectedImageUri = null - - // 显示成功提示 - Toast.makeText( - context, - context.getString(R.string.background_set_success), - Toast.LENGTH_SHORT - ).show() - } - ) - } - - // 主题模式选择对话框 - if (showThemeModeDialog) { - SingleChoiceDialog( - title = stringResource(R.string.theme_mode), - options = themeOptions, - selectedIndex = themeMode, - onOptionSelected = { index -> - themeMode = index - val newThemeMode = when(index) { - 0 -> null // 跟随系统 - 1 -> false // 浅色 - 2 -> true // 深色 - else -> null - } - context.saveThemeMode(newThemeMode) - when (index) { - 2 -> { // 深色 - ThemeConfig.forceDarkMode = true - CardConfig.isUserDarkModeEnabled = true - CardConfig.isUserLightModeEnabled = false - CardConfig.setThemeDefaults(true) - CardConfig.save(context) - } - 1 -> { // 浅色 - ThemeConfig.forceDarkMode = false - CardConfig.isUserLightModeEnabled = true - CardConfig.isUserDarkModeEnabled = false - CardConfig.setThemeDefaults(false) - CardConfig.save(context) - } - 0 -> { // 跟随系统 - ThemeConfig.forceDarkMode = null - CardConfig.isUserLightModeEnabled = false - CardConfig.isUserDarkModeEnabled = false - val isNightModeActive = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES - CardConfig.setThemeDefaults(isNightModeActive) - CardConfig.save(context) - } - } - }, - onDismiss = { showThemeModeDialog = false } - ) - } - - // 语言切换对话框 - if (showLanguageDialog) { - KeyValueChoiceDialog( - title = stringResource(R.string.language_setting), - options = supportedLanguages, - selectedCode = currentLanguage, - onOptionSelected = { code -> - applyLanguageSetting(code) - }, - onDismiss = { showLanguageDialog = false } - ) - } - - // DPI 设置确认对话框 - if (showDpiConfirmDialog) { - ConfirmDialog( - title = stringResource(R.string.dpi_confirm_title), - message = stringResource(R.string.dpi_confirm_message, currentDpi, tempDpi), - summaryText = stringResource(R.string.dpi_confirm_summary), - confirmText = stringResource(R.string.confirm), - dismissText = stringResource(R.string.cancel), - onConfirm = { applyDpiSetting(tempDpi) }, - onDismiss = { - showDpiConfirmDialog = false - tempDpi = currentDpi - } - ) - } - - // 主题色选择对话框 - if (showThemeColorDialog) { - AlertDialog( - onDismissRequest = { showThemeColorDialog = false }, - title = { Text(stringResource(R.string.choose_theme_color)) }, - text = { - Column { - themeColorOptions.forEach { (name, theme) -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - context.saveThemeColors(when (theme) { - ThemeColors.Green -> "green" - ThemeColors.Purple -> "purple" - ThemeColors.Orange -> "orange" - ThemeColors.Pink -> "pink" - ThemeColors.Gray -> "gray" - ThemeColors.Yellow -> "yellow" - else -> "default" - }) - showThemeColorDialog = false - } - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - val isDark = isSystemInDarkTheme() - Box( - modifier = Modifier.padding(end = 12.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - ColorCircle( - color = if (isDark) theme.primaryDark else theme.primaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - ColorCircle( - color = if (isDark) theme.secondaryDark else theme.secondaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - ColorCircle( - color = if (isDark) theme.tertiaryDark else theme.tertiaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - } - } - Text(name) - Spacer(modifier = Modifier.weight(1f)) - // 当前选中的主题显示选中标记 - if (ThemeConfig.currentTheme::class == theme::class) { - Icon( - Icons.Default.Check, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - } - } - } - }, - confirmButton = { - Button( - onClick = { showThemeColorDialog = false } - ) { - Text(stringResource(R.string.cancel)) - } - } - ) - } - - LaunchedEffect(Unit) { - // 初始化动态管理器配置 - dynamicSignConfig = Natives.getDynamicManager() - dynamicSignConfig?.let { config -> - if (config.isValid()) { - isDynamicSignEnabled = true - dynamicSignSize = config.size.toString() - dynamicSignHash = config.hash - } - } - } - - fun parseDynamicSignSize(input: String): Int? { - return try { - when { - input.startsWith("0x", true) -> input.substring(2).toInt(16) - else -> input.toInt() - } - } catch (_: NumberFormatException) { - null - } - } - - // 动态管理器配置对话框 - if (showDynamicSignDialog) { - AlertDialog( - onDismissRequest = { showDynamicSignDialog = false }, - title = { Text(stringResource(R.string.dynamic_manager_title)) }, - text = { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { - // 启用开关 - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { isDynamicSignEnabled = !isDynamicSignEnabled } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Switch( - checked = isDynamicSignEnabled, - onCheckedChange = { isDynamicSignEnabled = it } - ) - Spacer(modifier = Modifier.width(12.dp)) - Text(stringResource(R.string.enable_dynamic_manager)) - } - - Spacer(modifier = Modifier.height(16.dp)) - - // 签名大小输入 - OutlinedTextField( - value = dynamicSignSize, - onValueChange = { input -> - val isValid = when { - input.isEmpty() -> true - input.matches(Regex("^\\d+$")) -> true - input.matches(Regex("^0[xX][0-9a-fA-F]*$")) -> true - else -> false - } - if (isValid) { - dynamicSignSize = input - } - }, - label = { Text(stringResource(R.string.signature_size)) }, - enabled = isDynamicSignEnabled, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text - ) - ) - - Spacer(modifier = Modifier.height(12.dp)) - - // 签名哈希输入 - OutlinedTextField( - value = dynamicSignHash, - onValueChange = { hash -> - if (hash.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) { - dynamicSignHash = hash - } - }, - label = { Text(stringResource(R.string.signature_hash)) }, - enabled = isDynamicSignEnabled, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - supportingText = { - Text(stringResource(R.string.hash_must_be_64_chars)) - }, - isError = isDynamicSignEnabled && dynamicSignHash.isNotEmpty() && dynamicSignHash.length != 64 - ) - } - }, - confirmButton = { - Button( - onClick = { - if (isDynamicSignEnabled) { - val size = parseDynamicSignSize(dynamicSignSize) - if (size != null && size > 0 && dynamicSignHash.length == 64) { - val success = Natives.setDynamicManager(size, dynamicSignHash) - if (success) { - dynamicSignConfig = Natives.DynamicManagerConfig(size, dynamicSignHash) - Toast.makeText( - context, - context.getString(R.string.dynamic_manager_set_success), - Toast.LENGTH_SHORT - ).show() - } else { - Toast.makeText( - context, - context.getString(R.string.dynamic_manager_set_failed), - Toast.LENGTH_SHORT - ).show() - } - } else { - Toast.makeText( - context, - context.getString(R.string.invalid_sign_config), - Toast.LENGTH_SHORT - ).show() - return@Button - } - } else { - val success = Natives.clearDynamicManager() - if (success) { - dynamicSignConfig = null - dynamicSignSize = "" - dynamicSignHash = "" - Toast.makeText( - context, - context.getString(R.string.dynamic_manager_disabled_success), - Toast.LENGTH_SHORT - ).show() - } else { - Toast.makeText( - context, - context.getString(R.string.dynamic_manager_clear_failed), - Toast.LENGTH_SHORT - ).show() - return@Button - } - } - showDynamicSignDialog = false - }, - enabled = if (isDynamicSignEnabled) { - parseDynamicSignSize(dynamicSignSize)?.let { it > 0 } == true && - dynamicSignHash.length == 64 - } else true - ) { - Text(stringResource(R.string.confirm)) - } - }, - dismissButton = { - TextButton(onClick = { showDynamicSignDialog = false }) { - Text(stringResource(R.string.cancel)) - } - } - ) - } - - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopAppBar( - title = { - Text( - text = stringResource(R.string.more_settings), - style = MaterialTheme.typography.titleLarge - ) - }, - navigationIcon = { - IconButton(onClick = { - navigator.popBackStack() - }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.back) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = CardConfig.cardAlpha), - scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = CardConfig.cardAlpha) - ), - windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - scrollBehavior = scrollBehavior - ) - }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .verticalScroll(rememberScrollState()) - .padding(horizontal = 16.dp) - .padding(top = 8.dp) - ) { - // ========== 外观设置部分 ========== - SettingsCard( - title = stringResource(R.string.appearance_settings), - ) { - // 语言设置 - SettingItem( - icon = Icons.Default.Language, - title = stringResource(R.string.language_setting), - subtitle = supportedLanguages.find { it.first == currentLanguage }?.second - ?: stringResource(R.string.language_follow_system), - onClick = { showLanguageDialog = true } - ) - - // 主题模式 - SettingItem( - icon = Icons.Default.DarkMode, - title = stringResource(R.string.theme_mode), - subtitle = themeOptions[themeMode], - onClick = { showThemeModeDialog = true } - ) - - // 动态颜色开关 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - SwitchSettingItem( - icon = Icons.Filled.ColorLens, - title = stringResource(R.string.dynamic_color_title), - summary = stringResource(R.string.dynamic_color_summary), - checked = useDynamicColor, - onChange = { enabled -> - useDynamicColor = enabled - context.saveDynamicColorState(enabled) - } - ) - } - - // 只在未启用动态颜色时显示主题色选择 - AnimatedVisibility( - visible = Build.VERSION.SDK_INT < Build.VERSION_CODES.S || !useDynamicColor, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically() - ) { - SettingItem( - icon = Icons.Default.Palette, - title = stringResource(R.string.theme_color), - subtitle = when (ThemeConfig.currentTheme) { - is ThemeColors.Green -> stringResource(R.string.color_green) - is ThemeColors.Purple -> stringResource(R.string.color_purple) - is ThemeColors.Orange -> stringResource(R.string.color_orange) - is ThemeColors.Pink -> stringResource(R.string.color_pink) - is ThemeColors.Gray -> stringResource(R.string.color_gray) - is ThemeColors.Yellow -> stringResource(R.string.color_yellow) - else -> stringResource(R.string.color_default) - }, - onClick = { showThemeColorDialog = true }, - trailingContent = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(start = 8.dp) - ) { - // 显示当前主题的三种主题色调 - val theme = ThemeConfig.currentTheme - val isDark = isSystemInDarkTheme() - - // Primary color - ColorCircle( - color = if (isDark) theme.primaryDark else theme.primaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - - // Secondary color - ColorCircle( - color = if (isDark) theme.secondaryDark else theme.secondaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - - // Tertiary color - ColorCircle( - color = if (isDark) theme.tertiaryDark else theme.tertiaryLight, - isSelected = false, - modifier = Modifier.padding(horizontal = 2.dp) - ) - } - } - ) - } - - SettingsDivider() - - // DPI 设置 - SettingItem( - icon = Icons.Default.FormatSize, - title = stringResource(R.string.app_dpi_title), - subtitle = stringResource(R.string.app_dpi_summary), - onClick = {}, - trailingContent = { - Text( - text = getDpiFriendlyName(tempDpi), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary - ) - } - ) - - // DPI 滑动条 - Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { - val sliderValue by animateFloatAsState( - targetValue = tempDpi.toFloat(), - label = "DPI Slider Animation" - ) - - Slider( - value = sliderValue, - onValueChange = { - tempDpi = it.toInt() - isDpiCustom = !dpiPresets.containsValue(tempDpi) - }, - valueRange = 160f..600f, - steps = 11, - colors = SliderDefaults.colors( - thumbColor = MaterialTheme.colorScheme.primary, - activeTrackColor = MaterialTheme.colorScheme.primary, - inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant - ) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) { - dpiPresets.forEach { (name, dpi) -> - val isSelected = tempDpi == dpi - val buttonColor = if (isSelected) - MaterialTheme.colorScheme.primaryContainer - else - MaterialTheme.colorScheme.surfaceVariant - - Box( - modifier = Modifier - .weight(1f) - .padding(horizontal = 2.dp) - .clip(RoundedCornerShape(8.dp)) - .background(buttonColor) - .clickable { - tempDpi = dpi - isDpiCustom = false - } - .padding(vertical = 8.dp, horizontal = 4.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = name, - style = MaterialTheme.typography.labelMedium, - color = if (isSelected) - MaterialTheme.colorScheme.onPrimaryContainer - else - MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } - - Text( - text = if (isDpiCustom) - "${stringResource(R.string.dpi_size_custom)}: $tempDpi" - else - "${getDpiFriendlyName(tempDpi)}: $tempDpi", - style = MaterialTheme.typography.bodySmall, - modifier = Modifier.padding(top = 8.dp) - ) - - Button( - onClick = { - if (tempDpi != currentDpi) { - showDpiConfirmDialog = true - } - }, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - enabled = tempDpi != currentDpi - ) { - Icon( - Icons.Default.Check, - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.dpi_apply_settings)) - } - } - - SettingsDivider() - - // 自定义背景开关 - SwitchSettingItem( - icon = Icons.Filled.Wallpaper, - title = stringResource(id = R.string.settings_custom_background), - summary = stringResource(id = R.string.settings_custom_background_summary), - checked = isCustomBackgroundEnabled, - onChange = { isChecked -> - if (isChecked) { - pickImageLauncher.launch("image/*") - } else { - context.saveCustomBackground(null) - isCustomBackgroundEnabled = false - CardConfig.cardAlpha = 1f - CardConfig.cardDim = 0f - CardConfig.isCustomAlphaSet = false - CardConfig.isCustomDimSet = false - CardConfig.isCustomBackgroundEnabled = false - saveCardConfig(context) - - // 重置其他相关设置 - ThemeConfig.needsResetOnThemeChange = true - ThemeConfig.preventBackgroundRefresh = false - - context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) - .edit { - putBoolean( - "prevent_background_refresh", - false - ) - } - - Toast.makeText( - context, - context.getString(R.string.background_removed), - Toast.LENGTH_SHORT - ).show() - } - } - ) - - // 透明度和亮度调节滑动条 - AnimatedVisibility( - visible = ThemeConfig.customBackgroundUri != null, - enter = fadeIn() + slideInVertically(), - exit = fadeOut() + slideOutVertically() - ) { - Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { - // 透明度滑动条 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 4.dp) - ) { - Icon( - Icons.Filled.Opacity, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.settings_card_alpha), - style = MaterialTheme.typography.titleSmall - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "${(cardAlpha * 100).roundToInt()}%", - style = MaterialTheme.typography.labelMedium, - ) - } - - val alphaSliderValue by animateFloatAsState( - targetValue = cardAlpha, - label = "Alpha Slider Animation" - ) - - Slider( - value = alphaSliderValue, - onValueChange = { newValue -> - cardAlpha = newValue - CardConfig.cardAlpha = newValue - CardConfig.isCustomAlphaSet = true - prefs.edit { - putBoolean("is_custom_alpha_set", true) - putFloat("card_alpha", newValue) - } - }, - onValueChangeFinished = { - coroutineScope.launch(Dispatchers.IO) { - saveCardConfig(context) - } - }, - valueRange = 0f..1f, - steps = 20, - colors = SliderDefaults.colors( - thumbColor = MaterialTheme.colorScheme.primary, - activeTrackColor = MaterialTheme.colorScheme.primary, - inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant - ) - ) - - // 亮度调节滑动条 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(top = 16.dp, bottom = 4.dp) - ) { - Icon( - Icons.Filled.LightMode, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.settings_card_dim), - style = MaterialTheme.typography.titleSmall - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "${(cardDim * 100).roundToInt()}%", - style = MaterialTheme.typography.labelMedium, - ) - } - - val dimSliderValue by animateFloatAsState( - targetValue = cardDim, - label = "Dim Slider Animation" - ) - - Slider( - value = dimSliderValue, - onValueChange = { newValue -> - cardDim = newValue - CardConfig.cardDim = newValue - CardConfig.isCustomDimSet = true - prefs.edit { - putBoolean("is_custom_dim_set", true) - putFloat("card_dim", newValue) - } - }, - onValueChangeFinished = { - coroutineScope.launch(Dispatchers.IO) { - saveCardConfig(context) - } - }, - valueRange = 0f..1f, - steps = 20, - colors = SliderDefaults.colors( - thumbColor = MaterialTheme.colorScheme.primary, - activeTrackColor = MaterialTheme.colorScheme.primary, - inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant - ) - ) - } - } - } - - // 自定义设置 - SettingsCard( - title = stringResource(R.string.custom_settings) - ) { - // 图标切换 - SwitchSettingItem( - icon = Icons.Default.Android, - title = stringResource(R.string.icon_switch_title), - summary = stringResource(R.string.icon_switch_summary), - checked = useAltIcon, - onChange = onUseAltIconChange - ) - - // 显示更多模块信息开关 - SwitchSettingItem( - icon = Icons.Filled.Info, - title = stringResource(R.string.show_more_module_info), - summary = stringResource(R.string.show_more_module_info_summary), - checked = showMoreModuleInfo, - onChange = onShowMoreModuleInfoChange - ) - - // 添加简洁模式开关 - SwitchSettingItem( - icon = Icons.Filled.Brush, - title = stringResource(R.string.simple_mode), - summary = stringResource(R.string.simple_mode_summary), - checked = isSimpleMode, - onChange = onSimpleModeChange - ) - - SwitchSettingItem( - icon = Icons.Filled.Brush, - title = stringResource(R.string.kernel_simple_kernel), - summary = stringResource(R.string.kernel_simple_kernel_summary), - checked = isKernelSimpleMode, - onChange = onKernelSimpleModeChange - ) - - // 隐藏内核部分版本号 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_kernel_kernelsu_version), - summary = stringResource(R.string.hide_kernel_kernelsu_version_summary), - checked = isHideVersion, - onChange = onHideVersionChange - ) - - // 模块数量等信息 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_other_info), - summary = stringResource(R.string.hide_other_info_summary), - checked = isHideOtherInfo, - onChange = onHideOtherInfoChange - ) - - // SuSFS 状态信息 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_susfs_status), - summary = stringResource(R.string.hide_susfs_status_summary), - checked = isHideSusfsStatus, - onChange = onHideSusfsStatusChange - ) - - // Zygsik 实现状态信息 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_zygisk_implement), - summary = stringResource(R.string.hide_zygisk_implement_summary), - checked = isHideZygiskImplement, - onChange = onHideZygiskImplement - ) - - if (Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) { - // 隐藏KPM开关 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.show_kpm_info), - summary = stringResource(R.string.show_kpm_info_summary), - checked = isShowKpmInfo, - onChange = onShowKpmInfoChange - ) - } - - // 隐藏链接信息 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_link_card), - summary = stringResource(R.string.hide_link_card_summary), - checked = isHideLinkCard, - onChange = onHideLinkCardChange - ) - - // 隐藏标签行 - SwitchSettingItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_tag_card), - summary = stringResource(R.string.hide_tag_card_summary), - checked = isHideTagRow, - onChange = onHideTagRowChange - ) - } - KsuIsValid { - // 高级设置 - SettingsCard( - title = stringResource(R.string.advanced_settings) - ) { - // SELinux 开关 - SwitchSettingItem( - icon = Icons.Filled.Security, - title = stringResource(R.string.selinux), - summary = if (selinuxEnabled) - stringResource(R.string.selinux_enabled) else - stringResource(R.string.selinux_disabled), - checked = selinuxEnabled, - onChange = { enabled -> - val command = if (enabled) "setenforce 1" else "setenforce 0" - Shell.getShell().newJob().add(command).exec().let { result -> - if (result.isSuccess) { - selinuxEnabled = enabled - // 显示成功提示 - val message = if (enabled) - context.getString(R.string.selinux_enabled_toast) - else - context.getString(R.string.selinux_disabled_toast) - - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } else { - // 显示失败提示 - Toast.makeText( - context, - context.getString(R.string.selinux_change_failed), - Toast.LENGTH_SHORT - ).show() - } - } - } - ) - - // SuSFS 开关(仅在支持时显示) - val suSFS = getSuSFS() - val isSUS_SU = getSuSFSFeatures() - if (suSFS == "Supported" && isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { - // 默认启用 - var isEnabled by rememberSaveable { - mutableStateOf(true) - } - - // 在启动时检查状态 - LaunchedEffect(Unit) { - // 如果当前模式不是2就强制启用 - val currentMode = susfsSUS_SU_Mode() - val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true) - if (currentMode != "2" && wasManuallyDisabled) { - susfsSUS_SU_2() // 强制切换到模式2 - prefs.edit { putBoolean("enable_sus_su", true) } - } - isEnabled = currentMode == "2" - } - - SwitchSettingItem( - icon = Icons.Filled.Security, - title = stringResource(id = R.string.settings_susfs_toggle), - summary = stringResource(id = R.string.settings_susfs_toggle_summary), - checked = isEnabled, - onChange = { - if (it) { - // 手动启用 - susfsSUS_SU_2() - prefs.edit { putBoolean("enable_sus_su", true) } - Toast.makeText( - context, - context.getString(R.string.susfs_enabled), - Toast.LENGTH_SHORT - ).show() - } else { - // 手动关闭 - susfsSUS_SU_0() - prefs.edit { putBoolean("enable_sus_su", false) } - Toast.makeText( - context, - context.getString(R.string.susfs_disabled), - Toast.LENGTH_SHORT - ).show() - } - isEnabled = it - } - ) - } - // 动态管理器设置 - if (Natives.version >= Natives.MINIMAL_SUPPORTED_DYNAMIC_MANAGER) { - SettingItem( - icon = Icons.Filled.Security, - title = stringResource(R.string.dynamic_manager_title), - subtitle = if (isDynamicSignEnabled) { - stringResource( - R.string.dynamic_manager_enabled_summary, - dynamicSignSize - ) - } else { - stringResource(R.string.dynamic_manager_disabled) - }, - onClick = { showDynamicSignDialog = true } - ) - } - } - } - } - } -} - - - -@Composable -fun SettingsCard( - title: String, - icon: ImageVector? = null, - content: @Composable () -> Unit -) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = SETTINGS_GROUP_SPACING), - colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh), - elevation = getCardElevation(), - shape = MaterialTheme.shapes.medium - ) { - Column(modifier = Modifier.padding(vertical = 8.dp)) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .padding(horizontal = 16.dp) - ) { - if (icon != null) { - Icon( - imageVector = icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - } - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - ) - } - content() - } - } -} - -@Composable -fun SettingItem( - icon: ImageVector, - title: String, - subtitle: String? = null, - onClick: () -> Unit, - iconTint: Color = MaterialTheme.colorScheme.primary, - trailingContent: @Composable (() -> Unit)? = { - Icon( - Icons.AutoMirrored.Filled.NavigateNext, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } -) { - Row( - modifier = Modifier - .fillMaxWidth() -// .height(if (subtitle != null) SETTINGS_ITEM_HEIGHT + 12.dp else SETTINGS_ITEM_HEIGHT) - .clickable(onClick = onClick) - .padding(horizontal = 16.dp, vertical = 5.dp), - verticalAlignment = Alignment.Top - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = iconTint, - modifier = Modifier - .padding(end = 16.dp) - .size(24.dp) - ) - - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center - ) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - maxLines = Int.MAX_VALUE, - overflow = TextOverflow.Visible - ) - if (subtitle != null) { - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = subtitle, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = Int.MAX_VALUE, - overflow = TextOverflow.Visible - ) - } - } - - trailingContent?.invoke() - } -} - -@Composable -fun SwitchSettingItem( - icon: ImageVector, - title: String, - summary: String? = null, - checked: Boolean, - onChange: (Boolean) -> Unit -) { - Row( - modifier = Modifier - .fillMaxWidth() -// .height(if (summary != null) SETTINGS_ITEM_HEIGHT + 12.dp else SETTINGS_ITEM_HEIGHT) - .clickable { onChange(!checked) } - .padding(horizontal = 16.dp, vertical = 10.dp), - verticalAlignment = Alignment.Top - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = if (checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .padding(end = 16.dp) - .size(24.dp) - ) - - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center - ) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - lineHeight = 20.sp, -// maxLines = 1, -// overflow = TextOverflow.Ellipsis - ) - if (summary != null) { - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = summary, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - lineHeight = 16.sp, -// maxLines = 2, -// overflow = TextOverflow.Ellipsis - ) - } - } - - Switch( - checked = checked, - onCheckedChange = onChange - ) - } -} - -@Composable -fun SettingsDivider() { - HorizontalDivider( - modifier = Modifier.padding(vertical = 8.dp) - ) -} - -@Composable -fun ColorCircle( - color: Color, - isSelected: Boolean, - modifier: Modifier = Modifier -) { - Box( - modifier = modifier - .size(20.dp) - .clip(CircleShape) - .background(color) - .then( - if (isSelected) { - Modifier.border( - width = 2.dp, - color = MaterialTheme.colorScheme.primary, - shape = CircleShape - ) - } else { - Modifier - } - ) - ) -} - -@Composable -fun SingleChoiceDialog( - title: String, - options: List, - selectedIndex: Int, - onOptionSelected: (Int) -> Unit, - onDismiss: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismiss, - title = { Text(title) }, - text = { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - options.forEachIndexed { index, option -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - onOptionSelected(index) - onDismiss() - } - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selectedIndex == index, - onClick = null - ) - Spacer(modifier = Modifier.width(8.dp)) - Text(option) - } - } - } - }, - confirmButton = { - TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel)) - } - } - ) -} - -@Composable -fun ConfirmDialog( - title: String, - message: String, - summaryText: String? = null, - confirmText: String = stringResource(R.string.confirm), - dismissText: String = stringResource(R.string.cancel), - onConfirm: () -> Unit, - onDismiss: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismiss, - title = { Text(title) }, - text = { - Column { - Text(message) - if (summaryText != null) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - summaryText, - style = MaterialTheme.typography.bodySmall - ) - } - } - }, - confirmButton = { - TextButton(onClick = onConfirm) { - Text(confirmText) - } - }, - dismissButton = { - TextButton(onClick = onDismiss) { - Text(dismissText) - } - } - ) -} - -@Composable -fun KeyValueChoiceDialog( - title: String, - options: List>, - selectedCode: String, - onOptionSelected: (String) -> Unit, - onDismiss: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismiss, - title = { Text(title) }, - text = { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - options.forEach { (code, name) -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - onOptionSelected(code) - onDismiss() - } - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selectedCode == code, - onClick = null - ) - Spacer(modifier = Modifier.width(8.dp)) - Text(name) - } - } - } - }, - confirmButton = { - TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel)) - } - } - ) -} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/KernelFlash.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/KernelFlash.kt similarity index 98% rename from manager/app/src/main/java/zako/zako/zako/zakoui/screen/KernelFlash.kt rename to manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/KernelFlash.kt index a15f5293..a87558f9 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/KernelFlash.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/KernelFlash.kt @@ -1,4 +1,4 @@ -package zako.zako.zako.zakoui.screen +package zako.zako.zako.zakoui.screen.kernelFlash import android.content.Context import android.net.Uri @@ -42,9 +42,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import zako.zako.zako.zakoui.flash.FlashState -import zako.zako.zako.zakoui.flash.HorizonKernelState -import zako.zako.zako.zakoui.flash.HorizonKernelWorker +import zako.zako.zako.zakoui.screen.kernelFlash.state.FlashState +import zako.zako.zako.zakoui.screen.kernelFlash.state.HorizonKernelState +import zako.zako.zako.zakoui.screen.kernelFlash.state.HorizonKernelWorker import java.io.File import java.text.SimpleDateFormat import java.util.* diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/component/SlotSelectionDialog.kt similarity index 99% rename from manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt rename to manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/component/SlotSelectionDialog.kt index 2f43c969..26da72c5 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/component/SlotSelectionDialog.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.component +package zako.zako.zako.zakoui.screen.kernelFlash.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/flash/KernelFlash.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/state/KernelFlashState.kt similarity index 99% rename from manager/app/src/main/java/zako/zako/zako/zakoui/flash/KernelFlash.kt rename to manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/state/KernelFlashState.kt index b577a6a1..db636bc7 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/flash/KernelFlash.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/kernelFlash/state/KernelFlashState.kt @@ -1,4 +1,4 @@ -package zako.zako.zako.zakoui.flash +package zako.zako.zako.zakoui.screen.kernelFlash.state import android.annotation.SuppressLint import android.app.Activity diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettings.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettings.kt new file mode 100644 index 00000000..8c9e1185 --- /dev/null +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettings.kt @@ -0,0 +1,716 @@ +package zako.zako.zako.zakoui.screen.moreSettings + +import android.annotation.SuppressLint +import android.content.Context +import android.net.Uri +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.* +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.sukisu.ultra.Natives +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.theme.component.ImageEditorDialog +import com.sukisu.ultra.ui.component.KsuIsValid +import com.sukisu.ultra.ui.theme.* +import com.sukisu.ultra.ui.util.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import zako.zako.zako.zakoui.screen.moreSettings.component.ColorCircle +import zako.zako.zako.zakoui.screen.moreSettings.component.MoreSettingsDialogs +import zako.zako.zako.zakoui.screen.moreSettings.component.SettingItem +import zako.zako.zako.zakoui.screen.moreSettings.component.SettingsCard +import zako.zako.zako.zakoui.screen.moreSettings.component.SettingsDivider +import zako.zako.zako.zakoui.screen.moreSettings.component.SwitchSettingItem +import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState +import kotlin.math.roundToInt + +@SuppressLint("LocalContextConfigurationRead", "LocalContextResourcesRead", "ObsoleteSdkInt") +@OptIn(ExperimentalMaterial3Api::class) +@Destination +@Composable +fun MoreSettingsScreen( + navigator: DestinationsNavigator +) { + // 顶部滚动行为 + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) } + val systemIsDark = isSystemInDarkTheme() + + // 创建设置状态管理器 + val settingsState = remember { MoreSettingsState(context, prefs, systemIsDark) } + val settingsHandlers = remember { MoreSettingsHandlers(context, prefs, settingsState) } + + // 图片选择器 + val pickImageLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.GetContent() + ) { uri: Uri? -> + uri?.let { + settingsState.selectedImageUri = it + settingsState.showImageEditor = true + } + } + + // 初始化设置 + LaunchedEffect(Unit) { + settingsHandlers.initializeSettings() + } + + // 显示图片编辑对话框 + if (settingsState.showImageEditor && settingsState.selectedImageUri != null) { + ImageEditorDialog( + imageUri = settingsState.selectedImageUri!!, + onDismiss = { + settingsState.showImageEditor = false + settingsState.selectedImageUri = null + }, + onConfirm = { transformedUri -> + settingsHandlers.handleCustomBackground(transformedUri) + settingsState.showImageEditor = false + settingsState.selectedImageUri = null + } + ) + } + + // 各种设置对话框 + MoreSettingsDialogs( + state = settingsState, + handlers = settingsHandlers + ) + + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(R.string.more_settings), + style = MaterialTheme.typography.titleLarge + ) + }, + navigationIcon = { + IconButton(onClick = { navigator.popBackStack() }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back) + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = CardConfig.cardAlpha), + scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = CardConfig.cardAlpha) + ), + windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), + scrollBehavior = scrollBehavior + ) + }, + contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp) + .padding(top = 8.dp) + ) { + // 外观设置 + AppearanceSettings( + state = settingsState, + handlers = settingsHandlers, + pickImageLauncher = pickImageLauncher, + coroutineScope = coroutineScope + ) + + // 自定义设置 + CustomizationSettings( + state = settingsState, + handlers = settingsHandlers + ) + + // 高级设置 + KsuIsValid { + AdvancedSettings( + state = settingsState, + handlers = settingsHandlers + ) + } + } + } +} + +@Composable +private fun AppearanceSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers, + pickImageLauncher: ActivityResultLauncher, + coroutineScope: CoroutineScope +) { + SettingsCard(title = stringResource(R.string.appearance_settings)) { + // 语言设置 + SettingItem( + icon = Icons.Default.Language, + title = stringResource(R.string.language_setting), + subtitle = state.supportedLanguages.find { it.first == state.currentLanguage }?.second + ?: stringResource(R.string.language_follow_system), + onClick = { state.showLanguageDialog = true } + ) + + // 主题模式 + SettingItem( + icon = Icons.Default.DarkMode, + title = stringResource(R.string.theme_mode), + subtitle = state.themeOptions[state.themeMode], + onClick = { state.showThemeModeDialog = true } + ) + + // 动态颜色开关 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + SwitchSettingItem( + icon = Icons.Filled.ColorLens, + title = stringResource(R.string.dynamic_color_title), + summary = stringResource(R.string.dynamic_color_summary), + checked = state.useDynamicColor, + onChange = handlers::handleDynamicColorChange + ) + } + + // 主题色选择 + AnimatedVisibility( + visible = Build.VERSION.SDK_INT < Build.VERSION_CODES.S || !state.useDynamicColor, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + ThemeColorSelection(state = state) + } + + SettingsDivider() + + // DPI 设置 + DpiSettings(state = state, handlers = handlers) + + SettingsDivider() + + // 自定义背景设置 + CustomBackgroundSettings( + state = state, + handlers = handlers, + pickImageLauncher = pickImageLauncher, + coroutineScope = coroutineScope + ) + } +} + +@Composable +private fun CustomizationSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + SettingsCard(title = stringResource(R.string.custom_settings)) { + // 图标切换 + SwitchSettingItem( + icon = Icons.Default.Android, + title = stringResource(R.string.icon_switch_title), + summary = stringResource(R.string.icon_switch_summary), + checked = state.useAltIcon, + onChange = handlers::handleIconChange + ) + + // 显示更多模块信息 + SwitchSettingItem( + icon = Icons.Filled.Info, + title = stringResource(R.string.show_more_module_info), + summary = stringResource(R.string.show_more_module_info_summary), + checked = state.showMoreModuleInfo, + onChange = handlers::handleShowMoreModuleInfoChange + ) + + // 简洁模式开关 + SwitchSettingItem( + icon = Icons.Filled.Brush, + title = stringResource(R.string.simple_mode), + summary = stringResource(R.string.simple_mode_summary), + checked = state.isSimpleMode, + onChange = handlers::handleSimpleModeChange + ) + + SwitchSettingItem( + icon = Icons.Filled.Brush, + title = stringResource(R.string.kernel_simple_kernel), + summary = stringResource(R.string.kernel_simple_kernel_summary), + checked = state.isKernelSimpleMode, + onChange = handlers::handleKernelSimpleModeChange + ) + + // 各种隐藏选项 + HideOptionsSettings(state = state, handlers = handlers) + } +} + +@Composable +private fun HideOptionsSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + // 隐藏内核版本号 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_kernel_kernelsu_version), + summary = stringResource(R.string.hide_kernel_kernelsu_version_summary), + checked = state.isHideVersion, + onChange = handlers::handleHideVersionChange + ) + + // 隐藏模块数量等信息 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_other_info), + summary = stringResource(R.string.hide_other_info_summary), + checked = state.isHideOtherInfo, + onChange = handlers::handleHideOtherInfoChange + ) + + // SuSFS 状态信息 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_susfs_status), + summary = stringResource(R.string.hide_susfs_status_summary), + checked = state.isHideSusfsStatus, + onChange = handlers::handleHideSusfsStatusChange + ) + + // Zygisk 实现状态信息 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_zygisk_implement), + summary = stringResource(R.string.hide_zygisk_implement_summary), + checked = state.isHideZygiskImplement, + onChange = handlers::handleHideZygiskImplementChange + ) + + if (Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) { + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.show_kpm_info), + summary = stringResource(R.string.show_kpm_info_summary), + checked = state.isShowKpmInfo, + onChange = handlers::handleShowKpmInfoChange + ) + } + + // 隐藏链接信息 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_link_card), + summary = stringResource(R.string.hide_link_card_summary), + checked = state.isHideLinkCard, + onChange = handlers::handleHideLinkCardChange + ) + + // 隐藏标签行 + SwitchSettingItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_tag_card), + summary = stringResource(R.string.hide_tag_card_summary), + checked = state.isHideTagRow, + onChange = handlers::handleHideTagRowChange + ) +} + +@Composable +private fun AdvancedSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + SettingsCard(title = stringResource(R.string.advanced_settings)) { + // SELinux 开关 + SwitchSettingItem( + icon = Icons.Filled.Security, + title = stringResource(R.string.selinux), + summary = if (state.selinuxEnabled) + stringResource(R.string.selinux_enabled) else + stringResource(R.string.selinux_disabled), + checked = state.selinuxEnabled, + onChange = handlers::handleSelinuxChange + ) + + // SuSFS 开关(仅在支持时显示) + SusFSSettings(state = state, handlers = handlers) + + // 动态管理器设置 + if (Natives.version >= Natives.MINIMAL_SUPPORTED_DYNAMIC_MANAGER) { + SettingItem( + icon = Icons.Filled.Security, + title = stringResource(R.string.dynamic_manager_title), + subtitle = if (state.isDynamicSignEnabled) { + stringResource(R.string.dynamic_manager_enabled_summary, state.dynamicSignSize) + } else { + stringResource(R.string.dynamic_manager_disabled) + }, + onClick = { state.showDynamicSignDialog = true } + ) + } + } +} + +@Composable +private fun SusFSSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + val suSFS = getSuSFS() + val isSUS_SU = getSuSFSFeatures() + + if (suSFS == "Supported" && isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { + SwitchSettingItem( + icon = Icons.Filled.Security, + title = stringResource(id = R.string.settings_susfs_toggle), + summary = stringResource(id = R.string.settings_susfs_toggle_summary), + checked = state.isSusFSEnabled, + onChange = handlers::handleSusFSChange + ) + } +} + +@Composable +private fun ThemeColorSelection(state: MoreSettingsState) { + SettingItem( + icon = Icons.Default.Palette, + title = stringResource(R.string.theme_color), + subtitle = when (ThemeConfig.currentTheme) { + is ThemeColors.Green -> stringResource(R.string.color_green) + is ThemeColors.Purple -> stringResource(R.string.color_purple) + is ThemeColors.Orange -> stringResource(R.string.color_orange) + is ThemeColors.Pink -> stringResource(R.string.color_pink) + is ThemeColors.Gray -> stringResource(R.string.color_gray) + is ThemeColors.Yellow -> stringResource(R.string.color_yellow) + else -> stringResource(R.string.color_default) + }, + onClick = { state.showThemeColorDialog = true }, + trailingContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(start = 8.dp) + ) { + val theme = ThemeConfig.currentTheme + val isDark = isSystemInDarkTheme() + + ColorCircle( + color = if (isDark) theme.primaryDark else theme.primaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + ColorCircle( + color = if (isDark) theme.secondaryDark else theme.secondaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + ColorCircle( + color = if (isDark) theme.tertiaryDark else theme.tertiaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + } + } + ) +} + +@Composable +private fun DpiSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + SettingItem( + icon = Icons.Default.FormatSize, + title = stringResource(R.string.app_dpi_title), + subtitle = stringResource(R.string.app_dpi_summary), + onClick = {}, + trailingContent = { + Text( + text = handlers.getDpiFriendlyName(state.tempDpi), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary + ) + } + ) + + // DPI 滑动条和控制 + DpiSliderControls(state = state, handlers = handlers) +} + +@Composable +private fun DpiSliderControls( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + val sliderValue by animateFloatAsState( + targetValue = state.tempDpi.toFloat(), + label = "DPI Slider Animation" + ) + + Slider( + value = sliderValue, + onValueChange = { newValue -> + state.tempDpi = newValue.toInt() + state.isDpiCustom = !state.dpiPresets.containsValue(state.tempDpi) + }, + valueRange = 160f..600f, + steps = 11, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) + + // DPI 预设按钮行 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) { + state.dpiPresets.forEach { (name, dpi) -> + val isSelected = state.tempDpi == dpi + val buttonColor = if (isSelected) + MaterialTheme.colorScheme.primaryContainer + else + MaterialTheme.colorScheme.surfaceVariant + + Box( + modifier = Modifier + .weight(1f) + .padding(horizontal = 2.dp) + .clip(RoundedCornerShape(8.dp)) + .background(buttonColor) + .clickable { + state.tempDpi = dpi + state.isDpiCustom = false + } + .padding(vertical = 8.dp, horizontal = 4.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = name, + style = MaterialTheme.typography.labelMedium, + color = if (isSelected) + MaterialTheme.colorScheme.onPrimaryContainer + else + MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + + Text( + text = if (state.isDpiCustom) + "${stringResource(R.string.dpi_size_custom)}: ${state.tempDpi}" + else + "${handlers.getDpiFriendlyName(state.tempDpi)}: ${state.tempDpi}", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(top = 8.dp) + ) + + Button( + onClick = { state.showDpiConfirmDialog = true }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + enabled = state.tempDpi != state.currentDpi + ) { + Icon( + Icons.Default.Check, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.dpi_apply_settings)) + } + } +} + +@Composable +private fun CustomBackgroundSettings( + state: MoreSettingsState, + handlers: MoreSettingsHandlers, + pickImageLauncher: ActivityResultLauncher, + coroutineScope: CoroutineScope +) { + // 自定义背景开关 + SwitchSettingItem( + icon = Icons.Filled.Wallpaper, + title = stringResource(id = R.string.settings_custom_background), + summary = stringResource(id = R.string.settings_custom_background_summary), + checked = state.isCustomBackgroundEnabled, + onChange = { isChecked -> + if (isChecked) { + pickImageLauncher.launch("image/*") + } else { + handlers.handleRemoveCustomBackground() + } + } + ) + + // 透明度和亮度调节 + AnimatedVisibility( + visible = ThemeConfig.customBackgroundUri != null, + enter = fadeIn() + slideInVertically(), + exit = fadeOut() + slideOutVertically() + ) { + BackgroundAdjustmentControls( + state = state, + handlers = handlers, + coroutineScope = coroutineScope + ) + } +} + +@Composable +private fun BackgroundAdjustmentControls( + state: MoreSettingsState, + handlers: MoreSettingsHandlers, + coroutineScope: CoroutineScope +) { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + // 透明度滑动条 + AlphaSlider(state = state, handlers = handlers, coroutineScope = coroutineScope) + + // 亮度调节滑动条 + DimSlider(state = state, handlers = handlers, coroutineScope = coroutineScope) + } +} + +@Composable +private fun AlphaSlider( + state: MoreSettingsState, + handlers: MoreSettingsHandlers, + coroutineScope: CoroutineScope +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 4.dp) + ) { + Icon( + Icons.Filled.Opacity, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.settings_card_alpha), + style = MaterialTheme.typography.titleSmall + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "${(state.cardAlpha * 100).roundToInt()}%", + style = MaterialTheme.typography.labelMedium, + ) + } + + val alphaSliderValue by animateFloatAsState( + targetValue = state.cardAlpha, + label = "Alpha Slider Animation" + ) + + Slider( + value = alphaSliderValue, + onValueChange = { newValue -> + handlers.handleCardAlphaChange(newValue) + }, + onValueChangeFinished = { + coroutineScope.launch(Dispatchers.IO) { + saveCardConfig(handlers.context) + } + }, + valueRange = 0f..1f, + steps = 20, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) +} + +@Composable +private fun DimSlider( + state: MoreSettingsState, + handlers: MoreSettingsHandlers, + coroutineScope: CoroutineScope +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(top = 16.dp, bottom = 4.dp) + ) { + Icon( + Icons.Filled.LightMode, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.settings_card_dim), + style = MaterialTheme.typography.titleSmall + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "${(state.cardDim * 100).roundToInt()}%", + style = MaterialTheme.typography.labelMedium, + ) + } + + val dimSliderValue by animateFloatAsState( + targetValue = state.cardDim, + label = "Dim Slider Animation" + ) + + Slider( + value = dimSliderValue, + onValueChange = { newValue -> + handlers.handleCardDimChange(newValue) + }, + onValueChangeFinished = { + coroutineScope.launch(Dispatchers.IO) { + saveCardConfig(handlers.context) + } + }, + valueRange = 0f..1f, + steps = 20, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) +} + +fun saveCardConfig(context: Context) { + CardConfig.save(context) +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt new file mode 100644 index 00000000..c0bf446e --- /dev/null +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt @@ -0,0 +1,509 @@ +package zako.zako.zako.zakoui.screen.moreSettings + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.res.Configuration +import android.net.Uri +import android.os.Build +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.content.edit +import com.sukisu.ultra.Natives +import com.sukisu.ultra.R +import com.sukisu.ultra.ksuApp +import com.sukisu.ultra.ui.theme.* +import com.sukisu.ultra.ui.util.* +import com.topjohnwu.superuser.Shell +import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState +import zako.zako.zako.zakoui.screen.moreSettings.util.toggleLauncherIcon +import java.util.* + +/** + * 更多设置处理器 + */ +class MoreSettingsHandlers( + val context: Context, + private val prefs: SharedPreferences, + private val state: MoreSettingsState +) { + + /** + * 初始化设置 + */ + fun initializeSettings() { + // 加载设置 + CardConfig.load(context) + state.cardAlpha = CardConfig.cardAlpha + state.cardDim = CardConfig.cardDim + state.isCustomBackgroundEnabled = ThemeConfig.customBackgroundUri != null + + // 设置主题模式 + state.themeMode = when (ThemeConfig.forceDarkMode) { + true -> 2 + false -> 1 + null -> 0 + } + + // 确保卡片样式跟随主题模式 + when (state.themeMode) { + 2 -> { // 深色 + CardConfig.isUserDarkModeEnabled = true + CardConfig.isUserLightModeEnabled = false + } + 1 -> { // 浅色 + CardConfig.isUserDarkModeEnabled = false + CardConfig.isUserLightModeEnabled = true + } + 0 -> { // 跟随系统 + CardConfig.isUserDarkModeEnabled = false + CardConfig.isUserLightModeEnabled = false + } + } + + // 如果启用了系统跟随且系统是深色模式,应用深色模式默认值 + if (state.themeMode == 0 && state.systemIsDark) { + CardConfig.setThemeDefaults(true) + } + + state.currentDpi = prefs.getInt("app_dpi", state.systemDpi) + state.tempDpi = state.currentDpi + + CardConfig.save(context) + + // 初始化 SELinux 状态 + state.selinuxEnabled = Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing" + + // 初始化动态管理器配置 + state.dynamicSignConfig = Natives.getDynamicManager() + state.dynamicSignConfig?.let { config -> + if (config.isValid()) { + state.isDynamicSignEnabled = true + state.dynamicSignSize = config.size.toString() + state.dynamicSignHash = config.hash + } + } + + // 初始化 SuSFS 状态 + val currentMode = susfsSUS_SU_Mode() + val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true) + if (currentMode != "2" && wasManuallyDisabled) { + susfsSUS_SU_2() + prefs.edit { putBoolean("enable_sus_su", true) } + } + state.isSusFSEnabled = currentMode == "2" + } + + /** + * 处理主题模式变更 + */ + fun handleThemeModeChange(index: Int) { + state.themeMode = index + val newThemeMode = when (index) { + 0 -> null // 跟随系统 + 1 -> false // 浅色 + 2 -> true // 深色 + else -> null + } + context.saveThemeMode(newThemeMode) + + when (index) { + 2 -> { // 深色 + ThemeConfig.forceDarkMode = true + CardConfig.isUserDarkModeEnabled = true + CardConfig.isUserLightModeEnabled = false + CardConfig.setThemeDefaults(true) + CardConfig.save(context) + } + 1 -> { // 浅色 + ThemeConfig.forceDarkMode = false + CardConfig.isUserLightModeEnabled = true + CardConfig.isUserDarkModeEnabled = false + CardConfig.setThemeDefaults(false) + CardConfig.save(context) + } + 0 -> { // 跟随系统 + ThemeConfig.forceDarkMode = null + CardConfig.isUserLightModeEnabled = false + CardConfig.isUserDarkModeEnabled = false + val isNightModeActive = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + CardConfig.setThemeDefaults(isNightModeActive) + CardConfig.save(context) + } + } + } + + /** + * 处理语言设置变更 + */ + @SuppressLint("ObsoleteSdkInt") + fun handleLanguageChange(code: String) { + if (state.currentLanguage != code) { + prefs.edit { + putString("app_language", code) + commit() + } + + state.currentLanguage = code + + Toast.makeText( + context, + context.getString(R.string.language_changed), + Toast.LENGTH_SHORT + ).show() + + val locale = if (code.isEmpty()) Locale.getDefault() else Locale.forLanguageTag(code) + Locale.setDefault(locale) + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.createConfigurationContext(config) + } else { + @Suppress("DEPRECATION") + context.resources.updateConfiguration(config, context.resources.displayMetrics) + } + ksuApp.refreshCurrentActivity() + } + } + + /** + * 处理主题色变更 + */ + fun handleThemeColorChange(theme: ThemeColors) { + context.saveThemeColors(when (theme) { + ThemeColors.Green -> "green" + ThemeColors.Purple -> "purple" + ThemeColors.Orange -> "orange" + ThemeColors.Pink -> "pink" + ThemeColors.Gray -> "gray" + ThemeColors.Yellow -> "yellow" + else -> "default" + }) + } + + /** + * 处理动态颜色变更 + */ + fun handleDynamicColorChange(enabled: Boolean) { + state.useDynamicColor = enabled + context.saveDynamicColorState(enabled) + } + + /** + * 获取DPI大小友好名称 + */ + @Composable + fun getDpiFriendlyName(dpi: Int): String { + return when (dpi) { + 240 -> stringResource(R.string.dpi_size_small) + 320 -> stringResource(R.string.dpi_size_medium) + 420 -> stringResource(R.string.dpi_size_large) + 560 -> stringResource(R.string.dpi_size_extra_large) + else -> stringResource(R.string.dpi_size_custom) + } + } + + /** + * 应用 DPI 设置 + */ + fun handleDpiApply() { + if (state.tempDpi != state.currentDpi) { + prefs.edit { + putInt("app_dpi", state.tempDpi) + } + + state.currentDpi = state.tempDpi + Toast.makeText( + context, + context.getString(R.string.dpi_applied_success, state.tempDpi), + Toast.LENGTH_SHORT + ).show() + + val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) + restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(restartIntent) + + state.showDpiConfirmDialog = false + } + } + + /** + * 处理自定义背景 + */ + fun handleCustomBackground(transformedUri: Uri) { + context.saveAndApplyCustomBackground(transformedUri) + state.isCustomBackgroundEnabled = true + CardConfig.cardElevation = 0.dp + CardConfig.isCustomBackgroundEnabled = true + saveCardConfig(context) + + Toast.makeText( + context, + context.getString(R.string.background_set_success), + Toast.LENGTH_SHORT + ).show() + } + + /** + * 处理移除自定义背景 + */ + fun handleRemoveCustomBackground() { + context.saveCustomBackground(null) + state.isCustomBackgroundEnabled = false + CardConfig.cardAlpha = 1f + CardConfig.cardDim = 0f + CardConfig.isCustomAlphaSet = false + CardConfig.isCustomDimSet = false + CardConfig.isCustomBackgroundEnabled = false + saveCardConfig(context) + + ThemeConfig.needsResetOnThemeChange = true + ThemeConfig.preventBackgroundRefresh = false + + context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit { + putBoolean("prevent_background_refresh", false) + } + + Toast.makeText( + context, + context.getString(R.string.background_removed), + Toast.LENGTH_SHORT + ).show() + } + + /** + * 处理卡片透明度变更 + */ + fun handleCardAlphaChange(newValue: Float) { + state.cardAlpha = newValue + CardConfig.cardAlpha = newValue + CardConfig.isCustomAlphaSet = true + prefs.edit { + putBoolean("is_custom_alpha_set", true) + putFloat("card_alpha", newValue) + } + } + + /** + * 处理卡片亮度变更 + */ + fun handleCardDimChange(newValue: Float) { + state.cardDim = newValue + CardConfig.cardDim = newValue + CardConfig.isCustomDimSet = true + prefs.edit { + putBoolean("is_custom_dim_set", true) + putFloat("card_dim", newValue) + } + } + + /** + * 处理图标变更 + */ + fun handleIconChange(newValue: Boolean) { + prefs.edit { putBoolean("use_alt_icon", newValue) } + state.useAltIcon = newValue + toggleLauncherIcon(context, newValue) + Toast.makeText(context, context.getString(R.string.icon_switched), Toast.LENGTH_SHORT).show() + } + + /** + * 处理简洁模式变更 + */ + fun handleSimpleModeChange(newValue: Boolean) { + prefs.edit { putBoolean("is_simple_mode", newValue) } + state.isSimpleMode = newValue + } + + /** + * 处理内核简洁模式变更 + */ + fun handleKernelSimpleModeChange(newValue: Boolean) { + prefs.edit { putBoolean("is_kernel_simple_mode", newValue) } + state.isKernelSimpleMode = newValue + } + + /** + * 处理隐藏版本变更 + */ + fun handleHideVersionChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_version", newValue) } + state.isHideVersion = newValue + } + + /** + * 处理隐藏其他信息变更 + */ + fun handleHideOtherInfoChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_other_info", newValue) } + state.isHideOtherInfo = newValue + } + + /** + * 处理显示KPM信息变更 + */ + fun handleShowKpmInfoChange(newValue: Boolean) { + prefs.edit { putBoolean("show_kpm_info", newValue) } + state.isShowKpmInfo = newValue + } + + /** + * 处理隐藏SuSFS状态变更 + */ + fun handleHideSusfsStatusChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_susfs_status", newValue) } + state.isHideSusfsStatus = newValue + } + + /** + * 处理隐藏Zygisk实现变更 + */ + fun handleHideZygiskImplementChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_zygisk_Implement", newValue) } + state.isHideZygiskImplement = newValue + } + + /** + * 处理隐藏链接卡片变更 + */ + fun handleHideLinkCardChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_link_card", newValue) } + state.isHideLinkCard = newValue + } + + /** + * 处理隐藏标签行变更 + */ + fun handleHideTagRowChange(newValue: Boolean) { + prefs.edit { putBoolean("is_hide_tag_row", newValue) } + state.isHideTagRow = newValue + } + + /** + * 处理显示更多模块信息变更 + */ + fun handleShowMoreModuleInfoChange(newValue: Boolean) { + prefs.edit { putBoolean("show_more_module_info", newValue) } + state.showMoreModuleInfo = newValue + } + + /** + * 处理SELinux变更 + */ + fun handleSelinuxChange(enabled: Boolean) { + val command = if (enabled) "setenforce 1" else "setenforce 0" + Shell.getShell().newJob().add(command).exec().let { result -> + if (result.isSuccess) { + state.selinuxEnabled = enabled + val message = if (enabled) + context.getString(R.string.selinux_enabled_toast) + else + context.getString(R.string.selinux_disabled_toast) + + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } else { + Toast.makeText( + context, + context.getString(R.string.selinux_change_failed), + Toast.LENGTH_SHORT + ).show() + } + } + } + + /** + * 处理SuSFS变更 + */ + fun handleSusFSChange(enabled: Boolean) { + if (enabled) { + susfsSUS_SU_2() + prefs.edit { putBoolean("enable_sus_su", true) } + Toast.makeText( + context, + context.getString(R.string.susfs_enabled), + Toast.LENGTH_SHORT + ).show() + } else { + susfsSUS_SU_0() + prefs.edit { putBoolean("enable_sus_su", false) } + Toast.makeText( + context, + context.getString(R.string.susfs_disabled), + Toast.LENGTH_SHORT + ).show() + } + state.isSusFSEnabled = enabled + } + + /** + * 处理动态管理器配置 + */ + fun handleDynamicManagerConfig(enabled: Boolean, size: String, hash: String) { + if (enabled) { + val parsedSize = parseDynamicSignSize(size) + if (parsedSize != null && parsedSize > 0 && hash.length == 64) { + val success = Natives.setDynamicManager(parsedSize, hash) + if (success) { + state.dynamicSignConfig = Natives.DynamicManagerConfig(parsedSize, hash) + state.isDynamicSignEnabled = true + state.dynamicSignSize = size + state.dynamicSignHash = hash + Toast.makeText( + context, + context.getString(R.string.dynamic_manager_set_success), + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + context, + context.getString(R.string.dynamic_manager_set_failed), + Toast.LENGTH_SHORT + ).show() + } + } else { + Toast.makeText( + context, + context.getString(R.string.invalid_sign_config), + Toast.LENGTH_SHORT + ).show() + } + } else { + val success = Natives.clearDynamicManager() + if (success) { + state.dynamicSignConfig = null + state.isDynamicSignEnabled = false + state.dynamicSignSize = "" + state.dynamicSignHash = "" + Toast.makeText( + context, + context.getString(R.string.dynamic_manager_disabled_success), + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + context, + context.getString(R.string.dynamic_manager_clear_failed), + Toast.LENGTH_SHORT + ).show() + } + } + } + + /** + * 解析动态签名大小 + */ + private fun parseDynamicSignSize(input: String): Int? { + return try { + when { + input.startsWith("0x", true) -> input.substring(2).toInt(16) + else -> input.toInt() + } + } catch (_: NumberFormatException) { + null + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsComponents.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsComponents.kt new file mode 100644 index 00000000..3c182c1f --- /dev/null +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsComponents.kt @@ -0,0 +1,201 @@ +package zako.zako.zako.zakoui.screen.moreSettings.component + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.NavigateNext +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sukisu.ultra.ui.theme.* + +private val SETTINGS_GROUP_SPACING = 16.dp + +@Composable +fun SettingsCard( + title: String, + icon: ImageVector? = null, + content: @Composable () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = SETTINGS_GROUP_SPACING), + colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh), + elevation = getCardElevation(), + shape = MaterialTheme.shapes.medium + ) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .padding(horizontal = 16.dp) + ) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + } + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + ) + } + content() + } + } +} + +@Composable +fun SettingItem( + icon: ImageVector, + title: String, + subtitle: String? = null, + onClick: () -> Unit, + iconTint: Color = MaterialTheme.colorScheme.primary, + trailingContent: @Composable (() -> Unit)? = { + Icon( + Icons.AutoMirrored.Filled.NavigateNext, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 5.dp), + verticalAlignment = Alignment.Top + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = iconTint, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) + ) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + maxLines = Int.MAX_VALUE, + overflow = TextOverflow.Visible + ) + if (subtitle != null) { + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = Int.MAX_VALUE, + overflow = TextOverflow.Visible + ) + } + } + + trailingContent?.invoke() + } +} + +@Composable +fun SwitchSettingItem( + icon: ImageVector, + title: String, + summary: String? = null, + checked: Boolean, + onChange: (Boolean) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onChange(!checked) } + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.Top + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = if (checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) + ) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + lineHeight = 20.sp, + ) + if (summary != null) { + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 16.sp, + ) + } + } + + Switch( + checked = checked, + onCheckedChange = onChange + ) + } +} + +@Composable +fun SettingsDivider() { + HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp) + ) +} + +@Composable +fun ColorCircle( + color: Color, + isSelected: Boolean, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .size(20.dp) + .clip(CircleShape) + .background(color) + .then( + if (isSelected) { + Modifier.border( + width = 2.dp, + color = MaterialTheme.colorScheme.primary, + shape = CircleShape + ) + } else { + Modifier + } + ) + ) +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsDialogs.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsDialogs.kt new file mode 100644 index 00000000..a3596940 --- /dev/null +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/component/MoreSettingsDialogs.kt @@ -0,0 +1,394 @@ +package zako.zako.zako.zakoui.screen.moreSettings.component + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.theme.* +import zako.zako.zako.zakoui.screen.moreSettings.MoreSettingsHandlers +import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState + +@Composable +fun MoreSettingsDialogs( + state: MoreSettingsState, + handlers: MoreSettingsHandlers +) { + // 主题模式选择对话框 + if (state.showThemeModeDialog) { + SingleChoiceDialog( + title = stringResource(R.string.theme_mode), + options = state.themeOptions, + selectedIndex = state.themeMode, + onOptionSelected = { index -> + handlers.handleThemeModeChange(index) + }, + onDismiss = { state.showThemeModeDialog = false } + ) + } + + // 语言切换对话框 + if (state.showLanguageDialog) { + KeyValueChoiceDialog( + title = stringResource(R.string.language_setting), + options = state.supportedLanguages, + selectedCode = state.currentLanguage, + onOptionSelected = { code -> + handlers.handleLanguageChange(code) + }, + onDismiss = { state.showLanguageDialog = false } + ) + } + + // DPI 设置确认对话框 + if (state.showDpiConfirmDialog) { + ConfirmDialog( + title = stringResource(R.string.dpi_confirm_title), + message = stringResource(R.string.dpi_confirm_message, state.currentDpi, state.tempDpi), + summaryText = stringResource(R.string.dpi_confirm_summary), + confirmText = stringResource(R.string.confirm), + dismissText = stringResource(R.string.cancel), + onConfirm = { handlers.handleDpiApply() }, + onDismiss = { + state.showDpiConfirmDialog = false + state.tempDpi = state.currentDpi + } + ) + } + + // 主题色选择对话框 + if (state.showThemeColorDialog) { + ThemeColorDialog( + onColorSelected = { theme -> + handlers.handleThemeColorChange(theme) + state.showThemeColorDialog = false + }, + onDismiss = { state.showThemeColorDialog = false } + ) + } + + // 动态管理器配置对话框 + if (state.showDynamicSignDialog) { + DynamicManagerDialog( + state = state, + onConfirm = { enabled, size, hash -> + handlers.handleDynamicManagerConfig(enabled, size, hash) + state.showDynamicSignDialog = false + }, + onDismiss = { state.showDynamicSignDialog = false } + ) + } +} + +@Composable +fun SingleChoiceDialog( + title: String, + options: List, + selectedIndex: Int, + onOptionSelected: (Int) -> Unit, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + options.forEachIndexed { index, option -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onOptionSelected(index) + onDismiss() + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedIndex == index, + onClick = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(option) + } + } + } + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.cancel)) + } + } + ) +} + +@Composable +fun ConfirmDialog( + title: String, + message: String, + summaryText: String? = null, + confirmText: String = stringResource(R.string.confirm), + dismissText: String = stringResource(R.string.cancel), + onConfirm: () -> Unit, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { + Column { + Text(message) + if (summaryText != null) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + summaryText, + style = MaterialTheme.typography.bodySmall + ) + } + } + }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(confirmText) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(dismissText) + } + } + ) +} + +@Composable +fun KeyValueChoiceDialog( + title: String, + options: List>, + selectedCode: String, + onOptionSelected: (String) -> Unit, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + options.forEach { (code, name) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onOptionSelected(code) + onDismiss() + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedCode == code, + onClick = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(name) + } + } + } + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.cancel)) + } + } + ) +} + +@Composable +fun ThemeColorDialog( + onColorSelected: (ThemeColors) -> Unit, + onDismiss: () -> Unit +) { + val themeColorOptions = listOf( + stringResource(R.string.color_default) to ThemeColors.Default, + stringResource(R.string.color_green) to ThemeColors.Green, + stringResource(R.string.color_purple) to ThemeColors.Purple, + stringResource(R.string.color_orange) to ThemeColors.Orange, + stringResource(R.string.color_pink) to ThemeColors.Pink, + stringResource(R.string.color_gray) to ThemeColors.Gray, + stringResource(R.string.color_yellow) to ThemeColors.Yellow + ) + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.choose_theme_color)) }, + text = { + Column { + themeColorOptions.forEach { (name, theme) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onColorSelected(theme) } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val isDark = isSystemInDarkTheme() + Box( + modifier = Modifier.padding(end = 12.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + ColorCircle( + color = if (isDark) theme.primaryDark else theme.primaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + ColorCircle( + color = if (isDark) theme.secondaryDark else theme.secondaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + ColorCircle( + color = if (isDark) theme.tertiaryDark else theme.tertiaryLight, + isSelected = false, + modifier = Modifier.padding(horizontal = 2.dp) + ) + } + } + Text(name) + Spacer(modifier = Modifier.weight(1f)) + // 当前选中的主题显示选中标记 + if (ThemeConfig.currentTheme::class == theme::class) { + Icon( + Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + } + }, + confirmButton = { + Button( + onClick = onDismiss + ) { + Text(stringResource(R.string.cancel)) + } + } + ) +} + +@Composable +fun DynamicManagerDialog( + state: MoreSettingsState, + onConfirm: (Boolean, String, String) -> Unit, + onDismiss: () -> Unit +) { + var localEnabled by remember { mutableStateOf(state.isDynamicSignEnabled) } + var localSize by remember { mutableStateOf(state.dynamicSignSize) } + var localHash by remember { mutableStateOf(state.dynamicSignHash) } + + fun parseDynamicSignSize(input: String): Int? { + return try { + when { + input.startsWith("0x", true) -> input.substring(2).toInt(16) + else -> input.toInt() + } + } catch (_: NumberFormatException) { + null + } + } + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.dynamic_manager_title)) }, + text = { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + // 启用开关 + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { localEnabled = !localEnabled } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Switch( + checked = localEnabled, + onCheckedChange = { localEnabled = it } + ) + Spacer(modifier = Modifier.width(12.dp)) + Text(stringResource(R.string.enable_dynamic_manager)) + } + + Spacer(modifier = Modifier.height(16.dp)) + + // 签名大小输入 + OutlinedTextField( + value = localSize, + onValueChange = { input -> + val isValid = when { + input.isEmpty() -> true + input.matches(Regex("^\\d+$")) -> true + input.matches(Regex("^0[xX][0-9a-fA-F]*$")) -> true + else -> false + } + if (isValid) { + localSize = input + } + }, + label = { Text(stringResource(R.string.signature_size)) }, + enabled = localEnabled, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text + ) + ) + + Spacer(modifier = Modifier.height(12.dp)) + + // 签名哈希输入 + OutlinedTextField( + value = localHash, + onValueChange = { hash -> + if (hash.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) { + localHash = hash + } + }, + label = { Text(stringResource(R.string.signature_hash)) }, + enabled = localEnabled, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + supportingText = { + Text(stringResource(R.string.hash_must_be_64_chars)) + }, + isError = localEnabled && localHash.isNotEmpty() && localHash.length != 64 + ) + } + }, + confirmButton = { + Button( + onClick = { onConfirm(localEnabled, localSize, localHash) }, + enabled = if (localEnabled) { + parseDynamicSignSize(localSize)?.let { it > 0 } == true && + localHash.length == 64 + } else true + ) { + Text(stringResource(R.string.confirm)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.cancel)) + } + } + ) +} \ No newline at end of file diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/state/MoreSettingsState.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/state/MoreSettingsState.kt new file mode 100644 index 00000000..9f987f19 --- /dev/null +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/state/MoreSettingsState.kt @@ -0,0 +1,149 @@ +package zako.zako.zako.zakoui.screen.moreSettings.state + +import android.content.Context +import android.content.SharedPreferences +import android.content.res.Configuration +import android.net.Uri +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.sukisu.ultra.Natives +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.theme.CardConfig +import com.sukisu.ultra.ui.theme.ThemeConfig +import java.util.Locale + +/** + * 更多设置状态管理 + */ +@Stable +class MoreSettingsState( + val context: Context, + val prefs: SharedPreferences, + val systemIsDark: Boolean +) { + // 主题模式选择 + var themeMode by mutableIntStateOf( + when (ThemeConfig.forceDarkMode) { + true -> 2 // 深色 + false -> 1 // 浅色 + null -> 0 // 跟随系统 + } + ) + + // 动态颜色开关状态 + var useDynamicColor by mutableStateOf(ThemeConfig.useDynamicColor) + + // 对话框显示状态 + var showThemeModeDialog by mutableStateOf(false) + var showLanguageDialog by mutableStateOf(false) + var showThemeColorDialog by mutableStateOf(false) + var showDpiConfirmDialog by mutableStateOf(false) + var showImageEditor by mutableStateOf(false) + + // 动态管理器配置状态 + var dynamicSignConfig by mutableStateOf(null) + var isDynamicSignEnabled by mutableStateOf(false) + var dynamicSignSize by mutableStateOf("") + var dynamicSignHash by mutableStateOf("") + var showDynamicSignDialog by mutableStateOf(false) + + // 获取当前语言设置 + var currentLanguage by mutableStateOf(prefs.getString("app_language", "") ?: "") + + // 各种设置开关状态 + var isSimpleMode by mutableStateOf(prefs.getBoolean("is_simple_mode", false)) + var isHideVersion by mutableStateOf(prefs.getBoolean("is_hide_version", false)) + var isHideOtherInfo by mutableStateOf(prefs.getBoolean("is_hide_other_info", false)) + var isShowKpmInfo by mutableStateOf(prefs.getBoolean("show_kpm_info", false)) + var isHideZygiskImplement by mutableStateOf(prefs.getBoolean("is_hide_zygisk_Implement", false)) + var isHideSusfsStatus by mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false)) + var isHideLinkCard by mutableStateOf(prefs.getBoolean("is_hide_link_card", false)) + var isHideTagRow by mutableStateOf(prefs.getBoolean("is_hide_tag_row", false)) + var isKernelSimpleMode by mutableStateOf(prefs.getBoolean("is_kernel_simple_mode", false)) + var showMoreModuleInfo by mutableStateOf(prefs.getBoolean("show_more_module_info", false)) + var useAltIcon by mutableStateOf(prefs.getBoolean("use_alt_icon", false)) + + // SELinux状态 + var selinuxEnabled by mutableStateOf(false) + + // SuSFS 状态 + var isSusFSEnabled by mutableStateOf(true) + + // 卡片配置状态 + var cardAlpha by mutableFloatStateOf(CardConfig.cardAlpha) + var cardDim by mutableFloatStateOf(CardConfig.cardDim) + var isCustomBackgroundEnabled by mutableStateOf(ThemeConfig.customBackgroundUri != null) + + // 图片选择状态 + var selectedImageUri by mutableStateOf(null) + + // DPI 设置 + val systemDpi = context.resources.displayMetrics.densityDpi + var currentDpi by mutableIntStateOf(prefs.getInt("app_dpi", systemDpi)) + var tempDpi by mutableIntStateOf(currentDpi) + var isDpiCustom by mutableStateOf(true) + + // 主题模式选项 + val themeOptions = listOf( + context.getString(R.string.theme_follow_system), + context.getString(R.string.theme_light), + context.getString(R.string.theme_dark) + ) + + // 预设 DPI 选项 + val dpiPresets = mapOf( + context.getString(R.string.dpi_size_small) to 240, + context.getString(R.string.dpi_size_medium) to 320, + context.getString(R.string.dpi_size_large) to 420, + context.getString(R.string.dpi_size_extra_large) to 560 + ) + + // 获取支持的语言列表 + val supportedLanguages by lazy { + val languages = mutableListOf>() + languages.add("" to context.getString(R.string.language_follow_system)) + val locales = context.resources.configuration.locales + for (i in 0 until locales.size()) { + val locale = locales.get(i) + val code = locale.toLanguageTag() + if (!languages.any { it.first == code }) { + languages.add(code to locale.getDisplayName(locale)) + } + } + + val commonLocales = listOf( + Locale.forLanguageTag("en"), // 英语 + Locale.forLanguageTag("zh-CN"), // 简体中文 + Locale.forLanguageTag("zh-HK"), // 繁体中文(香港) + Locale.forLanguageTag("zh-TW"), // 繁体中文(台湾) + Locale.forLanguageTag("ja"), // 日语 + Locale.forLanguageTag("fr"), // 法语 + Locale.forLanguageTag("de"), // 德语 + Locale.forLanguageTag("es"), // 西班牙语 + Locale.forLanguageTag("it"), // 意大利语 + Locale.forLanguageTag("ru"), // 俄语 + Locale.forLanguageTag("pt"), // 葡萄牙语 + Locale.forLanguageTag("ko"), // 韩语 + Locale.forLanguageTag("vi") // 越南语 + ) + + for (locale in commonLocales) { + val code = locale.toLanguageTag() + if (!languages.any { it.first == code }) { + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + try { + val testContext = context.createConfigurationContext(config) + testContext.getString(R.string.language_follow_system) + languages.add(code to locale.getDisplayName(locale)) + } catch (_: Exception) { + } + } + } + languages + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/RestartActivityUtils.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/util/RestartActivityUtils.kt similarity index 96% rename from manager/app/src/main/java/com/sukisu/ultra/ui/util/RestartActivityUtils.kt rename to manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/util/RestartActivityUtils.kt index ff8d881a..daeb2427 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/RestartActivityUtils.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/util/RestartActivityUtils.kt @@ -1,4 +1,4 @@ -package com.sukisu.ultra.ui.util +package zako.zako.zako.zakoui.screen.moreSettings.util import android.app.Activity import android.content.ComponentName