From 3526e84e048c366f61a0abeb1287fbee673d32ae Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:52:56 +0800 Subject: [PATCH] Refactor the UI to rewrite the interface (#61) --- .../java/com/sukisu/ultra/ui/MainActivity.kt | 227 +++- .../ultra/ui/component/SlotSelectionDialog.kt | 3 +- .../sukisu/ultra/ui/component/SwitchItem.kt | 85 +- .../java/com/sukisu/ultra/ui/screen/Home.kt | 503 ++++++-- .../com/sukisu/ultra/ui/screen/Install.kt | 3 +- .../java/com/sukisu/ultra/ui/screen/Kpm.kt | 287 ++++- .../java/com/sukisu/ultra/ui/screen/Module.kt | 221 ++-- .../sukisu/ultra/ui/screen/MoreSettings.kt | 1128 ++++++++++------- .../com/sukisu/ultra/ui/screen/Settings.kt | 700 ++++++---- .../com/sukisu/ultra/ui/screen/SuperUser.kt | 390 ++++-- .../com/sukisu/ultra/ui/screen/Template.kt | 12 +- .../com/sukisu/ultra/ui/theme/CardManage.kt | 88 +- .../java/com/sukisu/ultra/ui/theme/Color.kt | 310 +++-- .../java/com/sukisu/ultra/ui/theme/Theme.kt | 518 +++++--- .../java/com/sukisu/ultra/ui/theme/Type.kt | 103 +- .../ultra/ui/viewmodel/SuperUserViewModel.kt | 4 +- .../src/main/res/values-zh-rCN/strings.xml | 21 + manager/app/src/main/res/values/strings.xml | 21 + 18 files changed, 3135 insertions(+), 1489 deletions(-) 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 142f4cbe..09d00965 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,19 +1,23 @@ package com.sukisu.ultra.ui +import android.database.ContentObserver import android.os.Build import android.os.Bundle +import android.os.Handler 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.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController @@ -30,23 +34,52 @@ import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ui.screen.BottomBarDestination import com.sukisu.ultra.ui.theme.* import com.sukisu.ultra.ui.util.* + class MainActivity : ComponentActivity() { + private inner class ThemeChangeContentObserver( + handler: Handler, + private val onThemeChanged: () -> Unit + ) : ContentObserver(handler) { + override fun onChange(selfChange: Boolean) { + super.onChange(selfChange) + onThemeChanged() + } + } override fun onCreate(savedInstanceState: Bundle?) { - - // Enable edge to edge + // 启用边缘到边缘显示 enableEdgeToEdge() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } super.onCreate(savedInstanceState) - // 加载保存的背景设置 + // 加载保存的主题设置 loadCustomBackground() loadThemeMode() + loadThemeColors() + loadDynamicColorState() CardConfig.load(applicationContext) + val contentObserver = ThemeChangeContentObserver(Handler(mainLooper)) { + runOnUiThread { + ThemeConfig.backgroundImageLoaded = false + loadCustomBackground() + } + } + + contentResolver.registerContentObserver( + android.provider.Settings.System.getUriFor("ui_night_mode"), + false, + contentObserver + ) + + val destroyListeners = mutableListOf<() -> Unit>() + destroyListeners.add { + contentResolver.unregisterContentObserver(contentObserver) + } val isManager = Natives.becomeManager(ksuApp.packageName) if (isManager) { @@ -58,22 +91,37 @@ class MainActivity : ComponentActivity() { KernelSUTheme { val navController = rememberNavController() val snackBarHostState = remember { SnackbarHostState() } + Scaffold( bottomBar = { BottomBar(navController) }, - contentWindowInsets = WindowInsets(0, 0, 0, 0) + contentWindowInsets = WindowInsets(0, 0, 0, 0), + snackbarHost = { SnackbarHost(snackBarHostState) } ) { innerPadding -> CompositionLocalProvider( - LocalSnackbarHost provides snackBarHostState, + LocalSnackbarHost provides snackBarHostState ) { DestinationsNavHost( modifier = Modifier.padding(innerPadding), navGraph = NavGraphs.root as NavHostGraphSpec, navController = navController, - defaultTransitions = object : NavHostAnimatedDestinationStyle() { - override val enterTransition: AnimatedContentTransitionScope.() -> EnterTransition - get() = { fadeIn(animationSpec = tween(340)) } - override val exitTransition: AnimatedContentTransitionScope.() -> ExitTransition - get() = { fadeOut(animationSpec = tween(340)) } + defaultTransitions = remember { + object : NavHostAnimatedDestinationStyle() { + override val enterTransition: AnimatedContentTransitionScope.() -> EnterTransition = { + fadeIn(animationSpec = tween(300)) + + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Up, + animationSpec = tween(300) + ) + } + + override val exitTransition: AnimatedContentTransitionScope.() -> ExitTransition = { + fadeOut(animationSpec = tween(300)) + + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Down, + animationSpec = tween(300) + ) + } + } } ) } @@ -81,6 +129,13 @@ class MainActivity : ComponentActivity() { } } } + + private val destroyListeners = mutableListOf<() -> Unit>() + + override fun onDestroy() { + destroyListeners.forEach { it() } + super.onDestroy() + } } @Composable @@ -90,79 +145,113 @@ private fun BottomBar(navController: NavHostController) { val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() val kpmVersion = getKpmVersion() - // 获取卡片颜色和透明度 - val cardColor = MaterialTheme.colorScheme.secondaryContainer - val cardAlpha = CardConfig.cardAlpha - val cardElevation = CardConfig.cardElevation + val containerColor = MaterialTheme.colorScheme.surfaceContainer + val selectedColor = MaterialTheme.colorScheme.primary + val unselectedColor = MaterialTheme.colorScheme.onSurfaceVariant + val cornerRadius = 18.dp - NavigationBar( - tonalElevation = cardElevation, // 动态设置阴影 - containerColor = cardColor.copy(alpha = cardAlpha), - windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only( - WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom - ) + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + .clip(RoundedCornerShape(cornerRadius)), + color = containerColor.copy(alpha = 0.95f), + tonalElevation = 0.dp ) { - BottomBarDestination.entries.forEach { destination -> - if (destination == BottomBarDestination.Kpm) { - if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { + NavigationBar( + modifier = Modifier.windowInsetsPadding( + WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) + ), + containerColor = Color.Transparent, + tonalElevation = 0.dp + ) { + BottomBarDestination.entries.forEach { destination -> + if (destination == BottomBarDestination.Kpm) { + if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { + if (!fullFeatured && destination.rootRequired) return@forEach + val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) + NavigationBarItem( + selected = isCurrentDestOnBackStack, + onClick = { + if (!isCurrentDestOnBackStack) { + navigator.navigate(destination.direction) { + popUpTo(NavGraphs.root as RouteOrDirection) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + }, + icon = { + Icon( + imageVector = if (isCurrentDestOnBackStack) { + destination.iconSelected + } else { + destination.iconNotSelected + }, + contentDescription = stringResource(destination.label), + tint = if (isCurrentDestOnBackStack) selectedColor else unselectedColor + ) + }, + label = { + Text( + text = stringResource(destination.label), + style = MaterialTheme.typography.labelMedium + ) + }, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = selectedColor, + unselectedIconColor = unselectedColor, + selectedTextColor = selectedColor, + unselectedTextColor = unselectedColor, + indicatorColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) + } + } else { if (!fullFeatured && destination.rootRequired) return@forEach val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) NavigationBarItem( selected = isCurrentDestOnBackStack, onClick = { - if (isCurrentDestOnBackStack) { - navigator.popBackStack(destination.direction, false) - } - navigator.navigate(destination.direction) { - popUpTo(NavGraphs.root as RouteOrDirection) { - saveState = true + if (!isCurrentDestOnBackStack) { + navigator.navigate(destination.direction) { + popUpTo(NavGraphs.root as RouteOrDirection) { + saveState = true + } + launchSingleTop = true + restoreState = true } - launchSingleTop = true - restoreState = true } }, icon = { - if (isCurrentDestOnBackStack) { - Icon(destination.iconSelected, stringResource(destination.label)) - } else { - Icon(destination.iconNotSelected, stringResource(destination.label)) - } + Icon( + imageVector = if (isCurrentDestOnBackStack) { + destination.iconSelected + } else { + destination.iconNotSelected + }, + contentDescription = stringResource(destination.label), + tint = if (isCurrentDestOnBackStack) selectedColor else unselectedColor + ) + }, + label = { + Text( + text = stringResource(destination.label), + style = MaterialTheme.typography.labelMedium + ) }, - label = { Text(stringResource(destination.label)) }, - alwaysShowLabel = false, colors = NavigationBarItemDefaults.colors( - unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant + selectedIconColor = selectedColor, + unselectedIconColor = unselectedColor, + selectedTextColor = selectedColor, + unselectedTextColor = unselectedColor, + indicatorColor = MaterialTheme.colorScheme.secondaryContainer ) ) } - } else { - if (!fullFeatured && destination.rootRequired) return@forEach - val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) - NavigationBarItem( - selected = isCurrentDestOnBackStack, - onClick = { - if (isCurrentDestOnBackStack) { - navigator.popBackStack(destination.direction, false) - } - navigator.navigate(destination.direction) { - popUpTo(NavGraphs.root as RouteOrDirection) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - icon = { - if (isCurrentDestOnBackStack) { - Icon(destination.iconSelected, stringResource(destination.label)) - } else { - Icon(destination.iconNotSelected, stringResource(destination.label)) - } - }, - label = { Text(stringResource(destination.label)) }, - alwaysShowLabel = false, - ) } } } -} \ No newline at end of file +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt index bc92f0b5..ff1afdb8 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SlotSelectionDialog.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.sukisu.ultra.R import com.sukisu.ultra.ui.theme.ThemeConfig -import com.sukisu.ultra.ui.theme.getCardElevation import androidx.compose.foundation.shape.CornerSize /** @@ -60,7 +59,7 @@ fun SlotSelectionDialog( colors = CardDefaults.cardColors( containerColor = backgroundColor ), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) ) { Column( modifier = Modifier diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SwitchItem.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SwitchItem.kt index c925e034..c95cbfd3 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SwitchItem.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SwitchItem.kt @@ -1,32 +1,101 @@ package com.sukisu.ultra.ui.component +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp @Composable fun SwitchItem( icon: ImageVector, title: String, - summary: String, + summary: String? = null, checked: Boolean, - modifier: Modifier = Modifier, + enabled: Boolean = true, onCheckedChange: (Boolean) -> Unit ) { + // 颜色动画 + val iconTint by animateColorAsState( + targetValue = if (checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + animationSpec = tween(300), + label = "iconTint" + ) + + // 开关颜色 + val switchColors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.primary, + checkedTrackColor = MaterialTheme.colorScheme.primaryContainer, + checkedBorderColor = MaterialTheme.colorScheme.primary, + checkedIconColor = MaterialTheme.colorScheme.onPrimary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant, + uncheckedBorderColor = MaterialTheme.colorScheme.outline, + uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant, + disabledCheckedThumbColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), + disabledCheckedTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + disabledCheckedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + disabledCheckedIconColor = MaterialTheme.colorScheme.surfaceVariant, + disabledUncheckedThumbColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), + disabledUncheckedTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + disabledUncheckedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + disabledUncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant + ) + ListItem( - modifier = modifier, - leadingContent = { Icon(icon, contentDescription = null) }, - headlineContent = { Text(title) }, - supportingContent = { Text(summary) }, + headlineContent = { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + supportingContent = summary?.let { + { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + }, + leadingContent = { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = iconTint + ) + }, trailingContent = { Switch( checked = checked, - onCheckedChange = onCheckedChange + onCheckedChange = null, + enabled = enabled, + colors = switchColors ) - } + }, + modifier = Modifier + .fillMaxWidth() + .clickable(enabled = enabled) { + onCheckedChange(!checked) + } + .padding(vertical = 4.dp) ) } \ No newline at end of file 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 9d6011cc..0a9e1eee 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 @@ -2,12 +2,15 @@ package com.sukisu.ultra.ui.screen import android.annotation.SuppressLint import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Build import android.os.PowerManager import android.system.Os import android.util.Log import androidx.annotation.StringRes import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -19,10 +22,13 @@ 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.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.pm.PackageInfoCompat @@ -41,7 +47,6 @@ import com.sukisu.ultra.ui.util.module.LatestVersionInfo import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import com.sukisu.ultra.ui.theme.getCardColors -import com.sukisu.ultra.ui.theme.getCardElevation import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue @@ -71,24 +76,19 @@ fun HomeScreen(navigator: DestinationsNavigator) { LaunchedEffect(Unit) { isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_simple_mode", false) - } - // 从 SharedPreferences 加载隐藏 KernelSU 版本号开关状态 - LaunchedEffect(Unit) { + isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_hide_version", false) - } - // 从 SharedPreferences 加载隐藏模块数量等信息开关状态 - LaunchedEffect(Unit) { + isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_hide_other_info", false) - } - // 从 SharedPreferences 加载隐藏 SuSFS 状态开关状态 - LaunchedEffect(Unit) { + isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_hide_susfs_status", false) } + val kernelVersion = getKernelVersion() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val isManager = Natives.becomeManager(ksuApp.packageName) val deviceModel = getDeviceModel(context) @@ -126,7 +126,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { .padding(innerPadding) .nestedScroll(scrollBehavior.nestedScrollConnection) .verticalScroll(rememberScrollState()) - .padding(top = 12.dp) + .padding(top = 16.dp) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { @@ -143,6 +143,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { StatusCard(kernelVersion, ksuVersion, lkmMode) { navigator.navigate(InstallScreenDestination) } + if (isManager && Natives.requireNewKernel()) { WarningCard( stringResource(id = R.string.require_kernel_version).format( @@ -150,28 +151,39 @@ fun HomeScreen(navigator: DestinationsNavigator) { ) ) } + if (ksuVersion != null && !rootAvailable()) { WarningCard( stringResource(id = R.string.grant_root_failed) ) } + val checkUpdate = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("check_update", true) if (checkUpdate) { UpdateCard() } + val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) } var clickCount by rememberSaveable { mutableIntStateOf(prefs.getInt("click_count", 0)) } if (!isSimpleMode && clickCount < 3) { AnimatedVisibility( visible = clickCount < 3, + enter = fadeIn() + expandVertically(), exit = shrinkVertically() + fadeOut() ) { ElevatedCard( colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .clip(MaterialTheme.shapes.medium) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.medium, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) ) { Row( modifier = Modifier @@ -183,21 +195,31 @@ fun HomeScreen(navigator: DestinationsNavigator) { .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(end = 12.dp) + ) Text( text = stringResource(R.string.using_mksu_manager), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface ) } } } } + InfoCard() + if (!isSimpleMode) { ContributionCard() DonateCard() LearnMoreCard() } - Spacer(Modifier) + + Spacer(Modifier.height(16.dp)) } } } @@ -217,46 +239,53 @@ fun UpdateCard() { val newVersionUrl = newVersion.downloadUrl val changelog = newVersion.changelog - Log.d("UpdateCard", "Current version code: $currentVersionCode") - Log.d("UpdateCard", "New version code: $newVersionCode") - - - val uriHandler = LocalUriHandler.current val title = stringResource(id = R.string.module_changelog) val updateText = stringResource(id = R.string.module_update) AnimatedVisibility( visible = newVersionCode > currentVersionCode, - enter = fadeIn() + expandVertically(), + enter = fadeIn() + expandVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ), exit = shrinkVertically() + fadeOut() ) { val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) }) WarningCard( message = stringResource(id = R.string.new_version_available).format(newVersionCode), - MaterialTheme.colorScheme.outlineVariant - ) { - if (changelog.isEmpty()) { - uriHandler.openUri(newVersionUrl) - } else { - updateDialog.showConfirm( - title = title, - content = changelog, - markdown = true, - confirm = updateText - ) + color = MaterialTheme.colorScheme.tertiaryContainer, + onClick = { + if (changelog.isEmpty()) { + uriHandler.openUri(newVersionUrl) + } else { + updateDialog.showConfirm( + title = title, + content = changelog, + markdown = true, + confirm = updateText + ) + } } - } + ) } } @Composable fun RebootDropdownItem(@StringRes id: Int, reason: String = "") { - DropdownMenuItem(text = { - Text(stringResource(id)) - }, onClick = { - reboot(reason) - }) + DropdownMenuItem( + text = { Text(stringResource(id)) }, + onClick = { reboot(reason) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -267,35 +296,47 @@ private fun TopBar( onSettingsClick: () -> Unit, scrollBehavior: TopAppBarScrollBehavior? = null ) { - val cardColor = MaterialTheme.colorScheme.secondaryContainer + val cardColor = MaterialTheme.colorScheme.surfaceVariant val cardAlpha = CardConfig.cardAlpha TopAppBar( - title = { Text(stringResource(R.string.app_name)) }, + title = { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.titleLarge + ) + }, colors = TopAppBarDefaults.topAppBarColors( containerColor = cardColor.copy(alpha = cardAlpha), - scrolledContainerColor = cardColor.copy(alpha = cardAlpha) + scrolledContainerColor = cardColor.copy(alpha = 1f) ), actions = { if (kernelVersion.isGKI()) { IconButton(onClick = onInstallClick) { - Icon(Icons.Filled.Archive, stringResource(R.string.install)) + Icon( + Icons.Filled.Archive, + contentDescription = stringResource(R.string.install), + tint = MaterialTheme.colorScheme.primary + ) } } var showDropdown by remember { mutableStateOf(false) } if (Natives.isKsuValid(ksuApp.packageName)) { IconButton(onClick = { showDropdown = true }) { - Icon(Icons.Filled.Refresh, stringResource(R.string.reboot)) + Icon( + Icons.Filled.Refresh, + contentDescription = stringResource(R.string.reboot), + tint = MaterialTheme.colorScheme.primary + ) + DropdownMenu( expanded = showDropdown, onDismissRequest = { showDropdown = false } ) { - RebootDropdownItem(id = R.string.reboot) - val pm = - LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager? + val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager? @Suppress("DEPRECATION") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") @@ -307,13 +348,20 @@ private fun TopBar( } } } + + IconButton(onClick = onSettingsClick) { + Icon( + Icons.Filled.Settings, + contentDescription = stringResource(id = R.string.settings), + tint = MaterialTheme.colorScheme.primary + ) + } }, windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior ) } - @Composable private fun StatusCard( kernelVersion: KernelVersion, @@ -322,17 +370,28 @@ private fun StatusCard( onClickInstall: () -> Unit = {} ) { ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) - ) { - Row(modifier = Modifier + colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier .fillMaxWidth() - .clickable { - if (kernelVersion.isGKI()) { - onClickInstall() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(enabled = kernelVersion.isGKI()) { + if (kernelVersion.isGKI()) { + onClickInstall() + } } - } - .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + .padding(24.dp), + verticalAlignment = Alignment.CenterVertically + ) { when { ksuVersion != null -> { val safeMode = when { @@ -346,8 +405,7 @@ private fun StatusCard( else -> " " } - val workingText = - "${stringResource(id = R.string.home_working)}$workingMode$safeMode" + val workingText = "${stringResource(id = R.string.home_working)}$workingMode$safeMode" val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_hide_version", false) @@ -358,40 +416,55 @@ private fun StatusCard( val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) .getBoolean("is_hide_susfs_status", false) - Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working)) + Icon( + Icons.Outlined.CheckCircle, + contentDescription = stringResource(R.string.home_working), + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(40.dp) + ) + Column(Modifier.padding(start = 20.dp)) { Text( text = workingText, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface ) + if (!isHideVersion) { Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_working_version, ksuVersion), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } + if (!isHideOtherInfo) { Spacer(Modifier.height(4.dp)) Text( - text = stringResource( - R.string.home_superuser_count, getSuperuserCount() - ), style = MaterialTheme.typography.bodyMedium + text = stringResource(R.string.home_superuser_count, getSuperuserCount()), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_module_count, getModuleCount()), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + val kpmVersion = getKpmVersion() if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_kpm_module, getKpmModuleCount()), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } + if (!isHideSusfsStatus) { Spacer(modifier = Modifier.height(4.dp)) @@ -405,7 +478,8 @@ private fun StatusCard( Text( text = stringResource(R.string.home_susfs, translatedStatus), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -413,31 +487,49 @@ private fun StatusCard( } kernelVersion.isGKI() -> { - Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed)) + Icon( + Icons.Outlined.Warning, + contentDescription = stringResource(R.string.home_not_installed), + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(40.dp) + ) + Column(Modifier.padding(start = 20.dp)) { Text( text = stringResource(R.string.home_not_installed), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.error ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_click_to_install), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } else -> { - Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported)) + Icon( + Icons.Outlined.Block, + contentDescription = stringResource(R.string.home_unsupported), + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(40.dp) + ) + Column(Modifier.padding(start = 20.dp)) { Text( text = stringResource(R.string.home_unsupported), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.error ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_unsupported_reason), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -448,31 +540,62 @@ private fun StatusCard( @Composable fun WarningCard( - message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null + message: String, + color: Color = MaterialTheme.colorScheme.errorContainer, + onClick: (() -> Unit)? = null ) { ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + colors = getCardColors(color), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.error.copy(alpha = 0.1f) + ) ) { Row( modifier = Modifier .fillMaxWidth() .then(onClick?.let { Modifier.clickable { it() } } ?: Modifier) - .padding(24.dp) + .padding(24.dp), + verticalAlignment = Alignment.CenterVertically ) { + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer, + modifier = Modifier + .padding(end = 16.dp) + .size(28.dp) + ) Text( - text = message, style = MaterialTheme.typography.bodyMedium + text = message, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onErrorContainer ) } } } + @Composable fun ContributionCard() { val uriHandler = LocalUriHandler.current val links = listOf("https://github.com/zako", "https://github.com/udochina") + ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + colors = getCardColors(MaterialTheme.colorScheme.tertiaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f) + ) ) { Row( modifier = Modifier @@ -484,15 +607,27 @@ fun ContributionCard() { .padding(24.dp), verticalAlignment = Alignment.CenterVertically ) { + Icon( + imageVector = Icons.Filled.Code, + contentDescription = null, + tint = MaterialTheme.colorScheme.onTertiaryContainer, + modifier = Modifier + .padding(end = 16.dp) + .size(28.dp) + ) + Column { Text( text = stringResource(R.string.home_ContributionCard_kernelsu), - style = MaterialTheme.typography.titleSmall + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onTertiaryContainer ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_click_to_ContributionCard_kernelsu), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.8f) ) } } @@ -505,25 +640,47 @@ fun LearnMoreCard() { val url = stringResource(R.string.home_learn_kernelsu_url) ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) - ) { - - Row(modifier = Modifier + colors = getCardColors(MaterialTheme.colorScheme.primaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier .fillMaxWidth() - .clickable { - uriHandler.openUri(url) - } - .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + uriHandler.openUri(url) + } + .padding(24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.School, + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier + .padding(end = 16.dp) + .size(28.dp) + ) + Column { Text( text = stringResource(R.string.home_learn_kernelsu), - style = MaterialTheme.typography.titleSmall + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onPrimaryContainer ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_click_to_learn_kernelsu), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) ) } } @@ -536,24 +693,46 @@ fun DonateCard() { ElevatedCard( colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) - ) { - - Row(modifier = Modifier + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier .fillMaxWidth() - .clickable { - uriHandler.openUri("https://patreon.com/weishu") - } - .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.1f) + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + uriHandler.openUri("https://patreon.com/weishu") + } + .padding(24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.Favorite, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .padding(end = 16.dp) + .size(28.dp) + ) + Column { Text( text = stringResource(R.string.home_support_title), - style = MaterialTheme.typography.titleSmall + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSecondaryContainer ) + Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.home_support_content), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.8f) ) } } @@ -568,8 +747,16 @@ private fun InfoCard() { .getBoolean("is_simple_mode", false) ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.05f) + ) ) { Column( modifier = Modifier @@ -586,57 +773,90 @@ private fun InfoCard() { icon: ImageVector = Icons.Default.Info ) { contents.appendLine(label).appendLine(content).appendLine() - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 8.dp) + ) { Icon( imageVector = icon, contentDescription = label, - modifier = Modifier.size(24.dp) + modifier = Modifier.size(28.dp), + tint = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.width(16.dp)) Column { - Text(text = label, style = MaterialTheme.typography.bodyLarge) - Text(text = content, style = MaterialTheme.typography.bodyMedium) + Text( + text = label, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = content, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } } - InfoCardItem(stringResource(R.string.home_kernel), uname.release, icon = Icons.Default.Memory) + InfoCardItem( + stringResource(R.string.home_kernel), + uname.release, + icon = Icons.Default.Memory + ) if (!isSimpleMode) { - Spacer(Modifier.height(16.dp)) val androidVersion = Build.VERSION.RELEASE - InfoCardItem(stringResource(R.string.home_android_version), androidVersion, icon = Icons.Default.Android) + InfoCardItem( + stringResource(R.string.home_android_version), + androidVersion, + icon = Icons.Default.Android + ) } - Spacer(Modifier.height(16.dp)) val deviceModel = getDeviceModel(context) - InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid) + InfoCardItem( + stringResource(R.string.home_device_model), + deviceModel, + icon = Icons.Default.PhoneAndroid + ) - Spacer(Modifier.height(16.dp)) val managerVersion = getManagerVersion(context) - InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings) + InfoCardItem( + stringResource(R.string.home_manager_version), + "${managerVersion.first} (${managerVersion.second})", + icon = Icons.Default.Settings + ) - Spacer(Modifier.height(16.dp)) - InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security) + InfoCardItem( + stringResource(R.string.home_selinux_status), + getSELinuxStatus(), + icon = Icons.Default.Security + ) if (!isSimpleMode) { if (lkmMode != true) { val kpmVersion = getKpmVersion() - var displayVersion: String val isKpmConfigured = checkKpmConfigured() - if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { + val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { val statusText = if (isKpmConfigured) { stringResource(R.string.kernel_patched) } else { stringResource(R.string.kernel_not_enabled) } - displayVersion = "${stringResource(R.string.not_supported)} ($statusText)" + "${stringResource(R.string.not_supported)} ($statusText)" } else { - displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)" + "${stringResource(R.string.supported)} ($kpmVersion)" } - Spacer(Modifier.height(16.dp)) - InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code) + + InfoCardItem( + stringResource(R.string.home_kpm_version), + displayVersion, + icon = Icons.Default.Code + ) } } @@ -644,25 +864,28 @@ private fun InfoCard() { .getBoolean("is_hide_susfs_status", false) if ((!isSimpleMode) && (!isHideSusfsStatus)) { - Spacer(modifier = Modifier.height(16.dp)) - val suSFS = getSuSFS() if (suSFS == "Supported") { val suSFSVersion = getSuSFSVersion() - if (suSFSVersion.isEmpty()) return@withContext - val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU" - val infoText = buildString { - append(suSFSVersion) - append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})") - if (isSUS_SU) { - val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" } - if (susSUMode.isNotEmpty()) { - append(" ${stringResource(R.string.sus_su_mode)} $susSUMode") + if (suSFSVersion.isNotEmpty()) { + val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU" + val infoText = buildString { + append(suSFSVersion) + append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})") + if (isSUS_SU) { + val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" } + if (susSUMode.isNotEmpty()) { + append(" ${stringResource(R.string.sus_su_mode)} $susSUMode") + } } } + + InfoCardItem( + stringResource(R.string.home_susfs_version), + infoText, + icon = Icons.Default.Storage + ) } - InfoCardItem( - stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage) } } } @@ -678,7 +901,7 @@ fun getManagerVersion(context: Context): Pair { @Preview @Composable private fun StatusCardPreview() { - Column { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { StatusCard(KernelVersion(5, 10, 101), 1, null) StatusCard(KernelVersion(5, 10, 101), 20000, true) StatusCard(KernelVersion(5, 10, 101), null, true) @@ -689,11 +912,11 @@ private fun StatusCardPreview() { @Preview @Composable private fun WarningCardPreview() { - Column { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { WarningCard(message = "Warning message") WarningCard( message = "Warning message ", - MaterialTheme.colorScheme.outlineVariant, + MaterialTheme.colorScheme.tertiaryContainer, onClick = {}) } } 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 1758922f..ad9f644d 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 @@ -50,7 +50,6 @@ import com.sukisu.ultra.flash.HorizonKernelWorker import com.sukisu.ultra.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.ThemeConfig import com.sukisu.ultra.ui.theme.getCardColors -import com.sukisu.ultra.ui.theme.getCardElevation import com.sukisu.ultra.ui.util.* /** @@ -584,7 +583,7 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { }, containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f), shape = MaterialTheme.shapes.medium, - tonalElevation = getCardElevation() + tonalElevation = 0.dp ) } } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt index 5390e221..cb3d8508 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt @@ -1,20 +1,24 @@ package com.sukisu.ultra.ui.screen -import android.app.Activity.RESULT_OK import android.content.Context import android.content.Intent import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.* 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.draw.shadow import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -36,6 +40,7 @@ import java.io.BufferedReader import java.io.FileInputStream import java.io.InputStreamReader import java.net.* +import android.app.Activity /** * KPM 管理界面 @@ -56,7 +61,7 @@ fun KpmScreen( val cardColor = if (!ThemeConfig.useDynamicColor) { ThemeConfig.currentTheme.ButtonContrast } else { - MaterialTheme.colorScheme.secondaryContainer + MaterialTheme.colorScheme.primaryContainer } val moduleConfirmContentMap = viewModel.moduleList.associate { module -> @@ -64,7 +69,7 @@ fun KpmScreen( module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName) } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val kpmInstallSuccess = stringResource(R.string.kpm_install_success) val kpmInstallFailed = stringResource(R.string.kpm_install_failed) @@ -103,16 +108,33 @@ fun KpmScreen( } } } + AlertDialog( onDismissRequest = { dismiss() tempFileForInstall?.delete() tempFileForInstall = null }, - title = { Text(kpmInstallMode) }, - text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } }, + title = { + Text( + text = kpmInstallMode, + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface + ) + }, + text = { + moduleName?.let { + Text( + text = stringResource(R.string.kpm_install_mode_description, it), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + }, confirmButton = { - Column { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { Button( onClick = { scope.launch { @@ -129,11 +151,20 @@ fun KpmScreen( } tempFileForInstall = null } - } + }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) ) { + Icon( + imageVector = Icons.Filled.Download, + contentDescription = null, + modifier = Modifier.size(18.dp).padding(end = 4.dp) + ) Text(kpmInstallModeLoad) } - Spacer(modifier = Modifier.height(8.dp)) + Button( onClick = { scope.launch { @@ -150,8 +181,17 @@ fun KpmScreen( } tempFileForInstall = null } - } + }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary + ) ) { + Icon( + imageVector = Icons.Filled.Inventory, + contentDescription = null, + modifier = Modifier.size(18.dp).padding(end = 4.dp) + ) Text(kpmInstallModeEmbed) } } @@ -166,14 +206,16 @@ fun KpmScreen( ) { Text(cancel) } - } + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = MaterialTheme.shapes.extraLarge ) } val selectPatchLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult + if (result.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult val uri = result.data?.data ?: return@rememberLauncherForActivityResult @@ -236,10 +278,13 @@ fun KpmScreen( onClearClick = { viewModel.search = "" }, scrollBehavior = scrollBehavior, dropdownContent = { - IconButton(onClick = { viewModel.fetchModuleList() }) { + IconButton( + onClick = { viewModel.fetchModuleList() } + ) { Icon( - imageVector = Icons.Outlined.Refresh, - contentDescription = stringResource(R.string.refresh) + imageVector = Icons.Filled.Refresh, + contentDescription = stringResource(R.string.refresh), + tint = MaterialTheme.colorScheme.primary ) } } @@ -256,39 +301,78 @@ fun KpmScreen( }, icon = { Icon( - imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.kpm_install) + imageVector = Icons.Filled.Add, + contentDescription = stringResource(R.string.kpm_install), + tint = MaterialTheme.colorScheme.onPrimaryContainer ) }, - text = { Text(stringResource(R.string.kpm_install)) }, - containerColor = cardColor.copy(alpha = 1f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer + text = { + Text( + text = stringResource(R.string.kpm_install), + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + }, + containerColor = cardColor, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + elevation = FloatingActionButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp + ), + expanded = true, + modifier = Modifier.padding(16.dp) ) }, snackbarHost = { SnackbarHost(snackBarHost) } ) { padding -> Column(modifier = Modifier.padding(padding)) { if (!isNoticeClosed) { - Row( + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ), modifier = Modifier .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .padding(16.dp) + .clip(MaterialTheme.shapes.medium) ) { - Text( - text = stringResource(R.string.kernel_module_notice), - modifier = Modifier.weight(1f), - textAlign = TextAlign.Center - ) - IconButton(onClick = { - isNoticeClosed = true - sharedPreferences.edit { putBoolean("is_notice_closed", true) } - }) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Icon( - imageVector = Icons.Outlined.Close, - contentDescription = stringResource(R.string.close_notice) + imageVector = Icons.Filled.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) ) + + Text( + text = stringResource(R.string.kernel_module_notice), + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + + IconButton( + onClick = { + isNoticeClosed = true + sharedPreferences.edit { putBoolean("is_notice_closed", true) } + }, + modifier = Modifier.size(24.dp), + colors = IconButtonDefaults.iconButtonColors( + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(R.string.close_notice) + ) + } } } } @@ -298,15 +382,30 @@ fun KpmScreen( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - Text( - stringResource(R.string.kpm_empty), - textAlign = TextAlign.Center - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Filled.Code, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f), + modifier = Modifier + .size(96.dp) + .padding(bottom = 16.dp) + ) + Text( + stringResource(R.string.kpm_empty), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } else { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(16.dp), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { items(viewModel.moduleList) { module -> @@ -489,13 +588,34 @@ private fun KpmModuleItem( if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) { AlertDialog( onDismissRequest = { viewModel.hideInputDialog() }, - title = { Text(stringResource(R.string.kpm_control)) }, + title = { + Text( + text = stringResource(R.string.kpm_control), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface + ) + }, text = { OutlinedTextField( value = viewModel.inputArgs, onValueChange = { viewModel.updateInputArgs(it) }, - label = { Text(stringResource(R.string.kpm_args)) }, - placeholder = { Text(module.args) } + label = { + Text( + text = stringResource(R.string.kpm_args), + color = MaterialTheme.colorScheme.primary + ) + }, + placeholder = { + Text( + text = module.args, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + }, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedBorderColor = MaterialTheme.colorScheme.outline + ) ) }, confirmButton = { @@ -512,23 +632,39 @@ private fun KpmModuleItem( } } ) { - Text(stringResource(R.string.confirm)) + Text( + text = stringResource(R.string.confirm), + color = MaterialTheme.colorScheme.primary + ) } }, dismissButton = { TextButton(onClick = { viewModel.hideInputDialog() }) { - Text(stringResource(R.string.cancel)) + Text( + text = stringResource(R.string.cancel), + color = MaterialTheme.colorScheme.primary + ) } - } + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = MaterialTheme.shapes.extraLarge ) } - ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) ) { Column( - modifier = Modifier.padding(16.dp) + modifier = Modifier.padding(20.dp) ) { Row( modifier = Modifier.fillMaxWidth(), @@ -538,54 +674,77 @@ private fun KpmModuleItem( Column(modifier = Modifier.weight(1f)) { Text( text = module.name, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface ) + + Spacer(modifier = Modifier.height(4.dp)) + Text( text = "${stringResource(R.string.kpm_version)}: ${module.version}", - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + Text( text = "${stringResource(R.string.kpm_author)}: ${module.author}", - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + Text( text = "${stringResource(R.string.kpm_args)}: ${module.args}", - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(12.dp)) Text( text = module.description, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(20.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - FilledTonalButton( + Button( onClick = { viewModel.showInputDialog(module.id) }, - enabled = module.hasAction + enabled = module.hasAction, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant + ) ) { Icon( - imageVector = Icons.Outlined.Settings, - contentDescription = null + imageVector = Icons.Filled.Settings, + contentDescription = null, + modifier = Modifier.size(20.dp) ) + Spacer(modifier = Modifier.width(8.dp)) Text(stringResource(R.string.kpm_control)) } - FilledTonalButton( - onClick = onUninstall + Button( + onClick = onUninstall, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ) ) { Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = null + imageVector = Icons.Filled.Delete, + contentDescription = null, + modifier = Modifier.size(20.dp) ) + Spacer(modifier = Modifier.width(8.dp)) Text(stringResource(R.string.kpm_uninstall)) } } 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 18086d27..ef093975 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 @@ -7,24 +7,11 @@ import android.net.Uri import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.* -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.toggleable @@ -32,41 +19,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.* import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.* -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Checkbox -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.* import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.shadow import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource @@ -105,7 +65,6 @@ import com.sukisu.ultra.ui.webui.WebUIActivity import okhttp3.OkHttpClient import com.sukisu.ultra.ui.util.ModuleModify import com.sukisu.ultra.ui.theme.getCardColors -import com.sukisu.ultra.ui.theme.getCardElevation import com.sukisu.ultra.ui.viewmodel.ModuleViewModel import java.io.BufferedReader import java.io.InputStreamReader @@ -254,7 +213,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val hideInstallButton = isSafeMode || hasMagisk - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val webUILauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() @@ -275,7 +234,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) { Icon( imageVector = Icons.Filled.MoreVert, - contentDescription = stringResource(id = R.string.settings) + contentDescription = stringResource(id = R.string.settings), + tint = MaterialTheme.colorScheme.primary ) DropdownMenu( @@ -284,7 +244,16 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) { DropdownMenuItem( text = { Text(stringResource(R.string.module_sort_action_first)) }, - trailingIcon = { Checkbox(viewModel.sortActionFirst, null) }, + trailingIcon = { + Checkbox( + checked = viewModel.sortActionFirst, + onCheckedChange = null, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + uncheckedColor = MaterialTheme.colorScheme.outline + ) + ) + }, onClick = { viewModel.sortActionFirst = !viewModel.sortActionFirst prefs.edit { @@ -300,23 +269,34 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) DropdownMenuItem( text = { Text(stringResource(R.string.module_sort_enabled_first)) }, - trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) }, + trailingIcon = { + Checkbox( + checked = viewModel.sortEnabledFirst, + onCheckedChange = null, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + uncheckedColor = MaterialTheme.colorScheme.outline + ) + ) + }, onClick = { viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst prefs.edit { - putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst) - } + putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst) + } scope.launch { viewModel.fetchModuleList() } } ) + HorizontalDivider(thickness = Dp.Hairline, modifier = Modifier.padding(vertical = 4.dp)) DropdownMenuItem( text = { Text(stringResource(R.string.backup_modules)) }, leadingIcon = { Icon( imageVector = Icons.Outlined.Download, - contentDescription = stringResource(R.string.backup) + contentDescription = stringResource(R.string.backup), + tint = MaterialTheme.colorScheme.primary ) }, onClick = { @@ -329,7 +309,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) { leadingIcon = { Icon( imageVector = Icons.Outlined.Refresh, - contentDescription = stringResource(R.string.restore) + contentDescription = stringResource(R.string.restore), + tint = MaterialTheme.colorScheme.primary ) }, onClick = { @@ -349,7 +330,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val cardColor = if (!ThemeConfig.useDynamicColor) { ThemeConfig.currentTheme.ButtonContrast } else { - MaterialTheme.colorScheme.secondaryContainer + MaterialTheme.colorScheme.primaryContainer } ExtendedFloatingActionButton( onClick = { @@ -363,16 +344,24 @@ fun ModuleScreen(navigator: DestinationsNavigator) { icon = { Icon( imageVector = Icons.Filled.Add, - contentDescription = moduleInstall + contentDescription = moduleInstall, + tint = MaterialTheme.colorScheme.onPrimaryContainer ) }, text = { Text( - text = moduleInstall + text = moduleInstall, + color = MaterialTheme.colorScheme.onPrimaryContainer ) }, - containerColor = cardColor.copy(alpha = 1f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer + containerColor = cardColor, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + elevation = FloatingActionButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp + ), + expanded = true, + modifier = Modifier.padding(16.dp) ) } }, @@ -389,10 +378,25 @@ fun ModuleScreen(navigator: DestinationsNavigator) { .padding(24.dp), contentAlignment = Alignment.Center ) { - Text( - stringResource(R.string.module_magisk_conflict), - textAlign = TextAlign.Center, - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Outlined.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + modifier = Modifier + .size(64.dp) + .padding(bottom = 16.dp) + ) + Text( + stringResource(R.string.module_magisk_conflict), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } else -> { @@ -591,10 +595,25 @@ private fun ModuleList( modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center ) { - Text( - stringResource(R.string.module_empty), - textAlign = TextAlign.Center - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Outlined.Extension, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f), + modifier = Modifier + .size(96.dp) + .padding(bottom = 16.dp) + ) + Text( + text = stringResource(R.string.module_empty), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } } @@ -677,8 +696,16 @@ fun ModuleItem( onClick: (ModuleViewModel.ModuleInfo) -> Unit ) { ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), - elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.large, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) ) { val textDecoration = if (!module.remove) null else TextDecoration.LineThrough val interactionSource = remember { MutableInteractionSource() } @@ -706,6 +733,7 @@ fun ModuleItem( Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { val moduleVersion = stringResource(id = R.string.module_version) val moduleAuthor = stringResource(id = R.string.module_author) @@ -720,6 +748,7 @@ fun ModuleItem( lineHeight = MaterialTheme.typography.bodySmall.lineHeight, fontFamily = MaterialTheme.typography.titleMedium.fontFamily, textDecoration = textDecoration, + color = MaterialTheme.colorScheme.onSurface ) Text( @@ -727,7 +756,8 @@ fun ModuleItem( fontSize = MaterialTheme.typography.bodySmall.fontSize, lineHeight = MaterialTheme.typography.bodySmall.lineHeight, fontFamily = MaterialTheme.typography.bodySmall.fontFamily, - textDecoration = textDecoration + textDecoration = textDecoration, + color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( @@ -735,7 +765,8 @@ fun ModuleItem( fontSize = MaterialTheme.typography.bodySmall.fontSize, lineHeight = MaterialTheme.typography.bodySmall.lineHeight, fontFamily = MaterialTheme.typography.bodySmall.fontFamily, - textDecoration = textDecoration + textDecoration = textDecoration, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } @@ -749,7 +780,15 @@ fun ModuleItem( enabled = !module.update, checked = module.enabled, onCheckedChange = onCheckChanged, - interactionSource = if (!module.hasWebUi) interactionSource else null + interactionSource = if (!module.hasWebUi) interactionSource else null, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + checkedIconColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant, + uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant + ) ) } } @@ -764,23 +803,23 @@ fun ModuleItem( fontWeight = MaterialTheme.typography.bodySmall.fontWeight, overflow = TextOverflow.Ellipsis, maxLines = 4, - textDecoration = textDecoration + textDecoration = textDecoration, + color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.height(16.dp)) HorizontalDivider(thickness = Dp.Hairline) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(8.dp)) Row( - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { - if (module.hasActionScript) { FilledTonalButton( - modifier = Modifier.defaultMinSize(52.dp, 32.dp), + modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp), enabled = !module.remove && module.enabled, onClick = { navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId)) @@ -809,13 +848,11 @@ fun ModuleItem( ) } } - - Spacer(modifier = Modifier.weight(0.1f, true)) } if (module.hasWebUi) { FilledTonalButton( - modifier = Modifier.defaultMinSize(52.dp, 32.dp), + modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp), enabled = !module.remove && module.enabled, onClick = { onClick(module) }, interactionSource = interactionSource, @@ -848,7 +885,7 @@ fun ModuleItem( if (updateUrl.isNotEmpty()) { Button( - modifier = Modifier.defaultMinSize(52.dp, 32.dp), + modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp), enabled = !module.remove, onClick = { onUpdate(module) }, shape = ButtonDefaults.textShape, @@ -868,20 +905,20 @@ fun ModuleItem( ) } } - - Spacer(modifier = Modifier.weight(0.1f, true)) } FilledTonalButton( - modifier = Modifier.defaultMinSize(52.dp, 32.dp), + modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp), onClick = { onUninstallClicked(module) }, contentPadding = ButtonDefaults.TextButtonContentPadding, colors = if (!ThemeConfig.useDynamicColor) { ButtonDefaults.filledTonalButtonColors( - containerColor = ThemeConfig.currentTheme.ButtonContrast + containerColor = if (!module.remove) MaterialTheme.colorScheme.errorContainer else ThemeConfig.currentTheme.ButtonContrast ) } else { - ButtonDefaults.filledTonalButtonColors() + ButtonDefaults.filledTonalButtonColors( + containerColor = if (!module.remove) MaterialTheme.colorScheme.errorContainer else MaterialTheme.colorScheme.secondaryContainer + ) } ) { if (!module.remove) { @@ -889,13 +926,13 @@ fun ModuleItem( modifier = Modifier.size(20.dp), imageVector = Icons.Outlined.Delete, contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer ) } else { Icon( modifier = Modifier.size(20.dp).rotate(180f), imageVector = Icons.Outlined.Refresh, - contentDescription = null, - + contentDescription = null ) } if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) { @@ -903,7 +940,8 @@ fun ModuleItem( modifier = Modifier.padding(start = 7.dp), fontFamily = MaterialTheme.typography.labelMedium.fontFamily, fontSize = MaterialTheme.typography.labelMedium.fontSize, - text = stringResource(if (!module.remove) R.string.uninstall else R.string.restore) + text = stringResource(if (!module.remove) R.string.uninstall else R.string.restore), + color = if (!module.remove) MaterialTheme.colorScheme.onErrorContainer else MaterialTheme.colorScheme.onSecondaryContainer ) } } @@ -931,5 +969,4 @@ fun ModuleItemPreview() { dirId = "dirId" ) ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {}) -} - +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt index 7cb59966..cabbfcd9 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt @@ -1,57 +1,111 @@ package com.sukisu.ultra.ui.screen -import androidx.compose.animation.AnimatedVisibility import android.content.Context import android.net.Uri import android.os.Build -import androidx.compose.foundation.layout.* +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll 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.material.icons.automirrored.filled.NavigateNext +import androidx.compose.material.icons.filled.Brush +import androidx.compose.material.icons.filled.ColorLens +import androidx.compose.material.icons.filled.DarkMode +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Opacity +import androidx.compose.material.icons.filled.Palette +import androidx.compose.material.icons.filled.Security +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material.icons.filled.Wallpaper +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +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.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment +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.topjohnwu.superuser.Shell -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +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.SwitchItem -import com.sukisu.ultra.ui.theme.* -import com.sukisu.ultra.ui.util.* -import androidx.core.content.edit -import com.sukisu.ultra.R -import com.sukisu.ultra.* - +import com.sukisu.ultra.ui.theme.CardConfig +import com.sukisu.ultra.ui.theme.ThemeColors +import com.sukisu.ultra.ui.theme.ThemeConfig +import com.sukisu.ultra.ui.theme.saveAndApplyCustomBackground +import com.sukisu.ultra.ui.theme.saveCustomBackground +import com.sukisu.ultra.ui.theme.saveDynamicColorState +import com.sukisu.ultra.ui.theme.saveThemeColors +import com.sukisu.ultra.ui.theme.saveThemeMode +import com.sukisu.ultra.ui.util.getSuSFS +import com.sukisu.ultra.ui.util.getSuSFSFeatures +import com.sukisu.ultra.ui.util.susfsSUS_SU_0 +import com.sukisu.ultra.ui.util.susfsSUS_SU_2 +import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode +import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlin.math.roundToInt fun saveCardConfig(context: Context) { - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - with(prefs.edit()) { - putFloat("card_alpha", CardConfig.cardAlpha) - putBoolean("custom_background_enabled", CardConfig.cardElevation == 0.dp) - putBoolean("is_custom_alpha_set", CardConfig.isCustomAlphaSet) - apply() - } + CardConfig.save(context) } @OptIn(ExperimentalMaterial3Api::class) @@ -60,7 +114,10 @@ fun saveCardConfig(context: Context) { 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( @@ -85,23 +142,23 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) { stringResource(R.string.theme_dark) ) - // 简洁模块开关状态 + // 简洁模式开关状态 var isSimpleMode by remember { mutableStateOf(prefs.getBoolean("is_simple_mode", false)) } - // 更新简洁模块开关状态 + // 更新简洁模式开关状态 val onSimpleModeChange = { newValue: Boolean -> prefs.edit { putBoolean("is_simple_mode", newValue) } isSimpleMode = newValue } - // 隐藏内核 KernelSU 版本号开关状态 + // 隐藏内核版本号开关状态 var isHideVersion by remember { mutableStateOf(prefs.getBoolean("is_hide_version", false)) } - // 隐藏内核 KernelSU 版本号模块开关状态 + // 隐藏内核版本号开关状态 val onHideVersionChange = { newValue: Boolean -> prefs.edit { putBoolean("is_hide_version", newValue) } isHideVersion = newValue @@ -118,18 +175,18 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) { isHideOtherInfo = newValue } - // 隐藏 SuSFS 状态开关状态 + // 隐藏SuSFS状态开关状态 var isHideSusfsStatus by remember { mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false)) } - // 隐藏 SuSFS 状态开关状态 + // 隐藏SuSFS状态开关状态 val onHideSusfsStatusChange = { newValue: Boolean -> prefs.edit { putBoolean("is_hide_susfs_status", newValue) } isHideSusfsStatus = newValue } - // SELinux 状态 + // SELinux状态 var selinuxEnabled by remember { mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing") } @@ -145,27 +202,48 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) { var showImageEditor by remember { mutableStateOf(false) } var selectedImageUri by remember { mutableStateOf(null) } - // 初始化卡片配置 - val systemIsDark = isSystemInDarkTheme() - LaunchedEffect(Unit) { - CardConfig.apply { - cardAlpha = prefs.getFloat("card_alpha", 0.45f) - cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation - isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false) + // 展开/折叠状态 + var isCustomizeExpanded by remember { mutableStateOf(false) } + var isAppearanceExpanded by remember { mutableStateOf(true) } + var isAdvancedExpanded by remember { mutableStateOf(false) } - // 如果没有手动设置透明度,且是深色模式,则使用默认值 - if (!isCustomAlphaSet) { - val isDarkMode = ThemeConfig.forceDarkMode ?: systemIsDark - if (isDarkMode) { - cardAlpha = 0.35f - } - } - } + // 初始化卡片配置 + LaunchedEffect(Unit) { + // 加载设置 + CardConfig.load(context) + cardAlpha = CardConfig.cardAlpha + 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.setDarkModeDefaults() + } + + // 保存设置 + CardConfig.save(context) } // 主题色选项 @@ -209,20 +287,29 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) { saveCardConfig(context) showImageEditor = false selectedImageUri = null + + // 显示成功提示 + Toast.makeText( + context, + context.getString(R.string.background_set_success), + Toast.LENGTH_SHORT + ).show() } ) } Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar( title = { Text(stringResource(R.string.more_settings)) }, navigationIcon = { IconButton(onClick = { navigator.popBackStack() }) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, null) + Icon(Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back)) } }, - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, ) } ) { paddingValues -> @@ -230,402 +317,579 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) { modifier = Modifier .padding(paddingValues) .verticalScroll(rememberScrollState()) - .padding(top = 12.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) ) { - // SELinux 开关 - if (ksuIsValid) { - SwitchItem( - 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 - ) { enabled -> - val command = if (enabled) "setenforce 1" else "setenforce 0" - Shell.getShell().newJob().add(command).exec().let { result -> - if (result.isSuccess) selinuxEnabled = enabled - } - } - } - - var isExpanded by remember { mutableStateOf(false) } - - ListItem( - leadingContent = { Icon(Icons.Filled.AutoFixHigh, null) }, - headlineContent = { Text(stringResource(R.string.custom_settings)) }, - modifier = Modifier.clickable { - isExpanded = !isExpanded - } + // 外观设置部分 + SectionHeader( + title = stringResource(R.string.appearance_settings), + expanded = isAppearanceExpanded, + onToggle = { isAppearanceExpanded = !isAppearanceExpanded } ) - AnimatedVisibility( - visible = isExpanded, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - // 添加简洁模块开关 - SwitchItem( - icon = Icons.Filled.Brush, - title = stringResource(R.string.simple_mode), - summary = stringResource(R.string.simple_mode_summary), - checked = isSimpleMode - ) { - onSimpleModeChange(it) - } - } - AnimatedVisibility( - visible = isExpanded, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - // 隐藏内核部分版本号 - SwitchItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_kernel_kernelsu_version), - summary = stringResource(R.string.hide_kernel_kernelsu_version_summary), - checked = isHideVersion - ) { - onHideVersionChange(it) - } - } - AnimatedVisibility( - visible = isExpanded, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - // 模块数量等信息 - SwitchItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_other_info), - summary = stringResource(R.string.hide_other_info_summary), - checked = isHideOtherInfo - ) { - onHideOtherInfoChange(it) - } - } - AnimatedVisibility( - visible = isExpanded, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - // SuSFS 状态信息 - SwitchItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(R.string.hide_susfs_status), - summary = stringResource(R.string.hide_susfs_status_summary), - checked = isHideSusfsStatus - ) { - onHideSusfsStatusChange(it) - } - } - // region SUSFS 配置(仅在支持时显示) - val suSFS = getSuSFS() - val isSUS_SU = getSuSFSFeatures() - if (suSFS == "Supported") { - if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { - // 初始化时,默认启用 - var isEnabled by rememberSaveable { - mutableStateOf(true) // 默认启用 - } + AnimatedVisibility( + visible = isAppearanceExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Surface( + shape = RoundedCornerShape(16.dp), + tonalElevation = 1.dp, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Column { + // 主题模式 + ListItem( + headlineContent = { Text(stringResource(R.string.theme_mode)) }, + supportingContent = { Text(themeOptions[themeMode]) }, + leadingContent = { + Icon( + Icons.Default.DarkMode, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + }, + trailingContent = { + Icon( + Icons.AutoMirrored.Filled.NavigateNext, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent + ), + modifier = Modifier.clickable { showThemeModeDialog = 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) } + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + + // 动态颜色开关 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + SwitchItem( + icon = Icons.Filled.ColorLens, + title = stringResource(R.string.dynamic_color_title), + summary = stringResource(R.string.dynamic_color_summary), + checked = useDynamicColor + ) { enabled -> + useDynamicColor = enabled + context.saveDynamicColorState(enabled) + } + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) } - isEnabled = currentMode == "2" - } - SwitchItem( - icon = Icons.Filled.VisibilityOff, - title = stringResource(id = R.string.settings_susfs_toggle), - summary = stringResource(id = R.string.settings_susfs_toggle_summary), - checked = isEnabled - ) { - if (it) { - // 手动启用 - susfsSUS_SU_2() - prefs.edit { putBoolean("enable_sus_su", true) } - } else { - // 手动关闭 - susfsSUS_SU_0() - prefs.edit { putBoolean("enable_sus_su", false) } - } - isEnabled = it - } - } - } - // endregion - // 动态颜色开关 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - SwitchItem( - icon = Icons.Filled.ColorLens, - title = stringResource(R.string.dynamic_color_title), - summary = stringResource(R.string.dynamic_color_summary), - checked = useDynamicColor - ) { enabled -> - useDynamicColor = enabled - context.saveDynamicColorState(enabled) - } - } - // 只在未启用动态颜色时显示主题色选择 - AnimatedVisibility( - visible = !useDynamicColor - ) { - ListItem( - leadingContent = { Icon(Icons.Default.Palette, null) }, - headlineContent = { Text(stringResource(R.string.theme_color)) }, - supportingContent = { - val currentThemeName = when (ThemeConfig.currentTheme) { - is ThemeColors.Default -> stringResource(R.string.color_default) - is ThemeColors.Blue -> stringResource(R.string.color_blue) - 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) - } - Text(currentThemeName) - }, - modifier = Modifier.clickable { showThemeColorDialog = true } - ) - - if (showThemeColorDialog) { - AlertDialog( - onDismissRequest = { showThemeColorDialog = false }, - title = { Text(stringResource(R.string.choose_theme_color)) }, - text = { + // 只在未启用动态颜色时显示主题色选择 + AnimatedVisibility( + visible = !useDynamicColor, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { Column { - themeColorOptions.forEach { (name, theme) -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - context.saveThemeColors(when (theme) { - ThemeColors.Default -> "default" - ThemeColors.Blue -> "blue" - 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 - ) { - RadioButton( - selected = ThemeConfig.currentTheme::class == theme::class, - onClick = null + ListItem( + headlineContent = { Text(stringResource(R.string.theme_color)) }, + supportingContent = { + val currentThemeName = when (ThemeConfig.currentTheme) { + is ThemeColors.Default -> stringResource(R.string.color_default) + is ThemeColors.Blue -> stringResource(R.string.color_blue) + 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) + } + Text(currentThemeName) + }, + leadingContent = { + Icon( + Icons.Default.Palette, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary ) - Spacer(modifier = Modifier.width(8.dp)) - Box( - modifier = Modifier - .size(24.dp) - .background(theme.Primary, shape = CircleShape) + }, + trailingContent = { + Icon( + Icons.AutoMirrored.Filled.NavigateNext, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant ) - Spacer(modifier = Modifier.width(8.dp)) - Text(name) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent + ), + modifier = Modifier.clickable { showThemeColorDialog = true } + ) + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + } + } + + // 自定义背景开关 + ListItem( + headlineContent = { Text(stringResource(id = R.string.settings_custom_background)) }, + supportingContent = { Text(stringResource(id = R.string.settings_custom_background_summary)) }, + leadingContent = { + Icon( + Icons.Filled.Wallpaper, + contentDescription = null, + tint = if (isCustomBackgroundEnabled) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + trailingContent = { + Switch( + checked = isCustomBackgroundEnabled, + onCheckedChange = { isChecked -> + if (isChecked) { + pickImageLauncher.launch("image/*") + } else { + context.saveCustomBackground(null) + isCustomBackgroundEnabled = false + CardConfig.cardElevation = CardConfig.defaultElevation + CardConfig.cardAlpha = 0.80f + CardConfig.isCustomAlphaSet = false + CardConfig.isCustomBackgroundEnabled = false + saveCardConfig(context) + cardAlpha = 0.80f + + // 重置其他相关设置 + ThemeConfig.needsResetOnThemeChange = true + + // 显示成功提示 + Toast.makeText( + context, + context.getString(R.string.background_removed), + Toast.LENGTH_SHORT + ).show() + } + } + ) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent + ), + modifier = Modifier.clickable { + if (isCustomBackgroundEnabled) { + showCardSettings = !showCardSettings + } + } + ) + + // 透明度 Slider + AnimatedVisibility( + visible = ThemeConfig.customBackgroundUri != null && showCardSettings, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + modifier = Modifier.padding(horizontal = 16.dp) + ) { + Column(modifier = Modifier.padding(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, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Slider( + value = cardAlpha, + 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 + ) + ) + } + } + } + } + } + + // 自定义设置部分 + SectionHeader( + title = stringResource(R.string.custom_settings), + expanded = isCustomizeExpanded, + onToggle = { isCustomizeExpanded = !isCustomizeExpanded } + ) + + AnimatedVisibility( + visible = isCustomizeExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Surface( + shape = RoundedCornerShape(16.dp), + tonalElevation = 1.dp, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Column { + // 添加简洁模式开关 + SwitchItem( + icon = Icons.Filled.Brush, + title = stringResource(R.string.simple_mode), + summary = stringResource(R.string.simple_mode_summary), + checked = isSimpleMode + ) { + onSimpleModeChange(it) + } + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + + // 隐藏内核部分版本号 + SwitchItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_kernel_kernelsu_version), + summary = stringResource(R.string.hide_kernel_kernelsu_version_summary), + checked = isHideVersion + ) { + onHideVersionChange(it) + } + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + + // 模块数量等信息 + SwitchItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_other_info), + summary = stringResource(R.string.hide_other_info_summary), + checked = isHideOtherInfo + ) { + onHideOtherInfoChange(it) + } + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + + // SuSFS 状态信息 + SwitchItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(R.string.hide_susfs_status), + summary = stringResource(R.string.hide_susfs_status_summary), + checked = isHideSusfsStatus + ) { + onHideSusfsStatusChange(it) + } + } + } + } + + // 高级设置部分 + SectionHeader( + title = stringResource(R.string.advanced_settings), + expanded = isAdvancedExpanded, + onToggle = { isAdvancedExpanded = !isAdvancedExpanded } + ) + + AnimatedVisibility( + visible = isAdvancedExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Surface( + shape = RoundedCornerShape(16.dp), + tonalElevation = 1.dp, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Column { + // SELinux 开关 + if (ksuIsValid) { + SwitchItem( + 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 + ) { 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() } } } - }, - confirmButton = {} - ) - } - } - // 自定义背景开关 - ListItem( - leadingContent = { Icon(Icons.Filled.Wallpaper, null) }, - headlineContent = { Text(stringResource(id = R.string.settings_custom_background)) }, - supportingContent = { Text(stringResource(id = R.string.settings_custom_background_summary)) }, - modifier = Modifier.clickable { - if (isCustomBackgroundEnabled) { - showCardSettings = !showCardSettings - } - }, - trailingContent = { - Switch( - checked = isCustomBackgroundEnabled, - onCheckedChange = { isChecked -> - if (isChecked) { - pickImageLauncher.launch("image/*") - } else { - context.saveCustomBackground(null) - isCustomBackgroundEnabled = false - CardConfig.cardElevation = CardConfig.defaultElevation - CardConfig.cardAlpha = 0.45f - CardConfig.isCustomAlphaSet = false - CardConfig.isCustomBackgroundEnabled = false - saveCardConfig(context) - cardAlpha = 0.35f - themeMode = 0 - context.saveThemeMode(null) - CardConfig.isUserDarkModeEnabled = false - CardConfig.isUserLightModeEnabled = false - CardConfig.save(context) - } - } - ) - } - ) - // 透明度 Slider - AnimatedVisibility( - visible = ThemeConfig.customBackgroundUri != null && showCardSettings, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - ListItem( - leadingContent = { Icon(Icons.Filled.Opacity, null) }, - headlineContent = { Text(stringResource(R.string.settings_card_alpha)) }, - supportingContent = { - Slider( - value = cardAlpha, - onValueChange = { newValue -> - cardAlpha = newValue - CardConfig.cardAlpha = newValue - CardConfig.isCustomAlphaSet = true - prefs.edit { putBoolean("is_custom_alpha_set", true) } - prefs.edit { putFloat("card_alpha", newValue) } - }, - onValueChangeFinished = { - CoroutineScope(Dispatchers.IO).launch { - saveCardConfig(context) - } - }, - valueRange = 0f..1f, - colors = getSliderColors(cardAlpha, useCustomColors = true), - thumb = { - SliderDefaults.Thumb( - interactionSource = remember { MutableInteractionSource() }, - thumbSize = DpSize(0.dp, 0.dp) - ) - } - ) - } - ) - } - AnimatedVisibility( - visible = ThemeConfig.customBackgroundUri != null && showCardSettings, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ){ - ListItem( - leadingContent = { Icon(Icons.Filled.DarkMode, null) }, - headlineContent = { Text(stringResource(R.string.theme_mode)) }, - supportingContent = { Text(themeOptions[themeMode]) }, - modifier = Modifier.clickable { - showThemeModeDialog = true - } - ) - } - // 主题模式选择对话框 - if (showThemeModeDialog) { - AlertDialog( - onDismissRequest = { showThemeModeDialog = false }, - title = { Text(stringResource(R.string.theme_mode)) }, - text = { - Column { - themeOptions.forEachIndexed { index, option -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - themeMode = index - val newThemeMode = when(index) { - 0 -> null // 跟随系统 - 1 -> false // 浅色 - 2 -> true // 深色 - else -> null - } - context.saveThemeMode(newThemeMode) - when (index) { - 2 -> { - ThemeConfig.forceDarkMode = true - CardConfig.isUserLightModeEnabled = false - CardConfig.isUserDarkModeEnabled = true - CardConfig.save(context) - } - 1 -> { - ThemeConfig.forceDarkMode = false - CardConfig.isUserLightModeEnabled = true - CardConfig.isUserDarkModeEnabled = false - CardConfig.save(context) - } - 0 -> { - ThemeConfig.forceDarkMode = null - CardConfig.isUserLightModeEnabled = false - CardConfig.isUserDarkModeEnabled = false - CardConfig.save(context) - } - } - showThemeModeDialog = false - } - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = themeMode == index, - onClick = null - ) - Spacer(modifier = Modifier.width(8.dp)) - Text(option) + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + } + + // 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" + } + + SwitchItem( + icon = Icons.Filled.Security, + title = stringResource(id = R.string.settings_susfs_toggle), + summary = stringResource(id = R.string.settings_susfs_toggle_summary), + checked = isEnabled + ) { + 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 } } - }, - confirmButton = {} - ) + } + } } } } + + // 主题模式选择对话框 + if (showThemeModeDialog) { + AlertDialog( + onDismissRequest = { showThemeModeDialog = false }, + title = { Text(stringResource(R.string.theme_mode)) }, + text = { + Column { + themeOptions.forEachIndexed { index, option -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + 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 + if (!CardConfig.isCustomAlphaSet) { + CardConfig.cardAlpha = 0.35f + } + CardConfig.save(context) + } + 1 -> { // 浅色 + ThemeConfig.forceDarkMode = false + CardConfig.isUserLightModeEnabled = true + CardConfig.isUserDarkModeEnabled = false + if (!CardConfig.isCustomAlphaSet) { + CardConfig.cardAlpha = 0.80f + } + CardConfig.save(context) + } + 0 -> { // 跟随系统 + ThemeConfig.forceDarkMode = null + CardConfig.isUserLightModeEnabled = false + CardConfig.isUserDarkModeEnabled = false + CardConfig.save(context) + } + } + showThemeModeDialog = false + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = themeMode == index, + onClick = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(option) + } + } + } + }, + confirmButton = { + TextButton( + onClick = { showThemeModeDialog = false } + ) { + Text(stringResource(R.string.cancel)) + } + } + ) + } + + // 主题色选择对话框 + 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.Default -> "default" + ThemeColors.Blue -> "blue" + 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 + ) { + RadioButton( + selected = ThemeConfig.currentTheme::class == theme::class, + onClick = null + ) + Spacer(modifier = Modifier.width(12.dp)) + Box( + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + .background(theme.Primary) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text(name) + } + } + } + }, + confirmButton = { + TextButton( + onClick = { showThemeColorDialog = false } + ) { + Text(stringResource(R.string.cancel)) + } + } + ) + } } - @Composable -private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors { - val theme = ThemeConfig.currentTheme - val isDarkTheme = ThemeConfig.forceDarkMode ?: isSystemInDarkTheme() - val useDynamicColor = ThemeConfig.useDynamicColor - - return when { - // 使用动态颜色时 - useDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - SliderDefaults.colors( - activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f), - inactiveTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f), - thumbColor = MaterialTheme.colorScheme.primary - ) - } - // 使用自定义主题色时 - useCustomColors -> { - SliderDefaults.colors( - activeTrackColor = theme.getCustomSliderActiveColor(), - inactiveTrackColor = theme.getCustomSliderInactiveColor(), - thumbColor = theme.Primary - ) - } - else -> { - val activeColor = if (isDarkTheme) { - theme.Primary.copy(alpha = cardAlpha) - } else { - theme.Primary.copy(alpha = cardAlpha) - } - val inactiveColor = if (isDarkTheme) { - Color.DarkGray.copy(alpha = 0.3f) - } else { - Color.LightGray.copy(alpha = 0.3f) - } - SliderDefaults.colors( - activeTrackColor = activeColor, - inactiveTrackColor = inactiveColor, - thumbColor = activeColor - ) - } +fun SectionHeader( + title: String, + expanded: Boolean, + onToggle: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onToggle() } + .padding(vertical = 12.dp, horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold + ), + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + imageVector = if (expanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + contentDescription = if (expanded) + stringResource(R.string.collapse) + else + stringResource(R.string.expand), + tint = MaterialTheme.colorScheme.primary + ) } } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index d4677fc9..58bc2348 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -6,14 +6,18 @@ import android.net.Uri import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.* +import androidx.compose.animation.core.* +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.* -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -24,13 +28,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip 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.style.LineHeightStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.FileProvider import androidx.core.content.edit @@ -42,7 +44,6 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -52,25 +53,20 @@ import com.sukisu.ultra.R import com.sukisu.ultra.* import com.sukisu.ultra.ui.component.* import com.sukisu.ultra.ui.theme.* +import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha import com.sukisu.ultra.ui.util.LocalSnackbarHost import com.sukisu.ultra.ui.util.getBugreportFile import java.time.LocalDateTime import java.time.format.DateTimeFormatter -/** - * @author weishu - * @date 2023/1/1. - */ @OptIn(ExperimentalMaterial3Api::class) @Destination @Composable fun SettingScreen(navigator: DestinationsNavigator) { - // region 界面基础设置 - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val snackBarHost = LocalSnackbarHost.current val ksuIsValid = Natives.isKsuValid(ksuApp.packageName) - // endregion Scaffold( topBar = { @@ -114,237 +110,413 @@ fun SettingScreen(navigator: DestinationsNavigator) { snackBarHost.showSnackbar(context.getString(R.string.log_saved)) } } - // region 配置项列表 - // 配置文件模板入口 - val profileTemplate = stringResource(id = R.string.settings_profile_template) - if (ksuIsValid) { - ListItem( - leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, - headlineContent = { Text(profileTemplate) }, - supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) }, - modifier = Modifier.clickable { - navigator.navigate(AppProfileTemplateScreenDestination) - } - ) - } - // 卸载模块开关 - var umountChecked by rememberSaveable { - mutableStateOf(Natives.isDefaultUmountModules()) - } - - if (ksuIsValid) { - SwitchItem( - icon = Icons.Filled.FolderDelete, - title = stringResource(id = R.string.settings_umount_modules_default), - summary = stringResource(id = R.string.settings_umount_modules_default_summary), - checked = umountChecked - ) { - if (Natives.setDefaultUmountModules(it)) { - umountChecked = it - } - } - } - // SU 禁用开关(仅在兼容版本显示) - if (ksuIsValid) { - if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { - var isSuDisabled by rememberSaveable { - mutableStateOf(!Natives.isSuEnabled()) + + // 设置分组卡片 - 配置 + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha) + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Text( + text = stringResource(R.string.configuration), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + + // 配置文件模板入口 + val profileTemplate = stringResource(id = R.string.settings_profile_template) + if (ksuIsValid) { + SettingItem( + icon = Icons.Filled.Fence, + title = profileTemplate, + summary = stringResource(id = R.string.settings_profile_template_summary), + onClick = { + navigator.navigate(AppProfileTemplateScreenDestination) + } + ) } - SwitchItem( - icon = Icons.Filled.RemoveModerator, - title = stringResource(id = R.string.settings_disable_su), - summary = stringResource(id = R.string.settings_disable_su_summary), - checked = isSuDisabled, - ) { checked -> - val shouldEnable = !checked - if (Natives.setSuEnabled(shouldEnable)) { - isSuDisabled = !shouldEnable + + // 卸载模块开关 + var umountChecked by rememberSaveable { + mutableStateOf(Natives.isDefaultUmountModules()) + } + + if (ksuIsValid) { + SwitchSettingItem( + icon = Icons.Filled.FolderDelete, + title = stringResource(id = R.string.settings_umount_modules_default), + summary = stringResource(id = R.string.settings_umount_modules_default_summary), + checked = umountChecked, + onCheckedChange = { + if (Natives.setDefaultUmountModules(it)) { + umountChecked = it + } + } + ) + } + + // SU 禁用开关(仅在兼容版本显示) + if (ksuIsValid) { + if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { + var isSuDisabled by rememberSaveable { + mutableStateOf(!Natives.isSuEnabled()) + } + SwitchSettingItem( + icon = Icons.Filled.RemoveModerator, + title = stringResource(id = R.string.settings_disable_su), + summary = stringResource(id = R.string.settings_disable_su_summary), + checked = isSuDisabled, + onCheckedChange = { checked -> + val shouldEnable = !checked + if (Natives.setSuEnabled(shouldEnable)) { + isSuDisabled = !shouldEnable + } + } + ) } } } } - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - - // 更新检查开关 - var checkUpdate by rememberSaveable { - mutableStateOf( - prefs.getBoolean("check_update", true) - ) - } - SwitchItem( - icon = Icons.Filled.Update, - title = stringResource(id = R.string.settings_check_update), - summary = stringResource(id = R.string.settings_check_update_summary), - checked = checkUpdate + // 设置分组卡片 - 应用设置 + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha) + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) ) { - prefs.edit {putBoolean("check_update", it) } - checkUpdate = it - } - - // Web调试开关 - var enableWebDebugging by rememberSaveable { - mutableStateOf( - prefs.getBoolean("enable_web_debugging", false) - ) - } - if (Natives.isKsuValid(ksuApp.packageName)) { - SwitchItem( - icon = Icons.Filled.DeveloperMode, - title = stringResource(id = R.string.enable_web_debugging), - summary = stringResource(id = R.string.enable_web_debugging_summary), - checked = enableWebDebugging - ) { - prefs.edit { putBoolean("enable_web_debugging", it) } - enableWebDebugging = it - } - } - // 更多设置 - val newButtonTitle = stringResource(id = R.string.more_settings) - ListItem( - leadingContent = { - Icon( - Icons.Filled.Settings, - contentDescription = newButtonTitle + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Text( + text = stringResource(R.string.app_settings), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) - }, - headlineContent = { Text(newButtonTitle) }, - supportingContent = { Text(stringResource(id = R.string.more_settings)) }, - modifier = Modifier.clickable { - navigator.navigate(MoreSettingsScreenDestination) - } - ) - var showBottomsheet by remember { mutableStateOf(false) } + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - ListItem( - leadingContent = { - Icon( - Icons.Filled.BugReport, - stringResource(id = R.string.send_log) + // 更新检查开关 + var checkUpdate by rememberSaveable { + mutableStateOf( + prefs.getBoolean("check_update", true) + ) + } + SwitchSettingItem( + icon = Icons.Filled.Update, + title = stringResource(id = R.string.settings_check_update), + summary = stringResource(id = R.string.settings_check_update_summary), + checked = checkUpdate, + onCheckedChange = { + prefs.edit {putBoolean("check_update", it) } + checkUpdate = it + } ) - }, - headlineContent = { Text(stringResource(id = R.string.send_log)) }, - modifier = Modifier.clickable { - showBottomsheet = true - } - ) - if (showBottomsheet) { - ModalBottomSheet( - onDismissRequest = { showBottomsheet = false }, - content = { - Row( - modifier = Modifier - .padding(10.dp) - .align(Alignment.CenterHorizontally) + // Web调试开关 + var enableWebDebugging by rememberSaveable { + mutableStateOf( + prefs.getBoolean("enable_web_debugging", false) + ) + } + if (Natives.isKsuValid(ksuApp.packageName)) { + SwitchSettingItem( + icon = Icons.Filled.DeveloperMode, + title = stringResource(id = R.string.enable_web_debugging), + summary = stringResource(id = R.string.enable_web_debugging_summary), + checked = enableWebDebugging, + onCheckedChange = { + prefs.edit { putBoolean("enable_web_debugging", it) } + enableWebDebugging = it + } + ) + } + + // 更多设置 + SettingItem( + icon = Icons.Filled.Settings, + title = stringResource(id = R.string.more_settings), + summary = stringResource(id = R.string.more_settings), + onClick = { + navigator.navigate(MoreSettingsScreenDestination) + } + ) + } + } + + // 设置分组卡片 - 工具 + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha) + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Text( + text = stringResource(R.string.tools), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + + var showBottomsheet by remember { mutableStateOf(false) } + + SettingItem( + icon = Icons.Filled.BugReport, + title = stringResource(id = R.string.send_log), + onClick = { + showBottomsheet = true + } + ) + + if (showBottomsheet) { + ModalBottomSheet( + onDismissRequest = { showBottomsheet = false }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, ) { - Box { - Column( - modifier = Modifier - .padding(16.dp) - .clickable { - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm") - val current = LocalDateTime.now().format(formatter) - exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz") + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + LogActionButton( + icon = Icons.Filled.Save, + text = stringResource(R.string.save_log), + onClick = { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm") + val current = LocalDateTime.now().format(formatter) + exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz") + showBottomsheet = false + } + ) + + LogActionButton( + icon = Icons.Filled.Share, + text = stringResource(R.string.send_log), + onClick = { + scope.launch { + val bugreport = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + getBugreportFile(context) + } + } + + val uri: Uri = + FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileprovider", + bugreport + ) + + val shareIntent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + setDataAndType(uri, "application/gzip") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + context.startActivity( + Intent.createChooser( + shareIntent, + context.getString(R.string.send_log) + ) + ) + showBottomsheet = false } - ) { - Icon( - Icons.Filled.Save, - contentDescription = null, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - Text( - text = stringResource(id = R.string.save_log), - modifier = Modifier.padding(top = 16.dp), - textAlign = TextAlign.Center.also { - LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None - ) - } - - ) - } - } - Box { - Column( - modifier = Modifier - .padding(16.dp) - .clickable { - scope.launch { - val bugreport = loadingDialog.withLoading { - withContext(Dispatchers.IO) { - getBugreportFile(context) - } - } - - val uri: Uri = - FileProvider.getUriForFile( - context, - "${BuildConfig.APPLICATION_ID}.fileprovider", - bugreport - ) - - val shareIntent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, uri) - setDataAndType(uri, "application/gzip") - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - - context.startActivity( - Intent.createChooser( - shareIntent, - context.getString(R.string.send_log) - ) - ) - } - } - ) { - Icon( - Icons.Filled.Share, - contentDescription = null, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - Text( - text = stringResource(id = R.string.send_log), - modifier = Modifier.padding(top = 16.dp), - textAlign = TextAlign.Center.also { - LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None - ) - } - ) - } + } + ) } + Spacer(modifier = Modifier.height(16.dp)) } } - ) - } - val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode - if (lkmMode) { - UninstallItem(navigator) { - loadingDialog.withLoading(it) + val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode + if (lkmMode) { + UninstallItem(navigator) { + loadingDialog.withLoading(it) + } + } } } - val about = stringResource(id = R.string.about) - ListItem( - leadingContent = { - Icon( - Icons.Filled.ContactPage, - about + // 设置分组卡片 - 关于 + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha) + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Text( + text = stringResource(R.string.about), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + + SettingItem( + icon = Icons.Filled.Info, + title = stringResource(R.string.about), + onClick = { + aboutDialog.show() + } ) - }, - headlineContent = { Text(about) }, - modifier = Modifier.clickable { - aboutDialog.show() } + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Composable +fun LogActionButton( + icon: ImageVector, + text: String, + onClick: () -> Unit +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clickable(onClick = onClick) + .padding(8.dp) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(56.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer) + ) { + Icon( + imageVector = icon, + contentDescription = text, + tint = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.size(24.dp) ) } + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + } +} + +@Composable +fun SettingItem( + icon: ImageVector, + title: String, + summary: String? = null, + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) + ) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + if (summary != null) { + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + Icon( + imageVector = Icons.Filled.ChevronRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } +} + +@Composable +fun SwitchSettingItem( + icon: ImageVector, + title: String, + summary: String? = null, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onCheckedChange(!checked) } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) + ) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + if (summary != null) { + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + checkedIconColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant, + uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) } } @@ -381,16 +553,11 @@ fun UninstallItem( } } } - val uninstall = stringResource(id = R.string.settings_uninstall) - ListItem( - leadingContent = { - Icon( - Icons.Filled.Delete, - uninstall - ) - }, - headlineContent = { Text(uninstall) }, - modifier = Modifier.clickable { + + SettingItem( + icon = Icons.Filled.Delete, + title = stringResource(id = R.string.settings_uninstall), + onClick = { uninstallDialog.show() } ) @@ -436,7 +603,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { val cardColor = if (!ThemeConfig.useDynamicColor) { ThemeConfig.currentTheme.ButtonContrast } else { - MaterialTheme.colorScheme.secondaryContainer + MaterialTheme.colorScheme.surfaceContainerHigh } AlertDialog( @@ -444,29 +611,46 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { dismiss() }, title = { - Text(text = stringResource(R.string.settings_uninstall)) + Text( + text = stringResource(R.string.settings_uninstall), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface + ) }, text = { - Column { + Column( + modifier = Modifier.padding(vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { listOptions.forEachIndexed { index, option -> Row( modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) .clickable { selection = options[index] } - .padding(vertical = 8.dp) + .padding(vertical = 12.dp, horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = options[index].icon, contentDescription = null, - modifier = Modifier.padding(end = 8.dp) + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(end = 16.dp) + .size(24.dp) ) Column { - Text(text = option.titleText) + Text( + text = option.titleText, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) option.subtitleText?.let { Text( text = it, - style = MaterialTheme.typography.bodySmall, + style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } @@ -476,7 +660,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { } }, confirmButton = { - androidx.compose.material3.TextButton( + TextButton( onClick = { if (selection != UninstallType.NONE) { onSelected(selection) @@ -484,21 +668,27 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { dismiss() } ) { - Text(text = stringResource(android.R.string.ok)) + Text( + text = stringResource(android.R.string.ok), + color = MaterialTheme.colorScheme.primary + ) } }, dismissButton = { - androidx.compose.material3.TextButton( + TextButton( onClick = { dismiss() } ) { - Text(text = stringResource(android.R.string.cancel)) + Text( + text = stringResource(android.R.string.cancel), + color = MaterialTheme.colorScheme.primary + ) } }, - containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f), - shape = MaterialTheme.shapes.medium, - tonalElevation = getCardElevation() + containerColor = cardColor, + shape = MaterialTheme.shapes.extraLarge, + tonalElevation = 4.dp ) } } @@ -508,24 +698,26 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { private fun TopBar( scrollBehavior: TopAppBarScrollBehavior? = null ) { - val cardColor = MaterialTheme.colorScheme.secondaryContainer - val cardAlpha = CardConfig.cardAlpha + val systemIsDark = isSystemInDarkTheme() + val cardColor = MaterialTheme.colorScheme.surfaceVariant + val cardAlpha = if (ThemeConfig.customBackgroundUri != null) { + cardAlpha + } else { + if (systemIsDark) 0.35f else 0.80f + } + TopAppBar( - title = { Text(stringResource(R.string.settings)) }, + title = { + Text( + text = stringResource(R.string.settings), + style = MaterialTheme.typography.titleLarge + ) + }, colors = TopAppBarDefaults.topAppBarColors( containerColor = cardColor.copy(alpha = cardAlpha), scrolledContainerColor = cardColor.copy(alpha = cardAlpha) ), - windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior ) -} - -@Preview -@Composable -private fun SettingsPreview() { - SettingScreen(EmptyDestinationsNavigator) -} - - +} \ No newline at end of file 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 d9992976..a1ffdfc6 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 @@ -1,6 +1,9 @@ package com.sukisu.ultra.ui.screen +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -13,7 +16,10 @@ import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput @@ -21,6 +27,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel @@ -43,7 +50,7 @@ import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel fun SuperUserScreen(navigator: DestinationsNavigator) { val viewModel = viewModel() val scope = rememberCoroutineScope() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val listState = rememberLazyListState() val context = LocalContext.current val snackBarHostState = remember { SnackbarHostState() } @@ -80,44 +87,81 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { ) { Icon( imageVector = Icons.Filled.MoreVert, - contentDescription = stringResource(id = R.string.settings) + contentDescription = stringResource(id = R.string.settings), + tint = MaterialTheme.colorScheme.primary ) DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }) { - DropdownMenuItem(text = { - Text(stringResource(R.string.refresh)) - }, onClick = { - scope.launch { - viewModel.fetchAppList() - } - showDropdown = false - }) - DropdownMenuItem(text = { - Text( - if (viewModel.showSystemApps) { - stringResource(R.string.hide_system_apps) - } else { - stringResource(R.string.show_system_apps) + DropdownMenuItem( + text = { Text(stringResource(R.string.refresh)) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + }, + onClick = { + scope.launch { + viewModel.fetchAppList() } - ) - }, onClick = { - viewModel.showSystemApps = !viewModel.showSystemApps - showDropdown = false - }) - DropdownMenuItem(text = { - Text(stringResource(R.string.backup_allowlist)) - }, onClick = { - backupLauncher.launch(ModuleModify.createAllowlistBackupIntent()) - showDropdown = false - }) - DropdownMenuItem(text = { - Text(stringResource(R.string.restore_allowlist)) - }, onClick = { - restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent()) - showDropdown = false - }) + showDropdown = false + } + ) + DropdownMenuItem( + text = { + Text( + if (viewModel.showSystemApps) { + stringResource(R.string.hide_system_apps) + } else { + stringResource(R.string.show_system_apps) + } + ) + }, + leadingIcon = { + Icon( + imageVector = if (viewModel.showSystemApps) + Icons.Filled.VisibilityOff else Icons.Filled.Visibility, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + }, + onClick = { + viewModel.showSystemApps = !viewModel.showSystemApps + showDropdown = false + } + ) + HorizontalDivider(thickness = 0.5.dp, modifier = Modifier.padding(vertical = 4.dp)) + DropdownMenuItem( + text = { Text(stringResource(R.string.backup_allowlist)) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.Save, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + }, + onClick = { + backupLauncher.launch(ModuleModify.createAllowlistBackupIntent()) + showDropdown = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.restore_allowlist)) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.RestoreFromTrash, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + }, + onClick = { + restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent()) + showDropdown = false + } + ) } } }, @@ -128,32 +172,81 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), bottomBar = { // 批量操作按钮,直接放在底部栏 - if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface) - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceEvenly + AnimatedVisibility( + visible = viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty(), + enter = slideInVertically(initialOffsetY = { it }), + exit = slideOutVertically(targetOffsetY = { it }) + ) { + Surface( + color = MaterialTheme.colorScheme.surfaceContainerHighest, + tonalElevation = 0.dp, + shadowElevation = 0.dp ) { - Button( - onClick = { - scope.launch { - viewModel.updateBatchPermissions(true) - } - } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Text(stringResource(R.string.batch_authorization)) - } + OutlinedButton( + onClick = { + // 修改为重新赋值为空集合 + viewModel.selectedApps = emptySet() + viewModel.showBatchActions = false + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(android.R.string.cancel)) + } - Button( - onClick = { - scope.launch { - viewModel.updateBatchPermissions(false) - } + Button( + onClick = { + scope.launch { + viewModel.updateBatchPermissions(true) + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.batch_authorization)) + } + + Button( + onClick = { + scope.launch { + viewModel.updateBatchPermissions(false) + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ) + ) { + Icon( + imageVector = Icons.Filled.Block, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.batch_cancel_authorization)) } - ) { - Text(stringResource(R.string.batch_cancel_authorization)) } } } @@ -170,15 +263,23 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { state = listState, modifier = Modifier .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection) + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = PaddingValues( + top = 8.dp, + bottom = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) 88.dp else 16.dp + ) ) { - // 获取分组后的应用列表 - 修改分组逻辑,避免应用重复出现在多个分组中 + // 获取分组后的应用列表 val rootApps = viewModel.appList.filter { it.allowSu } val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile } val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile } // 显示ROOT权限应用组 if (rootApps.isNotEmpty()) { + item { + GroupHeader(title = stringResource(R.string.apps_with_root)) + } + items(rootApps, key = { "root_" + it.packageName + it.uid }) { app -> AppItem( app = app, @@ -214,6 +315,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { // 显示自定义配置应用组 if (customApps.isNotEmpty()) { + item { + GroupHeader(title = stringResource(R.string.apps_with_custom_profile)) + } + items(customApps, key = { "custom_" + it.packageName + it.uid }) { app -> AppItem( app = app, @@ -249,6 +354,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { // 显示其他应用组 if (otherApps.isNotEmpty()) { + item { + GroupHeader(title = stringResource(R.string.other_apps)) + } + items(otherApps, key = { "other_" + it.packageName + it.uid }) { app -> AppItem( app = app, @@ -281,6 +390,38 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { ) } } + + // 当没有应用显示时显示空状态 + if (viewModel.appList.isEmpty()) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .height(400.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Filled.Apps, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f), + modifier = Modifier + .size(96.dp) + .padding(bottom = 16.dp) + ) + Text( + text = stringResource(R.string.no_apps_found), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } } } } @@ -291,7 +432,7 @@ fun GroupHeader(title: String) { Box( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant) + .background(MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.7f)) .padding(horizontal = 16.dp, vertical = 8.dp) ) { Text( @@ -299,7 +440,7 @@ fun GroupHeader(title: String) { style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.primary ) ) } @@ -316,33 +457,48 @@ private fun AppItem( onLongClick: () -> Unit, viewModel: SuperUserViewModel ) { - ListItem( + val cardColor = if (app.allowSu) + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + else if (app.hasCustomProfile) + MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f) + else + MaterialTheme.colorScheme.surfaceContainerLow + + Card( + colors = CardDefaults.cardColors(containerColor = cardColor), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .clip(MaterialTheme.shapes.medium) + .shadow( + elevation = 0.dp, + shape = MaterialTheme.shapes.medium, + spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) + .then( + if (isSelected) + Modifier.border( + width = 2.dp, + color = MaterialTheme.colorScheme.primary, + shape = MaterialTheme.shapes.medium + ) + else + Modifier + ) .pointerInput(Unit) { detectTapGestures( onLongPress = { onLongClick() }, onTap = { onClick() } ) - }, - headlineContent = { Text(app.label) }, - supportingContent = { - Column { - Text(app.packageName) - FlowRow { - if (app.allowSu) { - LabelText(label = "ROOT") - } else { - if (Natives.uidShouldUmount(app.uid)) { - LabelText(label = "UMOUNT") - } - } - if (app.hasCustomProfile) { - LabelText(label = "CUSTOM") - } - } } - }, - leadingContent = { + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(app.packageInfo) @@ -350,43 +506,93 @@ private fun AppItem( .build(), contentDescription = app.label, modifier = Modifier - .padding(4.dp) - .width(48.dp) - .height(48.dp) + .padding(end = 16.dp) + .size(48.dp) + .clip(MaterialTheme.shapes.small) ) - }, - trailingContent = { + + Column( + modifier = Modifier + .weight(1f) + .padding(end = 8.dp) + ) { + Text( + text = app.label, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis + ) + + Text( + text = app.packageName, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis + ) + + FlowRow( + modifier = Modifier.padding(top = 4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (app.allowSu) { + LabelText(label = "ROOT", backgroundColor = MaterialTheme.colorScheme.primary) + } + if (Natives.uidShouldUmount(app.uid)) { + LabelText(label = "UMOUNT", backgroundColor = MaterialTheme.colorScheme.tertiary) + } + if (app.hasCustomProfile) { + LabelText(label = "CUSTOM", backgroundColor = MaterialTheme.colorScheme.secondary) + } + } + } + if (!viewModel.showBatchActions) { Switch( checked = app.allowSu, - onCheckedChange = onSwitchChange + onCheckedChange = onSwitchChange, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + checkedIconColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant, + uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant + ) ) } else { Checkbox( checked = isSelected, - onCheckedChange = { onToggleSelection() } + onCheckedChange = { onToggleSelection() }, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + uncheckedColor = MaterialTheme.colorScheme.outline + ) ) } } - ) + } } @Composable -fun LabelText(label: String) { +fun LabelText(label: String, backgroundColor: Color) { Box( modifier = Modifier - .padding(top = 4.dp, end = 4.dp) + .padding(top = 2.dp, end = 2.dp) .background( - Color.Black, + backgroundColor, shape = RoundedCornerShape(4.dp) ) + .clip(RoundedCornerShape(4.dp)) ) { Text( text = label, - modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp), + modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp), style = TextStyle( - fontSize = 8.sp, + fontSize = 10.sp, color = Color.White, + fontWeight = FontWeight.Medium ) ) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Template.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Template.kt index fe017583..a2dc3513 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Template.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Template.kt @@ -205,17 +205,17 @@ private fun TemplateItem( ) Text(template.description) FlowRow { - LabelText(label = "UID: ${template.uid}") - LabelText(label = "GID: ${template.gid}") - LabelText(label = template.context) + LabelText(label = "UID: ${template.uid}", backgroundColor = MaterialTheme.colorScheme.surface) + LabelText(label = "GID: ${template.gid}", backgroundColor = MaterialTheme.colorScheme.surface) + LabelText(label = template.context, backgroundColor = MaterialTheme.colorScheme.surface) if (template.local) { - LabelText(label = "local") + LabelText(label = "local", backgroundColor = MaterialTheme.colorScheme.surface) } else { - LabelText(label = "remote") + LabelText(label = "remote", backgroundColor = MaterialTheme.colorScheme.surface) } } } - }, + } ) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt index a011e407..57d12f35 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt @@ -13,9 +13,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp object CardConfig { - val defaultElevation: Dp = 0.dp + val defaultElevation: Dp = 1.dp - var cardAlpha by mutableStateOf(0.45f) + var cardAlpha by mutableStateOf(0.80f) var cardElevation by mutableStateOf(defaultElevation) var isShadowEnabled by mutableStateOf(true) var isCustomAlphaSet by mutableStateOf(false) @@ -23,66 +23,90 @@ object CardConfig { var isUserLightModeEnabled by mutableStateOf(false) var isCustomBackgroundEnabled by mutableStateOf(false) + private var lastSystemDarkMode: Boolean? = null + + /** + * 保存卡片配置到SharedPreferences + */ fun save(context: Context) { val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) prefs.edit().apply { putFloat("card_alpha", cardAlpha) - putBoolean("custom_background_enabled", cardElevation == 0.dp) + putBoolean("custom_background_enabled", isCustomBackgroundEnabled) + putBoolean("is_shadow_enabled", isShadowEnabled) putBoolean("is_custom_alpha_set", isCustomAlphaSet) putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled) putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled) - putBoolean("is_custom_background_enabled", isCustomBackgroundEnabled) apply() } } + /** + * 从SharedPreferences加载卡片配置 + */ fun load(context: Context) { val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - cardAlpha = prefs.getFloat("card_alpha", 0.45f) - cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation + cardAlpha = prefs.getFloat("card_alpha", 0.80f) + isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false) + isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true) + cardElevation = if (isShadowEnabled) defaultElevation else 0.dp isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false) isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false) isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false) - isCustomBackgroundEnabled = prefs.getBoolean("is_custom_background_enabled", false) } + /** + * 更新阴影启用状态 + */ fun updateShadowEnabled(enabled: Boolean) { isShadowEnabled = enabled cardElevation = if (enabled) defaultElevation else 0.dp } + /** + * 设置深色模式默认值 + */ fun setDarkModeDefaults() { if (!isCustomAlphaSet) { - cardAlpha = 0.35f - cardElevation = 0.dp + cardAlpha = 0.50f + } + if (!isShadowEnabled) { + cardElevation = 0.dp } } } - +/** + * 获取卡片颜色配置 + */ @Composable -fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors( +fun getCardColors(originalColor: Color) = CardDefaults.cardColors( containerColor = originalColor.copy(alpha = CardConfig.cardAlpha), - contentColor = when { - CardConfig.isUserLightModeEnabled -> { - Color.Black - } - CardConfig.isUserDarkModeEnabled -> { - Color.White - } - !isSystemInDarkTheme() && !CardConfig.isUserDarkModeEnabled -> { - Color.Black - } - !isSystemInDarkTheme() && !CardConfig.isCustomBackgroundEnabled && !CardConfig.isUserDarkModeEnabled && originalColor.luminance() > 0.3 -> { - Color.Black - } - isSystemInDarkTheme() && !CardConfig.isUserDarkModeEnabled && !CardConfig.isUserLightModeEnabled-> { - Color.White - } - else -> { - Color.White - } - } + contentColor = determineContentColor(originalColor) ) -fun getCardElevation() = CardConfig.cardElevation \ No newline at end of file +/** + * 根据背景颜色、主题模式和用户设置确定内容颜色 + */ +@Composable +private fun determineContentColor(originalColor: Color): Color { + val isDarkTheme = isSystemInDarkTheme() + + // 处理主题切换过程中的颜色 + if (ThemeConfig.isThemeChanging) { + return if (isDarkTheme) Color.White else Color.Black + } + + return when { + // 用户明确设置了浅色或深色模式 + CardConfig.isUserLightModeEnabled -> Color.Black + CardConfig.isUserDarkModeEnabled -> Color.White + + // 根据系统主题和背景亮度自动确定 + !isDarkTheme && originalColor.luminance() > 0.5f -> Color.Black + isDarkTheme -> Color.White + + // 其他情况根据背景亮度确定 + else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Color.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Color.kt index f9d990dd..50d2b42b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Color.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Color.kt @@ -17,142 +17,278 @@ sealed class ThemeColors { abstract val OnTertiaryContainer: Color abstract val ButtonContrast: Color - open fun getCustomSliderActiveColor(): Color = Primary - open fun getCustomSliderInactiveColor(): Color = PrimaryContainer + // 表面颜色 + abstract val Surface: Color + abstract val SurfaceVariant: Color + abstract val OnSurface: Color + abstract val OnSurfaceVariant: Color - // Default Theme (white) + // 错误状态颜色 + abstract val Error: Color + abstract val OnError: Color + abstract val ErrorContainer: Color + abstract val OnErrorContainer: Color + + // 边框和背景色 + abstract val Outline: Color + abstract val OutlineVariant: Color + abstract val Background: Color + abstract val OnBackground: Color + + // 默认主题 (白色) object Default : ThemeColors() { override val Primary = Color(0xFFFFFFFF) - override val Secondary = Color(0xFFF5F5F5) - override val Tertiary = Color(0xFFE0E0E0) - override val OnPrimary = Color(0xFF616161) - override val OnSecondary = Color(0xFF616161) - override val OnTertiary = Color(0xFF616161) - override val PrimaryContainer = Color(0xFFF5F5F5) - override val SecondaryContainer = Color(0xFFEEEEEE) - override val TertiaryContainer = Color(0xFFE0E0E0) - override val OnPrimaryContainer = Color(0xFF000000) - override val OnSecondaryContainer = Color(0xFF000000) - override val OnTertiaryContainer = Color(0xFF000000) - override val ButtonContrast = Color(0xFF00BFFF) + override val Secondary = Color(0xFF5F6368) + override val Tertiary = Color(0xFFFFFFFF) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFD1E3FF) + override val SecondaryContainer = Color(0xFFE4E6E8) + override val TertiaryContainer = Color(0xFFD0E1FC) + override val OnPrimaryContainer = Color(0xFF0D2E5E) + override val OnSecondaryContainer = Color(0xFF24262A) + override val OnTertiaryContainer = Color(0xFF0A2E62) + override val ButtonContrast = Color(0xFF0A2E62) + + override val Surface = Color(0xFFFCFCFC) + override val SurfaceVariant = Color(0xFFF2F2F2) + override val OnSurface = Color(0xFF202124) + override val OnSurfaceVariant = Color(0xFF5F6368) + + override val Error = Color(0xFFD93025) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFDECEA) + override val OnErrorContainer = Color(0xFF58160F) + + override val Outline = Color(0xFFDADADA) + override val OutlineVariant = Color(0xFFEEEEEE) + override val Background = Color(0xFFFFFFFF) + override val OnBackground = Color(0xFF202124) } - // Blue Theme + // 蓝色主题 object Blue : ThemeColors() { override val Primary = Color(0xFF2196F3) - override val Secondary = Color(0xFF1E88E5) + override val Secondary = Color(0xFF64B5F6) override val Tertiary = Color(0xFF0D47A1) override val OnPrimary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFCBE6FC) - override val SecondaryContainer = Color(0xFFBBDEFB) - override val TertiaryContainer = Color(0xFF90CAF9) - override val OnPrimaryContainer = Color(0xFF0A1A2E) - override val OnSecondaryContainer = Color(0xFF0A192D) - override val OnTertiaryContainer = Color(0xFF071B3D) - override val ButtonContrast = Color(0xFF00BFFF) + override val PrimaryContainer = Color(0xFFD6EAFF) + override val SecondaryContainer = Color(0xFFE3F2FD) + override val TertiaryContainer = Color(0xFFCFD8DC) + override val OnPrimaryContainer = Color(0xFF0A3049) + override val OnSecondaryContainer = Color(0xFF0D3C61) + override val OnTertiaryContainer = Color(0xFF071D41) + override val ButtonContrast = Color(0xFF1976D2) + + override val Surface = Color(0xFFF5F9FF) + override val SurfaceVariant = Color(0xFFEDF5FE) + override val OnSurface = Color(0xFF1A1C1E) + override val OnSurfaceVariant = Color(0xFF42474E) + + override val Error = Color(0xFFB00020) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFDE7E9) + override val OnErrorContainer = Color(0xFF410008) + + override val Outline = Color(0xFFBAC3CF) + override val OutlineVariant = Color(0xFFDFE3EB) + override val Background = Color(0xFFFAFCFF) + override val OnBackground = Color(0xFF1A1C1E) } - // Green Theme + // 绿色主题 object Green : ThemeColors() { - override val Primary = Color(0xFF4CAF50) - override val Secondary = Color(0xFF43A047) + override val Primary = Color(0xFF43A047) + override val Secondary = Color(0xFF66BB6A) override val Tertiary = Color(0xFF1B5E20) override val OnPrimary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFC8E6C9) - override val SecondaryContainer = Color(0xFFA5D6A7) - override val TertiaryContainer = Color(0xFF81C784) - override val OnPrimaryContainer = Color(0xFF0A1F0B) - override val OnSecondaryContainer = Color(0xFF0A1D0B) - override val OnTertiaryContainer = Color(0xFF071F09) - override val ButtonContrast = Color(0xFF32CD32) + override val PrimaryContainer = Color(0xFFD8EFDB) + override val SecondaryContainer = Color(0xFFE8F5E9) + override val TertiaryContainer = Color(0xFFB9F6CA) + override val OnPrimaryContainer = Color(0xFF0A280D) + override val OnSecondaryContainer = Color(0xFF0E2912) + override val OnTertiaryContainer = Color(0xFF051B07) + override val ButtonContrast = Color(0xFF2E7D32) + + override val Surface = Color(0xFFF6FBF6) + override val SurfaceVariant = Color(0xFFEDF7EE) + override val OnSurface = Color(0xFF191C19) + override val OnSurfaceVariant = Color(0xFF414941) + + override val Error = Color(0xFFC62828) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFF8D7DA) + override val OnErrorContainer = Color(0xFF4A0808) + + override val Outline = Color(0xFFBDC9BF) + override val OutlineVariant = Color(0xFFDDE6DE) + override val Background = Color(0xFFFBFDFB) + override val OnBackground = Color(0xFF191C19) } - // Purple Theme + // 紫色主题 object Purple : ThemeColors() { override val Primary = Color(0xFF9C27B0) - override val Secondary = Color(0xFF8E24AA) - override val Tertiary = Color(0xFF4A148C) + override val Secondary = Color(0xFFBA68C8) + override val Tertiary = Color(0xFF6A1B9A) override val OnPrimary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFE1BEE7) - override val SecondaryContainer = Color(0xFFCE93D8) - override val TertiaryContainer = Color(0xFFB39DDB) - override val OnPrimaryContainer = Color(0xFF1F0A23) - override val OnSecondaryContainer = Color(0xFF1C0A21) - override val OnTertiaryContainer = Color(0xFF12071C) - override val ButtonContrast = Color(0xFFDA70D6) + override val PrimaryContainer = Color(0xFFF3D8F8) + override val SecondaryContainer = Color(0xFFF5E9F7) + override val TertiaryContainer = Color(0xFFE1BEE7) + override val OnPrimaryContainer = Color(0xFF2A0934) + override val OnSecondaryContainer = Color(0xFF3C0F50) + override val OnTertiaryContainer = Color(0xFF1D0830) + override val ButtonContrast = Color(0xFF8E24AA) + + override val Surface = Color(0xFFFCF6FF) + override val SurfaceVariant = Color(0xFFF5EEFA) + override val OnSurface = Color(0xFF1D1B1E) + override val OnSurfaceVariant = Color(0xFF49454E) + + override val Error = Color(0xFFD50000) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFFDCD5) + override val OnErrorContainer = Color(0xFF480000) + + override val Outline = Color(0xFFC9B9D0) + override val OutlineVariant = Color(0xFFE8DAED) + override val Background = Color(0xFFFFFBFF) + override val OnBackground = Color(0xFF1D1B1E) } - // Orange Theme + // 橙色主题 object Orange : ThemeColors() { override val Primary = Color(0xFFFF9800) - override val Secondary = Color(0xFFFB8C00) + override val Secondary = Color(0xFFFFB74D) override val Tertiary = Color(0xFFE65100) override val OnPrimary = Color(0xFFFFFFFF) - override val OnSecondary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFF000000) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFFFE0B2) - override val SecondaryContainer = Color(0xFFFFCC80) - override val TertiaryContainer = Color(0xFFFFB74D) - override val OnPrimaryContainer = Color(0xFF1A1100) - override val OnSecondaryContainer = Color(0xFF1A1000) - override val OnTertiaryContainer = Color(0xFF1A0B00) - override val ButtonContrast = Color(0xFFFF6347) + override val PrimaryContainer = Color(0xFFFFECCC) + override val SecondaryContainer = Color(0xFFFFF0D9) + override val TertiaryContainer = Color(0xFFFFD180) + override val OnPrimaryContainer = Color(0xFF351F00) + override val OnSecondaryContainer = Color(0xFF3D2800) + override val OnTertiaryContainer = Color(0xFF2E1500) + override val ButtonContrast = Color(0xFFEF6C00) + + override val Surface = Color(0xFFFFF8F3) + override val SurfaceVariant = Color(0xFFFFF0E6) + override val OnSurface = Color(0xFF1F1B16) + override val OnSurfaceVariant = Color(0xFF4E4639) + + override val Error = Color(0xFFD32F2F) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFFDBC8) + override val OnErrorContainer = Color(0xFF490700) + + override val Outline = Color(0xFFD6C3AD) + override val OutlineVariant = Color(0xFFEFDFCC) + override val Background = Color(0xFFFFFBFF) + override val OnBackground = Color(0xFF1F1B16) } - // Pink Theme + // 粉色主题 object Pink : ThemeColors() { override val Primary = Color(0xFFE91E63) - override val Secondary = Color(0xFFD81B60) + override val Secondary = Color(0xFFF06292) override val Tertiary = Color(0xFF880E4F) override val OnPrimary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFF8BBD0) - override val SecondaryContainer = Color(0xFFF48FB1) - override val TertiaryContainer = Color(0xFFE91E63) - override val OnPrimaryContainer = Color(0xFF2E0A14) - override val OnSecondaryContainer = Color(0xFF2B0A13) - override val OnTertiaryContainer = Color(0xFF1C0311) - override val ButtonContrast = Color(0xFFFF1493) + override val PrimaryContainer = Color(0xFFFCE4EC) + override val SecondaryContainer = Color(0xFFFCE4EC) + override val TertiaryContainer = Color(0xFFF8BBD0) + override val OnPrimaryContainer = Color(0xFF3B0819) + override val OnSecondaryContainer = Color(0xFF3B0819) + override val OnTertiaryContainer = Color(0xFF2B0516) + override val ButtonContrast = Color(0xFFD81B60) + + override val Surface = Color(0xFFFFF7F9) + override val SurfaceVariant = Color(0xFFFCEEF2) + override val OnSurface = Color(0xFF201A1C) + override val OnSurfaceVariant = Color(0xFF534347) + + override val Error = Color(0xFFB71C1C) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFFDAD6) + override val OnErrorContainer = Color(0xFF410002) + + override val Outline = Color(0xFFD6BABF) + override val OutlineVariant = Color(0xFFEFDDE0) + override val Background = Color(0xFFFFFBFF) + override val OnBackground = Color(0xFF201A1C) } - // Gray Theme + // 灰色主题 object Gray : ThemeColors() { - override val Primary = Color(0xFF9E9E9E) - override val Secondary = Color(0xFF757575) - override val Tertiary = Color(0xFF616161) + override val Primary = Color(0xFF607D8B) + override val Secondary = Color(0xFF90A4AE) + override val Tertiary = Color(0xFF455A64) override val OnPrimary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFEEEEEE) - override val SecondaryContainer = Color(0xFFE0E0E0) - override val TertiaryContainer = Color(0xFFBDBDBD) - override val OnPrimaryContainer = Color(0xFF1A1A1A) - override val OnSecondaryContainer = Color(0xFF171717) - override val OnTertiaryContainer = Color(0xFF141414) - override val ButtonContrast = Color(0xFF696969) + override val PrimaryContainer = Color(0xFFECEFF1) + override val SecondaryContainer = Color(0xFFECEFF1) + override val TertiaryContainer = Color(0xFFCFD8DC) + override val OnPrimaryContainer = Color(0xFF1A2327) + override val OnSecondaryContainer = Color(0xFF1A2327) + override val OnTertiaryContainer = Color(0xFF121A1D) + override val ButtonContrast = Color(0xFF546E7A) + + override val Surface = Color(0xFFF6F9FB) + override val SurfaceVariant = Color(0xFFEEF2F4) + override val OnSurface = Color(0xFF191C1E) + override val OnSurfaceVariant = Color(0xFF41484D) + + override val Error = Color(0xFFC62828) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFFDAD6) + override val OnErrorContainer = Color(0xFF410002) + + override val Outline = Color(0xFFBDC1C4) + override val OutlineVariant = Color(0xFFDDE1E3) + override val Background = Color(0xFFFBFCFE) + override val OnBackground = Color(0xFF191C1E) } + // 黄色主题 object Yellow : ThemeColors() { - override val Primary = Color(0xFFFFD700) - override val Secondary = Color(0xFFFFBC52) - override val Tertiary = Color(0xFF795548) - override val OnPrimary = Color(0xFFFFFFFF) - override val OnSecondary = Color(0xFFFFFFFF) + override val Primary = Color(0xFFFFC107) + override val Secondary = Color(0xFFFFD54F) + override val Tertiary = Color(0xFFFF8F00) + override val OnPrimary = Color(0xFF000000) + override val OnSecondary = Color(0xFF000000) override val OnTertiary = Color(0xFFFFFFFF) - override val PrimaryContainer = Color(0xFFFFF7D6) - override val SecondaryContainer = Color(0xFFFFE6B3) - override val TertiaryContainer = Color(0xFFD7CCC8) - override val OnPrimaryContainer = Color(0xFF1A1600) - override val OnSecondaryContainer = Color(0xFF1A1100) - override val OnTertiaryContainer = Color(0xFF1A1717) - override val ButtonContrast = Color(0xFFFFD700) + override val PrimaryContainer = Color(0xFFFFF8E1) + override val SecondaryContainer = Color(0xFFFFF8E1) + override val TertiaryContainer = Color(0xFFFFECB3) + override val OnPrimaryContainer = Color(0xFF332A00) + override val OnSecondaryContainer = Color(0xFF332A00) + override val OnTertiaryContainer = Color(0xFF221200) + override val ButtonContrast = Color(0xFFFFB300) + + override val Surface = Color(0xFFFFFAF3) + override val SurfaceVariant = Color(0xFFFFF7E6) + override val OnSurface = Color(0xFF1F1C17) + override val OnSurfaceVariant = Color(0xFF4E4A3C) + + override val Error = Color(0xFFB71C1C) + override val OnError = Color(0xFFFFFFFF) + override val ErrorContainer = Color(0xFFFFDAD6) + override val OnErrorContainer = Color(0xFF410002) + + override val Outline = Color(0xFFD1C8AF) + override val OutlineVariant = Color(0xFFEEE8D7) + override val Background = Color(0xFFFFFCF8) + override val OnBackground = Color(0xFF1F1C17) } companion object { @@ -163,7 +299,7 @@ sealed class ThemeColors { "orange" -> Orange "pink" -> Pink "gray" -> Gray - "white" -> Yellow + "yellow" -> Yellow else -> Default } } 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 61fe77da..06b70802 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 @@ -5,6 +5,10 @@ import android.content.Context import android.net.Uri import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -14,19 +18,25 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.paint import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.zIndex +import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter import androidx.compose.foundation.background -import androidx.compose.ui.graphics.luminance +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import java.io.File import java.io.FileOutputStream @@ -36,39 +46,280 @@ import androidx.core.net.toUri import com.sukisu.ultra.ui.util.BackgroundTransformation import com.sukisu.ultra.ui.util.saveTransformedBackground +/** + * 主题配置对象,管理应用的主题相关状态 + */ object ThemeConfig { var customBackgroundUri by mutableStateOf(null) var forceDarkMode by mutableStateOf(null) var currentTheme by mutableStateOf(ThemeColors.Default) var useDynamicColor by mutableStateOf(false) + var backgroundImageLoaded by mutableStateOf(false) + var needsResetOnThemeChange by mutableStateOf(false) + var isThemeChanging by mutableStateOf(false) + private var lastDarkModeState: Boolean? = null + fun detectThemeChange(currentDarkMode: Boolean): Boolean { + val isChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode + lastDarkModeState = currentDarkMode + return isChanged + } + + fun resetBackgroundState() { + backgroundImageLoaded = false + isThemeChanging = true + } } +/** + * 应用主题 + */ @Composable -private fun getDarkColorScheme() = darkColorScheme( +fun KernelSUTheme( + darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { + true -> true + false -> false + null -> isSystemInDarkTheme() + }, + dynamicColor: Boolean = ThemeConfig.useDynamicColor, + content: @Composable () -> Unit +) { + val context = LocalContext.current + val systemIsDark = isSystemInDarkTheme() + + // 检测系统主题变化并保存状态 + val themeChanged = ThemeConfig.detectThemeChange(systemIsDark) + LaunchedEffect(systemIsDark, themeChanged) { + if (ThemeConfig.forceDarkMode == null && themeChanged) { + Log.d("ThemeSystem", "系统主题变化检测: 从 ${!systemIsDark} 变为 $systemIsDark") + ThemeConfig.resetBackgroundState() + + // 强制重新加载自定义背景 + context.loadCustomBackground() + + // 调整卡片样式以适应新主题 + CardConfig.apply { + load(context) + if (!isCustomAlphaSet) { + cardAlpha = if (systemIsDark) 0.35f else 0.80f + } + save(context) + } + } + } + + // 初始加载配置 + LaunchedEffect(Unit) { + context.loadCustomBackground() + context.loadThemeColors() + context.loadDynamicColorState() + context.loadThemeMode() + CardConfig.load(context) + + // 立即将加载状态设为false,确保首次会触发加载动画 + ThemeConfig.backgroundImageLoaded = false + } + + // 创建颜色方案 + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context) + } + darkTheme -> createDarkColorScheme() + else -> createLightColorScheme() + } + + // 根据暗色模式和自定义背景调整卡片配置 + val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null + if (darkTheme && !dynamicColor) { + CardConfig.setDarkModeDefaults() + } + CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground) + + // 使用rememberSaveable保留背景URI状态,防止在主题切换时丢失 + val backgroundUri = rememberSaveable { mutableStateOf(ThemeConfig.customBackgroundUri) } + + // 确保状态同步 + LaunchedEffect(ThemeConfig.customBackgroundUri) { + backgroundUri.value = ThemeConfig.customBackgroundUri + } + + // 背景图加载器 - 使用保存的URI状态 + val bgImagePainter = backgroundUri.value?.let { + rememberAsyncImagePainter( + model = it, + onError = { + Log.e("ThemeSystem", "背景图加载失败: ${it.result.throwable.message}") + ThemeConfig.customBackgroundUri = null + context.saveCustomBackground(null) + }, + onSuccess = { + Log.d("ThemeSystem", "背景图加载成功") + ThemeConfig.backgroundImageLoaded = true + ThemeConfig.isThemeChanging = false + } + ) + } + + // 背景透明度动画 - 使用更强健的动画配置 + val transition = updateTransition( + targetState = ThemeConfig.backgroundImageLoaded, + label = "bgTransition" + ) + val bgAlpha by transition.animateFloat( + label = "bgAlpha", + transitionSpec = { + spring( + dampingRatio = 0.8f, + stiffness = 300f + ) + } + ) { loaded -> if (loaded) 1f else 0f } + + // 清理函数,确保主题切换完成后重置状态 + DisposableEffect(systemIsDark) { + onDispose { + if (ThemeConfig.isThemeChanging) { + ThemeConfig.isThemeChanging = false + } + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography + ) { + Box(modifier = Modifier.fillMaxSize()) { + // 底色层 - 确保有底色 + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(-2f) + .background(if (darkTheme) Color.Black else Color.White) + ) + + // 自定义背景层 + backgroundUri.value?.let { uri -> + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(-1f) + .alpha(bgAlpha) + ) { + // 背景图片 + bgImagePainter?.let { painter -> + Box( + modifier = Modifier + .fillMaxSize() + .paint( + painter = painter, + contentScale = ContentScale.Crop + ) + .graphicsLayer { + alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f + } + ) + } + + // 亮度调节层 + Box( + modifier = Modifier + .fillMaxSize() + .background( + if (darkTheme) Color.Black.copy(alpha = 0.6f) + else Color.White.copy(alpha = 0.1f) + ) + ) + + // 边缘渐变遮罩 + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf( + Color.Transparent, + if (darkTheme) Color.Black.copy(alpha = 0.5f) + else Color.Black.copy(alpha = 0.2f) + ), + radius = 1200f + ) + ) + ) + } + } + + // 内容层 + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(1f) + ) { + content() + } + } + } +} + +/** + * 创建动态深色颜色方案 + */ +@RequiresApi(Build.VERSION_CODES.S) +@Composable +private fun createDynamicDarkColorScheme(context: Context) = + dynamicDarkColorScheme(context).copy( + background = Color.Transparent, + surface = Color.Transparent, + onBackground = Color.White, + onSurface = Color.White + ) + +/** + * 创建动态浅色颜色方案 + */ +@RequiresApi(Build.VERSION_CODES.S) +@Composable +private fun createDynamicLightColorScheme(context: Context) = + dynamicLightColorScheme(context).copy( + background = Color.Transparent, + surface = Color.Transparent + ) + +/** + * 创建深色颜色方案 + */ +@Composable +private fun createDarkColorScheme() = darkColorScheme( primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f), - onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f), + onPrimary = Color.White, primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f), - onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f), + onPrimaryContainer = Color.White, secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f), - onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f), + onSecondary = Color.White, secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f), - onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f), + onSecondaryContainer = Color.White, tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f), - onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f), + onTertiary = Color.White, tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f), - onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f), + onTertiaryContainer = Color.White, background = Color.Transparent, surface = Color.Transparent, - onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f), - onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f), + onBackground = Color.White, + onSurface = Color.White, surfaceVariant = Color(0xFF2F2F2F), - onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f), + onSurfaceVariant = Color.White.copy(alpha = 0.7f), outline = Color.White.copy(alpha = 0.12f), - outlineVariant = Color.White.copy(alpha = 0.12f) + outlineVariant = Color.White.copy(alpha = 0.12f), + error = ThemeConfig.currentTheme.Error, + onError = ThemeConfig.currentTheme.OnError, + errorContainer = ThemeConfig.currentTheme.ErrorContainer.copy(alpha = 0.15f), + onErrorContainer = Color.White ) +/** + * 创建浅色颜色方案 + */ @Composable -private fun getLightColorScheme() = lightColorScheme( +private fun createLightColorScheme() = lightColorScheme( primary = ThemeConfig.currentTheme.Primary, onPrimary = ThemeConfig.currentTheme.OnPrimary, primaryContainer = ThemeConfig.currentTheme.PrimaryContainer, @@ -88,163 +339,44 @@ private fun getLightColorScheme() = lightColorScheme( surfaceVariant = Color(0xFFF5F5F5), onSurfaceVariant = Color.Black.copy(alpha = 0.78f), outline = Color.Black.copy(alpha = 0.12f), - outlineVariant = Color.Black.copy(alpha = 0.12f) + outlineVariant = Color.Black.copy(alpha = 0.12f), + error = ThemeConfig.currentTheme.Error, + onError = ThemeConfig.currentTheme.OnError, + errorContainer = ThemeConfig.currentTheme.ErrorContainer, + onErrorContainer = ThemeConfig.currentTheme.OnErrorContainer ) -@Composable -fun KernelSUTheme( - darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { - true -> true - false -> false - null -> isSystemInDarkTheme() - }, - dynamicColor: Boolean = ThemeConfig.useDynamicColor, - content: @Composable () -> Unit -) { - val context = LocalContext.current - context.loadCustomBackground() - context.loadThemeColors() - context.loadDynamicColorState() - - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - if (darkTheme) { - val originalScheme = dynamicDarkColorScheme(context) - originalScheme.copy( - primary = adjustColor(originalScheme.primary), - onPrimary = adjustColor(originalScheme.onPrimary), - primaryContainer = adjustColor(originalScheme.primaryContainer), - onPrimaryContainer = adjustColor(originalScheme.onPrimaryContainer), - background = Color.Transparent, - surface = Color.Transparent, - onBackground = Color.White, - onSurface = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onSecondaryContainer = Color.White, - onTertiaryContainer = Color.White - ) - } else { - val originalScheme = dynamicLightColorScheme(context) - originalScheme.copy( - primary = adjustColor(originalScheme.primary), - onPrimary = adjustColor(originalScheme.onPrimary), - primaryContainer = adjustColor(originalScheme.primaryContainer), - onPrimaryContainer = adjustColor(originalScheme.onPrimaryContainer), - background = Color.Transparent, - surface = Color.Transparent - ) - } - } - darkTheme -> getDarkColorScheme() - else -> getLightColorScheme() - } - - val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null - - if (darkTheme && !dynamicColor) { - CardConfig.setDarkModeDefaults() - } - - CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground) - - MaterialTheme( - colorScheme = colorScheme, - typography = Typography - ) { - Box(modifier = Modifier.fillMaxSize()) { - // 背景图层 - ThemeConfig.customBackgroundUri?.let { uri -> - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(-1f) - ) { - // 背景图片 - Box( - modifier = Modifier - .fillMaxSize() - .paint( - painter = rememberAsyncImagePainter( - model = uri, - onError = { - ThemeConfig.customBackgroundUri = null - context.saveCustomBackground(null) - } - ), - contentScale = ContentScale.Crop - ) - ) - - // 亮度调节层 - Box( - modifier = Modifier - .fillMaxSize() - .background( - if (darkTheme) { - Color.Black.copy(alpha = 0.4f) - } else { - Color.White.copy(alpha = 0.1f) - } - ) - ) - - // 边缘渐变遮罩层 - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf( - Color.Transparent, - if (darkTheme) { - Color.Black.copy(alpha = 0.5f) - } else { - Color.Black.copy(alpha = 0.2f) - } - ), - radius = 1200f - ) - ) - ) - } - } - // 内容图层 - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(1f) - ) { - content() - } - } - } -} - -// 复制图片到应用内部存储 +/** + * 复制图片到应用内部存储 + */ private fun Context.copyImageToInternalStorage(uri: Uri): Uri? { - try { + return try { val contentResolver: ContentResolver = contentResolver - val inputStream: InputStream = contentResolver.openInputStream(uri)!! + val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null val fileName = "custom_background.jpg" val file = File(filesDir, fileName) val outputStream = FileOutputStream(file) val buffer = ByteArray(4 * 1024) var read: Int + while (inputStream.read(buffer).also { read = it } != -1) { outputStream.write(buffer, 0, read) } + outputStream.flush() outputStream.close() inputStream.close() - return Uri.fromFile(file) + + Uri.fromFile(file) } catch (e: Exception) { - Log.e("ImageCopy", "Failed to copy image: ${e.message}") - return null + Log.e("ImageCopy", "复制图片失败: ${e.message}") + null } } -// 保存变换后的背景图片到应用内部存储并更新配置 +/** + * 保存并应用自定义背景 + */ fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) { val finalUri = if (transformation != null) { saveTransformedBackground(uri, transformation) @@ -258,30 +390,49 @@ fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTra } ThemeConfig.customBackgroundUri = finalUri + ThemeConfig.backgroundImageLoaded = false CardConfig.cardElevation = 0.dp CardConfig.isCustomBackgroundEnabled = true } -// 保存背景图片到应用内部存储并更新配置 +/** + * 保存自定义背景 + */ fun Context.saveCustomBackground(uri: Uri?) { val newUri = uri?.let { copyImageToInternalStorage(it) } getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .edit { putString("custom_background", newUri?.toString()) } + ThemeConfig.customBackgroundUri = newUri + ThemeConfig.backgroundImageLoaded = false + if (uri != null) { CardConfig.cardElevation = 0.dp CardConfig.isCustomBackgroundEnabled = true } } +/** + * 加载自定义背景 + */ fun Context.loadCustomBackground() { val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .getString("custom_background", null) - ThemeConfig.customBackgroundUri = uriString?.toUri() + + // 判断是否有实际变化,避免无谓的重新加载 + val newUri = uriString?.toUri() + if (ThemeConfig.customBackgroundUri?.toString() != newUri?.toString()) { + Log.d("ThemeSystem", "加载自定义背景: $uriString") + ThemeConfig.customBackgroundUri = newUri + ThemeConfig.backgroundImageLoaded = false + } } +/** + * 保存主题模式 + */ fun Context.saveThemeMode(forceDark: Boolean?) { getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .edit { @@ -294,52 +445,49 @@ fun Context.saveThemeMode(forceDark: Boolean?) { ) } ThemeConfig.forceDarkMode = forceDark + ThemeConfig.needsResetOnThemeChange = forceDark == null } +/** + * 加载主题模式 + */ fun Context.loadThemeMode() { val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .getString("theme_mode", "system") + ThemeConfig.forceDarkMode = when(mode) { "dark" -> true "light" -> false else -> null } + ThemeConfig.needsResetOnThemeChange = ThemeConfig.forceDarkMode == null } +/** + * 保存主题颜色 + */ fun Context.saveThemeColors(themeName: String) { getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .edit { putString("theme_colors", themeName) } - ThemeConfig.currentTheme = when(themeName) { - "blue" -> ThemeColors.Blue - "green" -> ThemeColors.Green - "purple" -> ThemeColors.Purple - "orange" -> ThemeColors.Orange - "pink" -> ThemeColors.Pink - "gray" -> ThemeColors.Gray - "yellow" -> ThemeColors.Yellow - else -> ThemeColors.Default - } + ThemeConfig.currentTheme = ThemeColors.fromName(themeName) } +/** + * 加载主题颜色 + */ fun Context.loadThemeColors() { val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .getString("theme_colors", "default") - ThemeConfig.currentTheme = when(themeName) { - "blue" -> ThemeColors.Blue - "green" -> ThemeColors.Green - "purple" -> ThemeColors.Purple - "orange" -> ThemeColors.Orange - "pink" -> ThemeColors.Pink - "gray" -> ThemeColors.Gray - "yellow" -> ThemeColors.Yellow - else -> ThemeColors.Default - } + ThemeConfig.currentTheme = ThemeColors.fromName(themeName ?: "default") } +/** + * 保存动态颜色状态 + */ fun Context.saveDynamicColorState(enabled: Boolean) { getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .edit { @@ -348,28 +496,12 @@ fun Context.saveDynamicColorState(enabled: Boolean) { ThemeConfig.useDynamicColor = enabled } +/** + * 加载动态颜色状态 + */ fun Context.loadDynamicColorState() { val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) .getBoolean("use_dynamic_color", true) + ThemeConfig.useDynamicColor = enabled -} - -private fun adjustColor(color: Color): Color { - val minLuminance = 0.75f - val maxLuminance = 1f - var luminance = color.luminance() - if (luminance < minLuminance) { - luminance = minLuminance - } else if (luminance > maxLuminance) { - luminance = maxLuminance - } - return color.copy(luminance) -} - -private fun mixColors(color1: Color, color2: Color, ratio: Float): Color { - val r = (color1.red * ratio + color2.red * (1 - ratio)) - val g = (color1.green * ratio + color2.green * (1 - ratio)) - val b = (color1.blue * ratio + color2.blue * (1 - ratio)) - val a = (color1.alpha * ratio + color2.alpha * (1 - ratio)) - return Color(r, g, b, a) } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Type.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Type.kt index e943267a..beefa2e2 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Type.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Type.kt @@ -1,33 +1,108 @@ package com.sukisu.ultra.ui.theme +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -// Set of Material typography styles to start with -val Typography = androidx.compose.material3.Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, +val Typography = Typography( + // 大标题 + displayLarge = TextStyle( fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override + fontSize = 57.sp, + lineHeight = 64.sp, + letterSpacing = (-0.25).sp + ), + displayMedium = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 45.sp, + lineHeight = 52.sp, + letterSpacing = 0.sp + ), + displaySmall = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 36.sp, + lineHeight = 44.sp, + letterSpacing = 0.sp + ), + + // 标题 + headlineLarge = TextStyle( + fontWeight = FontWeight.SemiBold, + fontSize = 32.sp, + lineHeight = 40.sp, + letterSpacing = 0.sp + ), + headlineMedium = TextStyle( + fontWeight = FontWeight.SemiBold, + fontSize = 28.sp, + lineHeight = 36.sp, + letterSpacing = 0.sp + ), + headlineSmall = TextStyle( + fontWeight = FontWeight.SemiBold, + fontSize = 24.sp, + lineHeight = 32.sp, + letterSpacing = 0.sp + ), + + // 标题栏 titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.SemiBold, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp ), + titleMedium = TextStyle( + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.15.sp + ), + titleSmall = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp + ), + + // 主体文字 + bodyLarge = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ), + bodyMedium = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.25.sp + ), + bodySmall = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.4.sp + ), + + // 标签 + labelLarge = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp + ), + labelMedium = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ), labelSmall = TextStyle( - fontFamily = FontFamily.Default, fontWeight = FontWeight.Medium, fontSize = 11.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ ) \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt index a91e4b1f..e7635c98 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt @@ -68,9 +68,9 @@ class SuperUserViewModel : ViewModel() { // 批量操作相关状态 var showBatchActions by mutableStateOf(false) - private set + internal set var selectedApps by mutableStateOf>(emptySet()) - private set + internal set private val sortedList by derivedStateOf { val comparator = compareBy { diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 0fce62e9..bc922a9e 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -297,4 +297,25 @@ GKI安装 内核版本:%1$s 使用修补工具:%1$s + 配置 + 应用设置 + 工具 + + 清除 + Root 权限应用 + 自定义配置应用 + 其他应用 + 未找到应用 + SELinux 已设置为启用状态 + SELinux 已设置为禁用状态 + SELinux 状态更改失败 + 高级设置 + 外观设置 + 返回 + 展开 + 收起 + SuSFS 已启用 + SuSFS 已禁用 + 背景设置成功 + 已移除自定义背景 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index ac575ee8..be11273b 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -301,4 +301,25 @@ GKI installation Kernel version:%1$s Using the patching tool:%1$s + Configure + Application Settings + Tools + + Removals + Root Application of permissions + Customized Configuration Application + Other Applications + Application not found + SELinux Enabled + SELinux Disabled + SELinux Status change failed + Advanced Settings + Customize the toolbar + Comeback + Be in full swing + put away + SuSFS enabled + SuSFS disabled + Background set successfully + Removed custom backgrounds