Refactor the UI to rewrite the interface (#61)

This commit is contained in:
ShirkNeko
2025-04-29 15:52:56 +08:00
committed by GitHub
parent bfdb706b60
commit 3526e84e04
18 changed files with 3135 additions and 1489 deletions

View File

@@ -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<NavBackStackEntry>.() -> EnterTransition
get() = { fadeIn(animationSpec = tween(340)) }
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
get() = { fadeOut(animationSpec = tween(340)) }
defaultTransitions = remember {
object : NavHostAnimatedDestinationStyle() {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
fadeIn(animationSpec = tween(300)) +
slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Up,
animationSpec = tween(300)
)
}
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> 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,
)
}
}
}
}
}

View File

@@ -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

View File

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

View File

@@ -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 -> " <GKI>"
}
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<String, Long> {
@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 = {})
}
}

View File

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

View File

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

View File

@@ -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, "", {}, {}, {}, {})
}
}

View File

@@ -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<RootGraph>
@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)
}
}

View File

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

View File

@@ -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)
}
}
}
},
}
)
}

View File

@@ -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
/**
* 根据背景颜色、主题模式和用户设置确定内容颜色
*/
@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
}
}

View File

@@ -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
}
}

View File

@@ -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<Uri?>(null)
var forceDarkMode by mutableStateOf<Boolean?>(null)
var currentTheme by mutableStateOf<ThemeColors>(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)
}

View File

@@ -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
)
*/
)

View File

@@ -68,9 +68,9 @@ class SuperUserViewModel : ViewModel() {
// 批量操作相关状态
var showBatchActions by mutableStateOf(false)
private set
internal set
var selectedApps by mutableStateOf<Set<String>>(emptySet())
private set
internal set
private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> {

View File

@@ -297,4 +297,25 @@
<string name="GKI_install_methods">GKI安装</string>
<string name="kernel_version_log">内核版本:%1$s</string>
<string name="tool_version_log">使用修补工具:%1$s</string>
<string name="configuration">配置</string>
<string name="app_settings">应用设置</string>
<string name="tools">工具</string>
<!-- SuperUser 里用到的字符串资源 -->
<string name="clear">清除</string>
<string name="apps_with_root">Root 权限应用</string>
<string name="apps_with_custom_profile">自定义配置应用</string>
<string name="other_apps">其他应用</string>
<string name="no_apps_found">未找到应用</string>
<string name="selinux_enabled_toast">SELinux 已设置为启用状态</string>
<string name="selinux_disabled_toast">SELinux 已设置为禁用状态</string>
<string name="selinux_change_failed">SELinux 状态更改失败</string>
<string name="advanced_settings">高级设置</string>
<string name="appearance_settings">外观设置</string>
<string name="back">返回</string>
<string name="expand">展开</string>
<string name="collapse">收起</string>
<string name="susfs_enabled">SuSFS 已启用</string>
<string name="susfs_disabled">SuSFS 已禁用</string>
<string name="background_set_success">背景设置成功</string>
<string name="background_removed">已移除自定义背景</string>
</resources>

View File

@@ -301,4 +301,25 @@
<string name="GKI_install_methods">GKI installation</string>
<string name="kernel_version_log">Kernel version%1$s</string>
<string name="tool_version_log">Using the patching tool%1$s</string>
<string name="configuration">Configure</string>
<string name="app_settings">Application Settings</string>
<string name="tools">Tools</string>
<!-- String resources used in SuperUser -->
<string name="clear">Removals</string>
<string name="apps_with_root">Root Application of permissions</string>
<string name="apps_with_custom_profile">Customized Configuration Application</string>
<string name="other_apps">Other Applications</string>
<string name="no_apps_found">Application not found</string>
<string name="selinux_enabled_toast">SELinux Enabled</string>
<string name="selinux_disabled_toast">SELinux Disabled</string>
<string name="selinux_change_failed">SELinux Status change failed</string>
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="expand">Be in full swing</string>
<string name="collapse">put away</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
</resources>