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 package com.sukisu.ultra.ui
import android.database.ContentObserver
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.tween 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.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController 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.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.* import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
class MainActivity : ComponentActivity() { 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?) { override fun onCreate(savedInstanceState: Bundle?) {
// 启用边缘到边缘显示
// Enable edge to edge
enableEdgeToEdge() enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false window.isNavigationBarContrastEnforced = false
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// 加载保存的背景设置 // 加载保存的主题设置
loadCustomBackground() loadCustomBackground()
loadThemeMode() loadThemeMode()
loadThemeColors()
loadDynamicColorState()
CardConfig.load(applicationContext) 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) val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) { if (isManager) {
@@ -58,22 +91,37 @@ class MainActivity : ComponentActivity() {
KernelSUTheme { KernelSUTheme {
val navController = rememberNavController() val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
Scaffold( Scaffold(
bottomBar = { BottomBar(navController) }, bottomBar = { BottomBar(navController) },
contentWindowInsets = WindowInsets(0, 0, 0, 0) contentWindowInsets = WindowInsets(0, 0, 0, 0),
snackbarHost = { SnackbarHost(snackBarHostState) }
) { innerPadding -> ) { innerPadding ->
CompositionLocalProvider( CompositionLocalProvider(
LocalSnackbarHost provides snackBarHostState, LocalSnackbarHost provides snackBarHostState
) { ) {
DestinationsNavHost( DestinationsNavHost(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
navGraph = NavGraphs.root as NavHostGraphSpec, navGraph = NavGraphs.root as NavHostGraphSpec,
navController = navController, navController = navController,
defaultTransitions = object : NavHostAnimatedDestinationStyle() { defaultTransitions = remember {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition object : NavHostAnimatedDestinationStyle() {
get() = { fadeIn(animationSpec = tween(340)) } override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition fadeIn(animationSpec = tween(300)) +
get() = { fadeOut(animationSpec = tween(340)) } 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 @Composable
@@ -90,17 +145,25 @@ private fun BottomBar(navController: NavHostController) {
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
// 获取卡片颜色和透明度 val containerColor = MaterialTheme.colorScheme.surfaceContainer
val cardColor = MaterialTheme.colorScheme.secondaryContainer val selectedColor = MaterialTheme.colorScheme.primary
val cardAlpha = CardConfig.cardAlpha val unselectedColor = MaterialTheme.colorScheme.onSurfaceVariant
val cardElevation = CardConfig.cardElevation val cornerRadius = 18.dp
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp)
.clip(RoundedCornerShape(cornerRadius)),
color = containerColor.copy(alpha = 0.95f),
tonalElevation = 0.dp
) {
NavigationBar( NavigationBar(
tonalElevation = cardElevation, // 动态设置阴影 modifier = Modifier.windowInsetsPadding(
containerColor = cardColor.copy(alpha = cardAlpha), WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only( ),
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom containerColor = Color.Transparent,
) tonalElevation = 0.dp
) { ) {
BottomBarDestination.entries.forEach { destination -> BottomBarDestination.entries.forEach { destination ->
if (destination == BottomBarDestination.Kpm) { if (destination == BottomBarDestination.Kpm) {
@@ -110,9 +173,7 @@ private fun BottomBar(navController: NavHostController) {
NavigationBarItem( NavigationBarItem(
selected = isCurrentDestOnBackStack, selected = isCurrentDestOnBackStack,
onClick = { onClick = {
if (isCurrentDestOnBackStack) { if (!isCurrentDestOnBackStack) {
navigator.popBackStack(destination.direction, false)
}
navigator.navigate(destination.direction) { navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) { popUpTo(NavGraphs.root as RouteOrDirection) {
saveState = true saveState = true
@@ -120,18 +181,31 @@ private fun BottomBar(navController: NavHostController) {
launchSingleTop = true launchSingleTop = true
restoreState = true restoreState = true
} }
},
icon = {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
} }
}, },
label = { Text(stringResource(destination.label)) }, icon = {
alwaysShowLabel = false, 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( colors = NavigationBarItemDefaults.colors(
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant selectedIconColor = selectedColor,
unselectedIconColor = unselectedColor,
selectedTextColor = selectedColor,
unselectedTextColor = unselectedColor,
indicatorColor = MaterialTheme.colorScheme.secondaryContainer
) )
) )
} }
@@ -141,9 +215,7 @@ private fun BottomBar(navController: NavHostController) {
NavigationBarItem( NavigationBarItem(
selected = isCurrentDestOnBackStack, selected = isCurrentDestOnBackStack,
onClick = { onClick = {
if (isCurrentDestOnBackStack) { if (!isCurrentDestOnBackStack) {
navigator.popBackStack(destination.direction, false)
}
navigator.navigate(destination.direction) { navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) { popUpTo(NavGraphs.root as RouteOrDirection) {
saveState = true saveState = true
@@ -151,18 +223,35 @@ private fun BottomBar(navController: NavHostController) {
launchSingleTop = true launchSingleTop = true
restoreState = true restoreState = true
} }
},
icon = {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
} }
}, },
label = { Text(stringResource(destination.label)) }, icon = {
alwaysShowLabel = false, 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
)
) )
} }
} }
} }
} }
}

View File

@@ -14,7 +14,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.ThemeConfig import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize
/** /**
@@ -60,7 +59,7 @@ fun SlotSelectionDialog(
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = backgroundColor containerColor = backgroundColor
), ),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier

View File

@@ -1,32 +1,101 @@
package com.sukisu.ultra.ui.component 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.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable @Composable
fun SwitchItem( fun SwitchItem(
icon: ImageVector, icon: ImageVector,
title: String, title: String,
summary: String, summary: String? = null,
checked: Boolean, checked: Boolean,
modifier: Modifier = Modifier, enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit 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( ListItem(
modifier = modifier, headlineContent = {
leadingContent = { Icon(icon, contentDescription = null) }, Text(
headlineContent = { Text(title) }, text = title,
supportingContent = { Text(summary) }, 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 = { trailingContent = {
Switch( Switch(
checked = checked, 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.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import android.system.Os import android.system.Os
import android.util.Log import android.util.Log
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@@ -19,10 +22,13 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.* import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.pm.PackageInfoCompat 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.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -71,24 +76,19 @@ fun HomeScreen(navigator: DestinationsNavigator) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false) .getBoolean("is_simple_mode", false)
}
// 从 SharedPreferences 加载隐藏 KernelSU 版本号开关状态
LaunchedEffect(Unit) {
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false) .getBoolean("is_hide_version", false)
}
// 从 SharedPreferences 加载隐藏模块数量等信息开关状态
LaunchedEffect(Unit) {
isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_other_info", false) .getBoolean("is_hide_other_info", false)
}
// 从 SharedPreferences 加载隐藏 SuSFS 状态开关状态
LaunchedEffect(Unit) {
isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false) .getBoolean("is_hide_susfs_status", false)
} }
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
val deviceModel = getDeviceModel(context) val deviceModel = getDeviceModel(context)
@@ -126,7 +126,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.padding(innerPadding) .padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(top = 12.dp) .padding(top = 16.dp)
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
@@ -143,6 +143,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
StatusCard(kernelVersion, ksuVersion, lkmMode) { StatusCard(kernelVersion, ksuVersion, lkmMode) {
navigator.navigate(InstallScreenDestination) navigator.navigate(InstallScreenDestination)
} }
if (isManager && Natives.requireNewKernel()) { if (isManager && Natives.requireNewKernel()) {
WarningCard( WarningCard(
stringResource(id = R.string.require_kernel_version).format( stringResource(id = R.string.require_kernel_version).format(
@@ -150,28 +151,39 @@ fun HomeScreen(navigator: DestinationsNavigator) {
) )
) )
} }
if (ksuVersion != null && !rootAvailable()) { if (ksuVersion != null && !rootAvailable()) {
WarningCard( WarningCard(
stringResource(id = R.string.grant_root_failed) stringResource(id = R.string.grant_root_failed)
) )
} }
val checkUpdate = val checkUpdate =
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("check_update", true) .getBoolean("check_update", true)
if (checkUpdate) { if (checkUpdate) {
UpdateCard() UpdateCard()
} }
val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) } val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) }
var clickCount by rememberSaveable { mutableIntStateOf(prefs.getInt("click_count", 0)) } var clickCount by rememberSaveable { mutableIntStateOf(prefs.getInt("click_count", 0)) }
if (!isSimpleMode && clickCount < 3) { if (!isSimpleMode && clickCount < 3) {
AnimatedVisibility( AnimatedVisibility(
visible = clickCount < 3, visible = clickCount < 3,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut() exit = shrinkVertically() + fadeOut()
) { ) {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), 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( Row(
modifier = Modifier modifier = Modifier
@@ -183,21 +195,31 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 12.dp)
)
Text( Text(
text = stringResource(R.string.using_mksu_manager), text = stringResource(R.string.using_mksu_manager),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
) )
} }
} }
} }
} }
InfoCard() InfoCard()
if (!isSimpleMode) { if (!isSimpleMode) {
ContributionCard() ContributionCard()
DonateCard() DonateCard()
LearnMoreCard() LearnMoreCard()
} }
Spacer(Modifier)
Spacer(Modifier.height(16.dp))
} }
} }
} }
@@ -217,25 +239,25 @@ fun UpdateCard() {
val newVersionUrl = newVersion.downloadUrl val newVersionUrl = newVersion.downloadUrl
val changelog = newVersion.changelog val changelog = newVersion.changelog
Log.d("UpdateCard", "Current version code: $currentVersionCode")
Log.d("UpdateCard", "New version code: $newVersionCode")
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val title = stringResource(id = R.string.module_changelog) val title = stringResource(id = R.string.module_changelog)
val updateText = stringResource(id = R.string.module_update) val updateText = stringResource(id = R.string.module_update)
AnimatedVisibility( AnimatedVisibility(
visible = newVersionCode > currentVersionCode, visible = newVersionCode > currentVersionCode,
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
),
exit = shrinkVertically() + fadeOut() exit = shrinkVertically() + fadeOut()
) { ) {
val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) }) val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) })
WarningCard( WarningCard(
message = stringResource(id = R.string.new_version_available).format(newVersionCode), message = stringResource(id = R.string.new_version_available).format(newVersionCode),
MaterialTheme.colorScheme.outlineVariant color = MaterialTheme.colorScheme.tertiaryContainer,
) { onClick = {
if (changelog.isEmpty()) { if (changelog.isEmpty()) {
uriHandler.openUri(newVersionUrl) uriHandler.openUri(newVersionUrl)
} else { } else {
@@ -247,16 +269,23 @@ fun UpdateCard() {
) )
} }
} }
)
} }
} }
@Composable @Composable
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") { fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
DropdownMenuItem(text = { DropdownMenuItem(
Text(stringResource(id)) text = { Text(stringResource(id)) },
}, onClick = { onClick = { reboot(reason) },
reboot(reason) leadingIcon = {
}) Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
}
)
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -267,35 +296,47 @@ private fun TopBar(
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer val cardColor = MaterialTheme.colorScheme.surfaceVariant
val cardAlpha = CardConfig.cardAlpha val cardAlpha = CardConfig.cardAlpha
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.app_name)) }, title = {
Text(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.titleLarge
)
},
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha), containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha) scrolledContainerColor = cardColor.copy(alpha = 1f)
), ),
actions = { actions = {
if (kernelVersion.isGKI()) { if (kernelVersion.isGKI()) {
IconButton(onClick = onInstallClick) { 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) } var showDropdown by remember { mutableStateOf(false) }
if (Natives.isKsuValid(ksuApp.packageName)) { if (Natives.isKsuValid(ksuApp.packageName)) {
IconButton(onClick = { showDropdown = true }) { 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( DropdownMenu(
expanded = showDropdown, expanded = showDropdown,
onDismissRequest = { showDropdown = false } onDismissRequest = { showDropdown = false }
) { ) {
RebootDropdownItem(id = R.string.reboot) RebootDropdownItem(id = R.string.reboot)
val pm = val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") 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), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
} }
@Composable @Composable
private fun StatusCard( private fun StatusCard(
kernelVersion: KernelVersion, kernelVersion: KernelVersion,
@@ -322,17 +370,28 @@ private fun StatusCard(
onClickInstall: () -> Unit = {} onClickInstall: () -> Unit = {}
) { ) {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
) { modifier = Modifier
Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .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()) { if (kernelVersion.isGKI()) {
onClickInstall() onClickInstall()
} }
} }
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) { .padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
when { when {
ksuVersion != null -> { ksuVersion != null -> {
val safeMode = when { val safeMode = when {
@@ -346,8 +405,7 @@ private fun StatusCard(
else -> " <GKI>" else -> " <GKI>"
} }
val workingText = val workingText = "${stringResource(id = R.string.home_working)}$workingMode$safeMode"
"${stringResource(id = R.string.home_working)}$workingMode$safeMode"
val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false) .getBoolean("is_hide_version", false)
@@ -358,40 +416,55 @@ private fun StatusCard(
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false) .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)) { Column(Modifier.padding(start = 20.dp)) {
Text( Text(
text = workingText, text = workingText,
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
) )
if (!isHideVersion) { if (!isHideVersion) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_working_version, ksuVersion), text = stringResource(R.string.home_working_version, ksuVersion),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
if (!isHideOtherInfo) { if (!isHideOtherInfo) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource( text = stringResource(R.string.home_superuser_count, getSuperuserCount()),
R.string.home_superuser_count, getSuperuserCount() style = MaterialTheme.typography.bodyMedium,
), style = MaterialTheme.typography.bodyMedium color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_module_count, getModuleCount()), text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()), text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} }
if (!isHideSusfsStatus) { if (!isHideSusfsStatus) {
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -405,7 +478,8 @@ private fun StatusCard(
Text( Text(
text = stringResource(R.string.home_susfs, translatedStatus), 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() -> { 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)) { Column(Modifier.padding(start = 20.dp)) {
Text( Text(
text = stringResource(R.string.home_not_installed), text = stringResource(R.string.home_not_installed),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.error
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_click_to_install), text = stringResource(R.string.home_click_to_install),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} }
else -> { 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)) { Column(Modifier.padding(start = 20.dp)) {
Text( Text(
text = stringResource(R.string.home_unsupported), text = stringResource(R.string.home_unsupported),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.error
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_unsupported_reason), 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 @Composable
fun WarningCard( fun WarningCard(
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null message: String,
color: Color = MaterialTheme.colorScheme.errorContainer,
onClick: (() -> Unit)? = null
) { ) {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(color),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then(onClick?.let { Modifier.clickable { it() } } ?: Modifier) .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(
text = message, style = MaterialTheme.typography.bodyMedium text = message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer
) )
} }
} }
} }
@Composable @Composable
fun ContributionCard() { fun ContributionCard() {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val links = listOf("https://github.com/zako", "https://github.com/udochina") val links = listOf("https://github.com/zako", "https://github.com/udochina")
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.tertiaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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( Row(
modifier = Modifier modifier = Modifier
@@ -484,15 +607,27 @@ fun ContributionCard() {
.padding(24.dp), .padding(24.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(
imageVector = Icons.Filled.Code,
contentDescription = null,
tint = MaterialTheme.colorScheme.onTertiaryContainer,
modifier = Modifier
.padding(end = 16.dp)
.size(28.dp)
)
Column { Column {
Text( Text(
text = stringResource(R.string.home_ContributionCard_kernelsu), text = stringResource(R.string.home_ContributionCard_kernelsu),
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onTertiaryContainer
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_click_to_ContributionCard_kernelsu), 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) val url = stringResource(R.string.home_learn_kernelsu_url)
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.primaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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)
)
) { ) {
Row(
Row(modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
uriHandler.openUri(url) uriHandler.openUri(url)
} }
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) { .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 { Column {
Text( Text(
text = stringResource(R.string.home_learn_kernelsu), text = stringResource(R.string.home_learn_kernelsu),
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onPrimaryContainer
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_click_to_learn_kernelsu), 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( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.shadow(
elevation = 0.dp,
shape = MaterialTheme.shapes.large,
spotColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.1f)
)
) { ) {
Row(
Row(modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
uriHandler.openUri("https://patreon.com/weishu") uriHandler.openUri("https://patreon.com/weishu")
} }
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) { .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 { Column {
Text( Text(
text = stringResource(R.string.home_support_title), text = stringResource(R.string.home_support_title),
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSecondaryContainer
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_support_content), 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) .getBoolean("is_simple_mode", false)
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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( Column(
modifier = Modifier modifier = Modifier
@@ -586,57 +773,90 @@ private fun InfoCard() {
icon: ImageVector = Icons.Default.Info icon: ImageVector = Icons.Default.Info
) { ) {
contents.appendLine(label).appendLine(content).appendLine() contents.appendLine(label).appendLine(content).appendLine()
Row(verticalAlignment = Alignment.CenterVertically) { Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp)
) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = label, contentDescription = label,
modifier = Modifier.size(24.dp) modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.primary
) )
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Column { Column {
Text(text = label, style = MaterialTheme.typography.bodyLarge) Text(
Text(text = content, style = MaterialTheme.typography.bodyMedium) 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) { if (!isSimpleMode) {
Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE 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) 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) 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(
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security) stringResource(R.string.home_selinux_status),
getSELinuxStatus(),
icon = Icons.Default.Security
)
if (!isSimpleMode) { if (!isSimpleMode) {
if (lkmMode != true) { if (lkmMode != true) {
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
var displayVersion: String
val isKpmConfigured = checkKpmConfigured() val isKpmConfigured = checkKpmConfigured()
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
val statusText = if (isKpmConfigured) { val statusText = if (isKpmConfigured) {
stringResource(R.string.kernel_patched) stringResource(R.string.kernel_patched)
} else { } else {
stringResource(R.string.kernel_not_enabled) stringResource(R.string.kernel_not_enabled)
} }
displayVersion = "${stringResource(R.string.not_supported)} ($statusText)" "${stringResource(R.string.not_supported)} ($statusText)"
} else { } 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,12 +864,10 @@ private fun InfoCard() {
.getBoolean("is_hide_susfs_status", false) .getBoolean("is_hide_susfs_status", false)
if ((!isSimpleMode) && (!isHideSusfsStatus)) { if ((!isSimpleMode) && (!isHideSusfsStatus)) {
Spacer(modifier = Modifier.height(16.dp))
val suSFS = getSuSFS() val suSFS = getSuSFS()
if (suSFS == "Supported") { if (suSFS == "Supported") {
val suSFSVersion = getSuSFSVersion() val suSFSVersion = getSuSFSVersion()
if (suSFSVersion.isEmpty()) return@withContext if (suSFSVersion.isNotEmpty()) {
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU" val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val infoText = buildString { val infoText = buildString {
append(suSFSVersion) append(suSFSVersion)
@@ -661,8 +879,13 @@ private fun InfoCard() {
} }
} }
} }
InfoCardItem( InfoCardItem(
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage) stringResource(R.string.home_susfs_version),
infoText,
icon = Icons.Default.Storage
)
}
} }
} }
} }
@@ -678,7 +901,7 @@ fun getManagerVersion(context: Context): Pair<String, Long> {
@Preview @Preview
@Composable @Composable
private fun StatusCardPreview() { private fun StatusCardPreview() {
Column { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
StatusCard(KernelVersion(5, 10, 101), 1, null) StatusCard(KernelVersion(5, 10, 101), 1, null)
StatusCard(KernelVersion(5, 10, 101), 20000, true) StatusCard(KernelVersion(5, 10, 101), 20000, true)
StatusCard(KernelVersion(5, 10, 101), null, true) StatusCard(KernelVersion(5, 10, 101), null, true)
@@ -689,11 +912,11 @@ private fun StatusCardPreview() {
@Preview @Preview
@Composable @Composable
private fun WarningCardPreview() { private fun WarningCardPreview() {
Column { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
WarningCard(message = "Warning message") WarningCard(message = "Warning message")
WarningCard( WarningCard(
message = "Warning message ", message = "Warning message ",
MaterialTheme.colorScheme.outlineVariant, MaterialTheme.colorScheme.tertiaryContainer,
onClick = {}) 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.CardConfig
import com.sukisu.ultra.ui.theme.ThemeConfig import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
/** /**
@@ -584,7 +583,7 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
}, },
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f), containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
tonalElevation = getCardElevation() tonalElevation = 0.dp
) )
} }
} }

View File

@@ -1,20 +1,24 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.app.Activity.RESULT_OK
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.* import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -36,6 +40,7 @@ import java.io.BufferedReader
import java.io.FileInputStream import java.io.FileInputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.net.* import java.net.*
import android.app.Activity
/** /**
* KPM 管理界面 * KPM 管理界面
@@ -56,7 +61,7 @@ fun KpmScreen(
val cardColor = if (!ThemeConfig.useDynamicColor) { val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast ThemeConfig.currentTheme.ButtonContrast
} else { } else {
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.primaryContainer
} }
val moduleConfirmContentMap = viewModel.moduleList.associate { module -> val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
@@ -64,7 +69,7 @@ fun KpmScreen(
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName) 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 kpmInstallSuccess = stringResource(R.string.kpm_install_success)
val kpmInstallFailed = stringResource(R.string.kpm_install_failed) val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
@@ -103,16 +108,33 @@ fun KpmScreen(
} }
} }
} }
AlertDialog( AlertDialog(
onDismissRequest = { onDismissRequest = {
dismiss() dismiss()
tempFileForInstall?.delete() tempFileForInstall?.delete()
tempFileForInstall = null tempFileForInstall = null
}, },
title = { Text(kpmInstallMode) }, title = {
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } }, 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 = { confirmButton = {
Column { Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Button( Button(
onClick = { onClick = {
scope.launch { scope.launch {
@@ -129,11 +151,20 @@ fun KpmScreen(
} }
tempFileForInstall = null 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) Text(kpmInstallModeLoad)
} }
Spacer(modifier = Modifier.height(8.dp))
Button( Button(
onClick = { onClick = {
scope.launch { scope.launch {
@@ -150,8 +181,17 @@ fun KpmScreen(
} }
tempFileForInstall = null 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) Text(kpmInstallModeEmbed)
} }
} }
@@ -166,14 +206,16 @@ fun KpmScreen(
) { ) {
Text(cancel) Text(cancel)
} }
} },
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
shape = MaterialTheme.shapes.extraLarge
) )
} }
val selectPatchLauncher = rememberLauncherForActivityResult( val selectPatchLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult if (result.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult
val uri = result.data?.data ?: return@rememberLauncherForActivityResult val uri = result.data?.data ?: return@rememberLauncherForActivityResult
@@ -236,10 +278,13 @@ fun KpmScreen(
onClearClick = { viewModel.search = "" }, onClearClick = { viewModel.search = "" },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
dropdownContent = { dropdownContent = {
IconButton(onClick = { viewModel.fetchModuleList() }) { IconButton(
onClick = { viewModel.fetchModuleList() }
) {
Icon( Icon(
imageVector = Icons.Outlined.Refresh, imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(R.string.refresh) contentDescription = stringResource(R.string.refresh),
tint = MaterialTheme.colorScheme.primary
) )
} }
} }
@@ -256,19 +301,40 @@ fun KpmScreen(
}, },
icon = { icon = {
Icon( Icon(
imageVector = Icons.Outlined.Add, imageVector = Icons.Filled.Add,
contentDescription = stringResource(R.string.kpm_install) contentDescription = stringResource(R.string.kpm_install),
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
}, },
text = { Text(stringResource(R.string.kpm_install)) }, text = {
containerColor = cardColor.copy(alpha = 1f), Text(
contentColor = MaterialTheme.colorScheme.onSecondaryContainer 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) } snackbarHost = { SnackbarHost(snackBarHost) }
) { padding -> ) { padding ->
Column(modifier = Modifier.padding(padding)) { Column(modifier = Modifier.padding(padding)) {
if (!isNoticeClosed) { if (!isNoticeClosed) {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(MaterialTheme.shapes.medium)
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -276,37 +342,70 @@ fun KpmScreen(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(
imageVector = Icons.Filled.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier
.padding(end = 16.dp)
.size(24.dp)
)
Text( Text(
text = stringResource(R.string.kernel_module_notice), text = stringResource(R.string.kernel_module_notice),
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
textAlign = TextAlign.Center style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
) )
IconButton(onClick = {
IconButton(
onClick = {
isNoticeClosed = true isNoticeClosed = true
sharedPreferences.edit { putBoolean("is_notice_closed", true) } sharedPreferences.edit { putBoolean("is_notice_closed", true) }
}) { },
modifier = Modifier.size(24.dp),
colors = IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Icon( Icon(
imageVector = Icons.Outlined.Close, imageVector = Icons.Filled.Close,
contentDescription = stringResource(R.string.close_notice) contentDescription = stringResource(R.string.close_notice)
) )
} }
} }
} }
}
if (viewModel.moduleList.isEmpty()) { if (viewModel.moduleList.isEmpty()) {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.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( Text(
stringResource(R.string.kpm_empty), stringResource(R.string.kpm_empty),
textAlign = TextAlign.Center textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
}
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
items(viewModel.moduleList) { module -> items(viewModel.moduleList) { module ->
@@ -489,13 +588,34 @@ private fun KpmModuleItem(
if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) { if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) {
AlertDialog( AlertDialog(
onDismissRequest = { viewModel.hideInputDialog() }, 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 = { text = {
OutlinedTextField( OutlinedTextField(
value = viewModel.inputArgs, value = viewModel.inputArgs,
onValueChange = { viewModel.updateInputArgs(it) }, onValueChange = { viewModel.updateInputArgs(it) },
label = { Text(stringResource(R.string.kpm_args)) }, label = {
placeholder = { Text(module.args) } 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 = { confirmButton = {
@@ -512,23 +632,39 @@ private fun KpmModuleItem(
} }
} }
) { ) {
Text(stringResource(R.string.confirm)) Text(
text = stringResource(R.string.confirm),
color = MaterialTheme.colorScheme.primary
)
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { viewModel.hideInputDialog() }) { 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( Card(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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( Column(
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(20.dp)
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -538,54 +674,77 @@ private fun KpmModuleItem(
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = module.name, text = module.name,
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
) )
Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = "${stringResource(R.string.kpm_version)}: ${module.version}", text = "${stringResource(R.string.kpm_version)}: ${module.version}",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Text( Text(
text = "${stringResource(R.string.kpm_author)}: ${module.author}", text = "${stringResource(R.string.kpm_author)}: ${module.author}",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Text( Text(
text = "${stringResource(R.string.kpm_args)}: ${module.args}", 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(
text = module.description, 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( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
FilledTonalButton( Button(
onClick = { viewModel.showInputDialog(module.id) }, 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( Icon(
imageVector = Icons.Outlined.Settings, imageVector = Icons.Filled.Settings,
contentDescription = null contentDescription = null,
modifier = Modifier.size(20.dp)
) )
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(R.string.kpm_control)) Text(stringResource(R.string.kpm_control))
} }
FilledTonalButton( Button(
onClick = onUninstall onClick = onUninstall,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.Delete, imageVector = Icons.Filled.Delete,
contentDescription = null contentDescription = null,
modifier = Modifier.size(20.dp)
) )
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(R.string.kpm_uninstall)) Text(stringResource(R.string.kpm_uninstall))
} }
} }

View File

@@ -7,24 +7,11 @@ import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource 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.*
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.toggleable 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.automirrored.outlined.*
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.* import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.Button import androidx.compose.material3.*
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.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.*
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.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.* import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -105,7 +65,6 @@ import com.sukisu.ultra.ui.webui.WebUIActivity
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import com.sukisu.ultra.ui.util.ModuleModify import com.sukisu.ultra.ui.util.ModuleModify
import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
@@ -254,7 +213,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val hideInstallButton = isSafeMode || hasMagisk val hideInstallButton = isSafeMode || hasMagisk
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val webUILauncher = rememberLauncherForActivityResult( val webUILauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
@@ -275,7 +234,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) { ) {
Icon( Icon(
imageVector = Icons.Filled.MoreVert, imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings) contentDescription = stringResource(id = R.string.settings),
tint = MaterialTheme.colorScheme.primary
) )
DropdownMenu( DropdownMenu(
@@ -284,7 +244,16 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_action_first)) }, 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 = { onClick = {
viewModel.sortActionFirst = !viewModel.sortActionFirst viewModel.sortActionFirst = !viewModel.sortActionFirst
prefs.edit { prefs.edit {
@@ -300,7 +269,16 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_enabled_first)) }, 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 = { onClick = {
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
prefs.edit { prefs.edit {
@@ -311,12 +289,14 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
} }
} }
) )
HorizontalDivider(thickness = Dp.Hairline, modifier = Modifier.padding(vertical = 4.dp))
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.backup_modules)) }, text = { Text(stringResource(R.string.backup_modules)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.Download, imageVector = Icons.Outlined.Download,
contentDescription = stringResource(R.string.backup) contentDescription = stringResource(R.string.backup),
tint = MaterialTheme.colorScheme.primary
) )
}, },
onClick = { onClick = {
@@ -329,7 +309,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.Refresh, imageVector = Icons.Outlined.Refresh,
contentDescription = stringResource(R.string.restore) contentDescription = stringResource(R.string.restore),
tint = MaterialTheme.colorScheme.primary
) )
}, },
onClick = { onClick = {
@@ -349,7 +330,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val cardColor = if (!ThemeConfig.useDynamicColor) { val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast ThemeConfig.currentTheme.ButtonContrast
} else { } else {
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.primaryContainer
} }
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { onClick = {
@@ -363,16 +344,24 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
icon = { icon = {
Icon( Icon(
imageVector = Icons.Filled.Add, imageVector = Icons.Filled.Add,
contentDescription = moduleInstall contentDescription = moduleInstall,
tint = MaterialTheme.colorScheme.onPrimaryContainer
) )
}, },
text = { text = {
Text( Text(
text = moduleInstall text = moduleInstall,
color = MaterialTheme.colorScheme.onPrimaryContainer
) )
}, },
containerColor = cardColor.copy(alpha = 1f), containerColor = cardColor,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp
),
expanded = true,
modifier = Modifier.padding(16.dp)
) )
} }
}, },
@@ -389,12 +378,27 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
.padding(24.dp), .padding(24.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.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( Text(
stringResource(R.string.module_magisk_conflict), stringResource(R.string.module_magisk_conflict),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} }
}
else -> { else -> {
ModuleList( ModuleList(
navigator = navigator, navigator = navigator,
@@ -591,10 +595,25 @@ private fun ModuleList(
modifier = Modifier.fillParentMaxSize(), modifier = Modifier.fillParentMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Column(
stringResource(R.string.module_empty), horizontalAlignment = Alignment.CenterHorizontally,
textAlign = TextAlign.Center 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 onClick: (ModuleViewModel.ModuleInfo) -> Unit
) { ) {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) 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 textDecoration = if (!module.remove) null else TextDecoration.LineThrough
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
@@ -706,6 +733,7 @@ fun ModuleItem(
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
val moduleVersion = stringResource(id = R.string.module_version) val moduleVersion = stringResource(id = R.string.module_version)
val moduleAuthor = stringResource(id = R.string.module_author) val moduleAuthor = stringResource(id = R.string.module_author)
@@ -720,6 +748,7 @@ fun ModuleItem(
lineHeight = MaterialTheme.typography.bodySmall.lineHeight, lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.titleMedium.fontFamily, fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
textDecoration = textDecoration, textDecoration = textDecoration,
color = MaterialTheme.colorScheme.onSurface
) )
Text( Text(
@@ -727,7 +756,8 @@ fun ModuleItem(
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight, lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
textDecoration = textDecoration textDecoration = textDecoration,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Text( Text(
@@ -735,7 +765,8 @@ fun ModuleItem(
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight, lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
textDecoration = textDecoration textDecoration = textDecoration,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -749,7 +780,15 @@ fun ModuleItem(
enabled = !module.update, enabled = !module.update,
checked = module.enabled, checked = module.enabled,
onCheckedChange = onCheckChanged, 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, fontWeight = MaterialTheme.typography.bodySmall.fontWeight,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 4, maxLines = 4,
textDecoration = textDecoration textDecoration = textDecoration,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(thickness = Dp.Hairline) HorizontalDivider(thickness = Dp.Hairline)
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(8.dp))
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (module.hasActionScript) { if (module.hasActionScript) {
FilledTonalButton( FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp), modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
enabled = !module.remove && module.enabled, enabled = !module.remove && module.enabled,
onClick = { onClick = {
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId)) navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
@@ -809,13 +848,11 @@ fun ModuleItem(
) )
} }
} }
Spacer(modifier = Modifier.weight(0.1f, true))
} }
if (module.hasWebUi) { if (module.hasWebUi) {
FilledTonalButton( FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp), modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
enabled = !module.remove && module.enabled, enabled = !module.remove && module.enabled,
onClick = { onClick(module) }, onClick = { onClick(module) },
interactionSource = interactionSource, interactionSource = interactionSource,
@@ -848,7 +885,7 @@ fun ModuleItem(
if (updateUrl.isNotEmpty()) { if (updateUrl.isNotEmpty()) {
Button( Button(
modifier = Modifier.defaultMinSize(52.dp, 32.dp), modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
enabled = !module.remove, enabled = !module.remove,
onClick = { onUpdate(module) }, onClick = { onUpdate(module) },
shape = ButtonDefaults.textShape, shape = ButtonDefaults.textShape,
@@ -868,20 +905,20 @@ fun ModuleItem(
) )
} }
} }
Spacer(modifier = Modifier.weight(0.1f, true))
} }
FilledTonalButton( FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp), modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
onClick = { onUninstallClicked(module) }, onClick = { onUninstallClicked(module) },
contentPadding = ButtonDefaults.TextButtonContentPadding, contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = if (!ThemeConfig.useDynamicColor) { colors = if (!ThemeConfig.useDynamicColor) {
ButtonDefaults.filledTonalButtonColors( ButtonDefaults.filledTonalButtonColors(
containerColor = ThemeConfig.currentTheme.ButtonContrast containerColor = if (!module.remove) MaterialTheme.colorScheme.errorContainer else ThemeConfig.currentTheme.ButtonContrast
) )
} else { } else {
ButtonDefaults.filledTonalButtonColors() ButtonDefaults.filledTonalButtonColors(
containerColor = if (!module.remove) MaterialTheme.colorScheme.errorContainer else MaterialTheme.colorScheme.secondaryContainer
)
} }
) { ) {
if (!module.remove) { if (!module.remove) {
@@ -889,13 +926,13 @@ fun ModuleItem(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Delete, imageVector = Icons.Outlined.Delete,
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer
) )
} else { } else {
Icon( Icon(
modifier = Modifier.size(20.dp).rotate(180f), modifier = Modifier.size(20.dp).rotate(180f),
imageVector = Icons.Outlined.Refresh, imageVector = Icons.Outlined.Refresh,
contentDescription = null, contentDescription = null
) )
} }
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) { if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
@@ -903,7 +940,8 @@ fun ModuleItem(
modifier = Modifier.padding(start = 7.dp), modifier = Modifier.padding(start = 7.dp),
fontFamily = MaterialTheme.typography.labelMedium.fontFamily, fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
fontSize = MaterialTheme.typography.labelMedium.fontSize, 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
) )
} }
} }
@@ -932,4 +970,3 @@ fun ModuleItemPreview() {
) )
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {}) ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
} }

View File

@@ -6,14 +6,18 @@ import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts 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.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -24,13 +28,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource 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.compose.ui.unit.dp
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.content.edit 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.FlashScreenDestination
import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -52,25 +53,20 @@ import com.sukisu.ultra.R
import com.sukisu.ultra.* import com.sukisu.ultra.*
import com.sukisu.ultra.ui.component.* import com.sukisu.ultra.ui.component.*
import com.sukisu.ultra.ui.theme.* 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.LocalSnackbarHost
import com.sukisu.ultra.ui.util.getBugreportFile import com.sukisu.ultra.ui.util.getBugreportFile
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
/**
* @author weishu
* @date 2023/1/1.
*/
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun SettingScreen(navigator: DestinationsNavigator) { fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置 val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName) val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// endregion
Scaffold( Scaffold(
topBar = { topBar = {
@@ -114,55 +110,97 @@ fun SettingScreen(navigator: DestinationsNavigator) {
snackBarHost.showSnackbar(context.getString(R.string.log_saved)) snackBarHost.showSnackbar(context.getString(R.string.log_saved))
} }
} }
// region 配置项列表
// 设置分组卡片 - 配置
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) val profileTemplate = stringResource(id = R.string.settings_profile_template)
if (ksuIsValid) { if (ksuIsValid) {
ListItem( SettingItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, icon = Icons.Filled.Fence,
headlineContent = { Text(profileTemplate) }, title = profileTemplate,
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) }, summary = stringResource(id = R.string.settings_profile_template_summary),
modifier = Modifier.clickable { onClick = {
navigator.navigate(AppProfileTemplateScreenDestination) navigator.navigate(AppProfileTemplateScreenDestination)
} }
) )
} }
// 卸载模块开关 // 卸载模块开关
var umountChecked by rememberSaveable { var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules()) mutableStateOf(Natives.isDefaultUmountModules())
} }
if (ksuIsValid) { if (ksuIsValid) {
SwitchItem( SwitchSettingItem(
icon = Icons.Filled.FolderDelete, icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default), title = stringResource(id = R.string.settings_umount_modules_default),
summary = stringResource(id = R.string.settings_umount_modules_default_summary), summary = stringResource(id = R.string.settings_umount_modules_default_summary),
checked = umountChecked checked = umountChecked,
) { onCheckedChange = {
if (Natives.setDefaultUmountModules(it)) { if (Natives.setDefaultUmountModules(it)) {
umountChecked = it umountChecked = it
} }
} }
)
} }
// SU 禁用开关(仅在兼容版本显示) // SU 禁用开关(仅在兼容版本显示)
if (ksuIsValid) { if (ksuIsValid) {
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable { var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled()) mutableStateOf(!Natives.isSuEnabled())
} }
SwitchItem( SwitchSettingItem(
icon = Icons.Filled.RemoveModerator, icon = Icons.Filled.RemoveModerator,
title = stringResource(id = R.string.settings_disable_su), title = stringResource(id = R.string.settings_disable_su),
summary = stringResource(id = R.string.settings_disable_su_summary), summary = stringResource(id = R.string.settings_disable_su_summary),
checked = isSuDisabled, checked = isSuDisabled,
) { checked -> onCheckedChange = { checked ->
val shouldEnable = !checked val shouldEnable = !checked
if (Natives.setSuEnabled(shouldEnable)) { if (Natives.setSuEnabled(shouldEnable)) {
isSuDisabled = !shouldEnable isSuDisabled = !shouldEnable
} }
} }
)
} }
} }
}
}
// 设置分组卡片 - 应用设置
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.app_settings),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -172,15 +210,16 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.getBoolean("check_update", true) prefs.getBoolean("check_update", true)
) )
} }
SwitchItem( SwitchSettingItem(
icon = Icons.Filled.Update, icon = Icons.Filled.Update,
title = stringResource(id = R.string.settings_check_update), title = stringResource(id = R.string.settings_check_update),
summary = stringResource(id = R.string.settings_check_update_summary), summary = stringResource(id = R.string.settings_check_update_summary),
checked = checkUpdate checked = checkUpdate,
) { onCheckedChange = {
prefs.edit {putBoolean("check_update", it) } prefs.edit {putBoolean("check_update", it) }
checkUpdate = it checkUpdate = it
} }
)
// Web调试开关 // Web调试开关
var enableWebDebugging by rememberSaveable { var enableWebDebugging by rememberSaveable {
@@ -189,90 +228,84 @@ fun SettingScreen(navigator: DestinationsNavigator) {
) )
} }
if (Natives.isKsuValid(ksuApp.packageName)) { if (Natives.isKsuValid(ksuApp.packageName)) {
SwitchItem( SwitchSettingItem(
icon = Icons.Filled.DeveloperMode, icon = Icons.Filled.DeveloperMode,
title = stringResource(id = R.string.enable_web_debugging), title = stringResource(id = R.string.enable_web_debugging),
summary = stringResource(id = R.string.enable_web_debugging_summary), summary = stringResource(id = R.string.enable_web_debugging_summary),
checked = enableWebDebugging checked = enableWebDebugging,
) { onCheckedChange = {
prefs.edit { putBoolean("enable_web_debugging", it) } prefs.edit { putBoolean("enable_web_debugging", it) }
enableWebDebugging = it enableWebDebugging = it
} }
}
// 更多设置
val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem(
leadingContent = {
Icon(
Icons.Filled.Settings,
contentDescription = newButtonTitle
) )
}, }
headlineContent = { Text(newButtonTitle) },
supportingContent = { Text(stringResource(id = R.string.more_settings)) }, // 更多设置
modifier = Modifier.clickable { SettingItem(
icon = Icons.Filled.Settings,
title = stringResource(id = R.string.more_settings),
summary = stringResource(id = R.string.more_settings),
onClick = {
navigator.navigate(MoreSettingsScreenDestination) 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) } var showBottomsheet by remember { mutableStateOf(false) }
ListItem( SettingItem(
leadingContent = { icon = Icons.Filled.BugReport,
Icon( title = stringResource(id = R.string.send_log),
Icons.Filled.BugReport, onClick = {
stringResource(id = R.string.send_log)
)
},
headlineContent = { Text(stringResource(id = R.string.send_log)) },
modifier = Modifier.clickable {
showBottomsheet = true showBottomsheet = true
} }
) )
if (showBottomsheet) { if (showBottomsheet) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { showBottomsheet = false }, onDismissRequest = { showBottomsheet = false },
content = { containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
) {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(10.dp) .fillMaxWidth()
.align(Alignment.CenterHorizontally) .padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) { ) {
Box { LogActionButton(
Column( icon = Icons.Filled.Save,
modifier = Modifier text = stringResource(R.string.save_log),
.padding(16.dp) onClick = {
.clickable {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm") val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
val current = LocalDateTime.now().format(formatter) val current = LocalDateTime.now().format(formatter)
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz") exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
showBottomsheet = false 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
)
}
) LogActionButton(
} icon = Icons.Filled.Share,
} text = stringResource(R.string.send_log),
Box { onClick = {
Column(
modifier = Modifier
.padding(16.dp)
.clickable {
scope.launch { scope.launch {
val bugreport = loadingDialog.withLoading { val bugreport = loadingDialog.withLoading {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@@ -299,30 +332,15 @@ fun SettingScreen(navigator: DestinationsNavigator) {
context.getString(R.string.send_log) context.getString(R.string.send_log)
) )
) )
}
} showBottomsheet = false
) {
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 val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
if (lkmMode) { if (lkmMode) {
@@ -330,22 +348,176 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.withLoading(it) loadingDialog.withLoading(it)
} }
} }
}
}
val about = stringResource(id = R.string.about) // 设置分组卡片 - 关于
ListItem( Card(
leadingContent = { modifier = Modifier
Icon( .fillMaxWidth()
Icons.Filled.ContactPage, .padding(horizontal = 16.dp, vertical = 8.dp),
about 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)
) )
},
headlineContent = { Text(about) }, SettingItem(
modifier = Modifier.clickable { icon = Icons.Filled.Info,
title = stringResource(R.string.about),
onClick = {
aboutDialog.show() 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
)
)
}
} }
@Composable @Composable
@@ -381,16 +553,11 @@ fun UninstallItem(
} }
} }
} }
val uninstall = stringResource(id = R.string.settings_uninstall)
ListItem( SettingItem(
leadingContent = { icon = Icons.Filled.Delete,
Icon( title = stringResource(id = R.string.settings_uninstall),
Icons.Filled.Delete, onClick = {
uninstall
)
},
headlineContent = { Text(uninstall) },
modifier = Modifier.clickable {
uninstallDialog.show() uninstallDialog.show()
} }
) )
@@ -436,7 +603,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
val cardColor = if (!ThemeConfig.useDynamicColor) { val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast ThemeConfig.currentTheme.ButtonContrast
} else { } else {
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.surfaceContainerHigh
} }
AlertDialog( AlertDialog(
@@ -444,29 +611,46 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
dismiss() dismiss()
}, },
title = { title = {
Text(text = stringResource(R.string.settings_uninstall)) Text(
text = stringResource(R.string.settings_uninstall),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface
)
}, },
text = { text = {
Column { Column(
modifier = Modifier.padding(vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
listOptions.forEachIndexed { index, option -> listOptions.forEachIndexed { index, option ->
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium)
.clickable { .clickable {
selection = options[index] selection = options[index]
} }
.padding(vertical = 8.dp) .padding(vertical = 12.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = options[index].icon, imageVector = options[index].icon,
contentDescription = null, contentDescription = null,
modifier = Modifier.padding(end = 8.dp) tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(end = 16.dp)
.size(24.dp)
) )
Column { Column {
Text(text = option.titleText) Text(
text = option.titleText,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
option.subtitleText?.let { option.subtitleText?.let {
Text( Text(
text = it, text = it,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -476,7 +660,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
} }
}, },
confirmButton = { confirmButton = {
androidx.compose.material3.TextButton( TextButton(
onClick = { onClick = {
if (selection != UninstallType.NONE) { if (selection != UninstallType.NONE) {
onSelected(selection) onSelected(selection)
@@ -484,21 +668,27 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
dismiss() dismiss()
} }
) { ) {
Text(text = stringResource(android.R.string.ok)) Text(
text = stringResource(android.R.string.ok),
color = MaterialTheme.colorScheme.primary
)
} }
}, },
dismissButton = { dismissButton = {
androidx.compose.material3.TextButton( TextButton(
onClick = { onClick = {
dismiss() 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), containerColor = cardColor,
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.extraLarge,
tonalElevation = getCardElevation() tonalElevation = 4.dp
) )
} }
} }
@@ -508,24 +698,26 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
private fun TopBar( private fun TopBar(
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer val systemIsDark = isSystemInDarkTheme()
val cardAlpha = CardConfig.cardAlpha val cardColor = MaterialTheme.colorScheme.surfaceVariant
val cardAlpha = if (ThemeConfig.customBackgroundUri != null) {
cardAlpha
} else {
if (systemIsDark) 0.35f else 0.80f
}
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.settings)) }, title = {
Text(
text = stringResource(R.string.settings),
style = MaterialTheme.typography.titleLarge
)
},
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha), containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha) scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
), ),
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
} }
@Preview
@Composable
private fun SettingsPreview() {
SettingScreen(EmptyDestinationsNavigator)
}

View File

@@ -1,6 +1,9 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -13,7 +16,10 @@ import androidx.compose.material.icons.filled.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput 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.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -43,7 +50,7 @@ import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
fun SuperUserScreen(navigator: DestinationsNavigator) { fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>() val viewModel = viewModel<SuperUserViewModel>()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val listState = rememberLazyListState() val listState = rememberLazyListState()
val context = LocalContext.current val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
@@ -80,21 +87,31 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
) { ) {
Icon( Icon(
imageVector = Icons.Filled.MoreVert, imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings) contentDescription = stringResource(id = R.string.settings),
tint = MaterialTheme.colorScheme.primary
) )
DropdownMenu(expanded = showDropdown, onDismissRequest = { DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false showDropdown = false
}) { }) {
DropdownMenuItem(text = { DropdownMenuItem(
Text(stringResource(R.string.refresh)) text = { Text(stringResource(R.string.refresh)) },
}, onClick = { leadingIcon = {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
onClick = {
scope.launch { scope.launch {
viewModel.fetchAppList() viewModel.fetchAppList()
} }
showDropdown = false showDropdown = false
}) }
DropdownMenuItem(text = { )
DropdownMenuItem(
text = {
Text( Text(
if (viewModel.showSystemApps) { if (viewModel.showSystemApps) {
stringResource(R.string.hide_system_apps) stringResource(R.string.hide_system_apps)
@@ -102,22 +119,49 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
stringResource(R.string.show_system_apps) stringResource(R.string.show_system_apps)
} }
) )
}, onClick = { },
leadingIcon = {
Icon(
imageVector = if (viewModel.showSystemApps)
Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
onClick = {
viewModel.showSystemApps = !viewModel.showSystemApps viewModel.showSystemApps = !viewModel.showSystemApps
showDropdown = false showDropdown = false
}) }
DropdownMenuItem(text = { )
Text(stringResource(R.string.backup_allowlist)) HorizontalDivider(thickness = 0.5.dp, modifier = Modifier.padding(vertical = 4.dp))
}, onClick = { 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()) backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
showDropdown = false showDropdown = false
}) }
DropdownMenuItem(text = { )
Text(stringResource(R.string.restore_allowlist)) DropdownMenuItem(
}, onClick = { text = { Text(stringResource(R.string.restore_allowlist)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.RestoreFromTrash,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
onClick = {
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent()) restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
showDropdown = false showDropdown = false
}) }
)
} }
} }
}, },
@@ -128,21 +172,59 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
bottomBar = { bottomBar = {
// 批量操作按钮,直接放在底部栏 // 批量操作按钮,直接放在底部栏
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) { 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
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp), .padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
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( Button(
onClick = { onClick = {
scope.launch { scope.launch {
viewModel.updateBatchPermissions(true) 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)) Text(stringResource(R.string.batch_authorization))
} }
@@ -151,13 +233,24 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
scope.launch { scope.launch {
viewModel.updateBatchPermissions(false) 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))
} }
} }
} }
} }
}
) { innerPadding -> ) { innerPadding ->
PullToRefreshBox( PullToRefreshBox(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
@@ -170,15 +263,23 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
state = listState, state = listState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .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 rootApps = viewModel.appList.filter { it.allowSu }
val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile } val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile }
val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile } val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile }
// 显示ROOT权限应用组 // 显示ROOT权限应用组
if (rootApps.isNotEmpty()) { if (rootApps.isNotEmpty()) {
item {
GroupHeader(title = stringResource(R.string.apps_with_root))
}
items(rootApps, key = { "root_" + it.packageName + it.uid }) { app -> items(rootApps, key = { "root_" + it.packageName + it.uid }) { app ->
AppItem( AppItem(
app = app, app = app,
@@ -214,6 +315,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
// 显示自定义配置应用组 // 显示自定义配置应用组
if (customApps.isNotEmpty()) { if (customApps.isNotEmpty()) {
item {
GroupHeader(title = stringResource(R.string.apps_with_custom_profile))
}
items(customApps, key = { "custom_" + it.packageName + it.uid }) { app -> items(customApps, key = { "custom_" + it.packageName + it.uid }) { app ->
AppItem( AppItem(
app = app, app = app,
@@ -249,6 +354,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
// 显示其他应用组 // 显示其他应用组
if (otherApps.isNotEmpty()) { if (otherApps.isNotEmpty()) {
item {
GroupHeader(title = stringResource(R.string.other_apps))
}
items(otherApps, key = { "other_" + it.packageName + it.uid }) { app -> items(otherApps, key = { "other_" + it.packageName + it.uid }) { app ->
AppItem( AppItem(
app = app, 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( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant) .background(MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.7f))
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
Text( Text(
@@ -299,7 +440,7 @@ fun GroupHeader(title: String) {
style = TextStyle( style = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.primary
) )
) )
} }
@@ -316,33 +457,48 @@ private fun AppItem(
onLongClick: () -> Unit, onLongClick: () -> Unit,
viewModel: SuperUserViewModel 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 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) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onLongPress = { onLongClick() }, onLongPress = { onLongClick() },
onTap = { onClick() } 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) { Row(
LabelText(label = "CUSTOM") modifier = Modifier
} .fillMaxWidth()
} .padding(16.dp),
} verticalAlignment = Alignment.CenterVertically
}, ) {
leadingContent = {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo) .data(app.packageInfo)
@@ -350,43 +506,93 @@ private fun AppItem(
.build(), .build(),
contentDescription = app.label, contentDescription = app.label,
modifier = Modifier modifier = Modifier
.padding(4.dp) .padding(end = 16.dp)
.width(48.dp) .size(48.dp)
.height(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) { if (!viewModel.showBatchActions) {
Switch( Switch(
checked = app.allowSu, 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 { } else {
Checkbox( Checkbox(
checked = isSelected, checked = isSelected,
onCheckedChange = { onToggleSelection() } onCheckedChange = { onToggleSelection() },
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.primary,
uncheckedColor = MaterialTheme.colorScheme.outline
)
) )
} }
} }
) }
} }
@Composable @Composable
fun LabelText(label: String) { fun LabelText(label: String, backgroundColor: Color) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(top = 4.dp, end = 4.dp) .padding(top = 2.dp, end = 2.dp)
.background( .background(
Color.Black, backgroundColor,
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) )
.clip(RoundedCornerShape(4.dp))
) { ) {
Text( Text(
text = label, text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp), modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp),
style = TextStyle( style = TextStyle(
fontSize = 8.sp, fontSize = 10.sp,
color = Color.White, color = Color.White,
fontWeight = FontWeight.Medium
) )
) )
} }

View File

@@ -205,17 +205,17 @@ private fun TemplateItem(
) )
Text(template.description) Text(template.description)
FlowRow { FlowRow {
LabelText(label = "UID: ${template.uid}") LabelText(label = "UID: ${template.uid}", backgroundColor = MaterialTheme.colorScheme.surface)
LabelText(label = "GID: ${template.gid}") LabelText(label = "GID: ${template.gid}", backgroundColor = MaterialTheme.colorScheme.surface)
LabelText(label = template.context) LabelText(label = template.context, backgroundColor = MaterialTheme.colorScheme.surface)
if (template.local) { if (template.local) {
LabelText(label = "local") LabelText(label = "local", backgroundColor = MaterialTheme.colorScheme.surface)
} else { } 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 import androidx.compose.ui.unit.dp
object CardConfig { 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 cardElevation by mutableStateOf(defaultElevation)
var isShadowEnabled by mutableStateOf(true) var isShadowEnabled by mutableStateOf(true)
var isCustomAlphaSet by mutableStateOf(false) var isCustomAlphaSet by mutableStateOf(false)
@@ -23,66 +23,90 @@ object CardConfig {
var isUserLightModeEnabled by mutableStateOf(false) var isUserLightModeEnabled by mutableStateOf(false)
var isCustomBackgroundEnabled by mutableStateOf(false) var isCustomBackgroundEnabled by mutableStateOf(false)
private var lastSystemDarkMode: Boolean? = null
/**
* 保存卡片配置到SharedPreferences
*/
fun save(context: Context) { fun save(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
prefs.edit().apply { prefs.edit().apply {
putFloat("card_alpha", cardAlpha) 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_custom_alpha_set", isCustomAlphaSet)
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled) putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled) putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
putBoolean("is_custom_background_enabled", isCustomBackgroundEnabled)
apply() apply()
} }
} }
/**
* 从SharedPreferences加载卡片配置
*/
fun load(context: Context) { fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 0.45f) cardAlpha = prefs.getFloat("card_alpha", 0.80f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation 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) isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false) isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false) isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
isCustomBackgroundEnabled = prefs.getBoolean("is_custom_background_enabled", false)
} }
/**
* 更新阴影启用状态
*/
fun updateShadowEnabled(enabled: Boolean) { fun updateShadowEnabled(enabled: Boolean) {
isShadowEnabled = enabled isShadowEnabled = enabled
cardElevation = if (enabled) defaultElevation else 0.dp cardElevation = if (enabled) defaultElevation else 0.dp
} }
/**
* 设置深色模式默认值
*/
fun setDarkModeDefaults() { fun setDarkModeDefaults() {
if (!isCustomAlphaSet) { if (!isCustomAlphaSet) {
cardAlpha = 0.35f cardAlpha = 0.50f
}
if (!isShadowEnabled) {
cardElevation = 0.dp cardElevation = 0.dp
} }
} }
} }
/**
* 获取卡片颜色配置
*/
@Composable @Composable
fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors( fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha), containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
contentColor = when { contentColor = determineContentColor(originalColor)
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
}
}
) )
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 OnTertiaryContainer: Color
abstract val ButtonContrast: 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() { object Default : ThemeColors() {
override val Primary = Color(0xFFFFFFFF) override val Primary = Color(0xFFFFFFFF)
override val Secondary = Color(0xFFF5F5F5) override val Secondary = Color(0xFF5F6368)
override val Tertiary = Color(0xFFE0E0E0) override val Tertiary = Color(0xFFFFFFFF)
override val OnPrimary = Color(0xFF616161) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFF616161) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFF616161) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFF5F5F5) override val PrimaryContainer = Color(0xFFD1E3FF)
override val SecondaryContainer = Color(0xFFEEEEEE) override val SecondaryContainer = Color(0xFFE4E6E8)
override val TertiaryContainer = Color(0xFFE0E0E0) override val TertiaryContainer = Color(0xFFD0E1FC)
override val OnPrimaryContainer = Color(0xFF000000) override val OnPrimaryContainer = Color(0xFF0D2E5E)
override val OnSecondaryContainer = Color(0xFF000000) override val OnSecondaryContainer = Color(0xFF24262A)
override val OnTertiaryContainer = Color(0xFF000000) override val OnTertiaryContainer = Color(0xFF0A2E62)
override val ButtonContrast = Color(0xFF00BFFF) 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() { object Blue : ThemeColors() {
override val Primary = Color(0xFF2196F3) override val Primary = Color(0xFF2196F3)
override val Secondary = Color(0xFF1E88E5) override val Secondary = Color(0xFF64B5F6)
override val Tertiary = Color(0xFF0D47A1) override val Tertiary = Color(0xFF0D47A1)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFCBE6FC) override val PrimaryContainer = Color(0xFFD6EAFF)
override val SecondaryContainer = Color(0xFFBBDEFB) override val SecondaryContainer = Color(0xFFE3F2FD)
override val TertiaryContainer = Color(0xFF90CAF9) override val TertiaryContainer = Color(0xFFCFD8DC)
override val OnPrimaryContainer = Color(0xFF0A1A2E) override val OnPrimaryContainer = Color(0xFF0A3049)
override val OnSecondaryContainer = Color(0xFF0A192D) override val OnSecondaryContainer = Color(0xFF0D3C61)
override val OnTertiaryContainer = Color(0xFF071B3D) override val OnTertiaryContainer = Color(0xFF071D41)
override val ButtonContrast = Color(0xFF00BFFF) 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() { object Green : ThemeColors() {
override val Primary = Color(0xFF4CAF50) override val Primary = Color(0xFF43A047)
override val Secondary = Color(0xFF43A047) override val Secondary = Color(0xFF66BB6A)
override val Tertiary = Color(0xFF1B5E20) override val Tertiary = Color(0xFF1B5E20)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFC8E6C9) override val PrimaryContainer = Color(0xFFD8EFDB)
override val SecondaryContainer = Color(0xFFA5D6A7) override val SecondaryContainer = Color(0xFFE8F5E9)
override val TertiaryContainer = Color(0xFF81C784) override val TertiaryContainer = Color(0xFFB9F6CA)
override val OnPrimaryContainer = Color(0xFF0A1F0B) override val OnPrimaryContainer = Color(0xFF0A280D)
override val OnSecondaryContainer = Color(0xFF0A1D0B) override val OnSecondaryContainer = Color(0xFF0E2912)
override val OnTertiaryContainer = Color(0xFF071F09) override val OnTertiaryContainer = Color(0xFF051B07)
override val ButtonContrast = Color(0xFF32CD32) 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() { object Purple : ThemeColors() {
override val Primary = Color(0xFF9C27B0) override val Primary = Color(0xFF9C27B0)
override val Secondary = Color(0xFF8E24AA) override val Secondary = Color(0xFFBA68C8)
override val Tertiary = Color(0xFF4A148C) override val Tertiary = Color(0xFF6A1B9A)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFE1BEE7) override val PrimaryContainer = Color(0xFFF3D8F8)
override val SecondaryContainer = Color(0xFFCE93D8) override val SecondaryContainer = Color(0xFFF5E9F7)
override val TertiaryContainer = Color(0xFFB39DDB) override val TertiaryContainer = Color(0xFFE1BEE7)
override val OnPrimaryContainer = Color(0xFF1F0A23) override val OnPrimaryContainer = Color(0xFF2A0934)
override val OnSecondaryContainer = Color(0xFF1C0A21) override val OnSecondaryContainer = Color(0xFF3C0F50)
override val OnTertiaryContainer = Color(0xFF12071C) override val OnTertiaryContainer = Color(0xFF1D0830)
override val ButtonContrast = Color(0xFFDA70D6) 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() { object Orange : ThemeColors() {
override val Primary = Color(0xFFFF9800) override val Primary = Color(0xFFFF9800)
override val Secondary = Color(0xFFFB8C00) override val Secondary = Color(0xFFFFB74D)
override val Tertiary = Color(0xFFE65100) override val Tertiary = Color(0xFFE65100)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFF000000)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFE0B2) override val PrimaryContainer = Color(0xFFFFECCC)
override val SecondaryContainer = Color(0xFFFFCC80) override val SecondaryContainer = Color(0xFFFFF0D9)
override val TertiaryContainer = Color(0xFFFFB74D) override val TertiaryContainer = Color(0xFFFFD180)
override val OnPrimaryContainer = Color(0xFF1A1100) override val OnPrimaryContainer = Color(0xFF351F00)
override val OnSecondaryContainer = Color(0xFF1A1000) override val OnSecondaryContainer = Color(0xFF3D2800)
override val OnTertiaryContainer = Color(0xFF1A0B00) override val OnTertiaryContainer = Color(0xFF2E1500)
override val ButtonContrast = Color(0xFFFF6347) 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() { object Pink : ThemeColors() {
override val Primary = Color(0xFFE91E63) override val Primary = Color(0xFFE91E63)
override val Secondary = Color(0xFFD81B60) override val Secondary = Color(0xFFF06292)
override val Tertiary = Color(0xFF880E4F) override val Tertiary = Color(0xFF880E4F)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFF8BBD0) override val PrimaryContainer = Color(0xFFFCE4EC)
override val SecondaryContainer = Color(0xFFF48FB1) override val SecondaryContainer = Color(0xFFFCE4EC)
override val TertiaryContainer = Color(0xFFE91E63) override val TertiaryContainer = Color(0xFFF8BBD0)
override val OnPrimaryContainer = Color(0xFF2E0A14) override val OnPrimaryContainer = Color(0xFF3B0819)
override val OnSecondaryContainer = Color(0xFF2B0A13) override val OnSecondaryContainer = Color(0xFF3B0819)
override val OnTertiaryContainer = Color(0xFF1C0311) override val OnTertiaryContainer = Color(0xFF2B0516)
override val ButtonContrast = Color(0xFFFF1493) 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() { object Gray : ThemeColors() {
override val Primary = Color(0xFF9E9E9E) override val Primary = Color(0xFF607D8B)
override val Secondary = Color(0xFF757575) override val Secondary = Color(0xFF90A4AE)
override val Tertiary = Color(0xFF616161) override val Tertiary = Color(0xFF455A64)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFEEEEEE) override val PrimaryContainer = Color(0xFFECEFF1)
override val SecondaryContainer = Color(0xFFE0E0E0) override val SecondaryContainer = Color(0xFFECEFF1)
override val TertiaryContainer = Color(0xFFBDBDBD) override val TertiaryContainer = Color(0xFFCFD8DC)
override val OnPrimaryContainer = Color(0xFF1A1A1A) override val OnPrimaryContainer = Color(0xFF1A2327)
override val OnSecondaryContainer = Color(0xFF171717) override val OnSecondaryContainer = Color(0xFF1A2327)
override val OnTertiaryContainer = Color(0xFF141414) override val OnTertiaryContainer = Color(0xFF121A1D)
override val ButtonContrast = Color(0xFF696969) 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() { object Yellow : ThemeColors() {
override val Primary = Color(0xFFFFD700) override val Primary = Color(0xFFFFC107)
override val Secondary = Color(0xFFFFBC52) override val Secondary = Color(0xFFFFD54F)
override val Tertiary = Color(0xFF795548) override val Tertiary = Color(0xFFFF8F00)
override val OnPrimary = Color(0xFFFFFFFF) override val OnPrimary = Color(0xFF000000)
override val OnSecondary = Color(0xFFFFFFFF) override val OnSecondary = Color(0xFF000000)
override val OnTertiary = Color(0xFFFFFFFF) override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFF7D6) override val PrimaryContainer = Color(0xFFFFF8E1)
override val SecondaryContainer = Color(0xFFFFE6B3) override val SecondaryContainer = Color(0xFFFFF8E1)
override val TertiaryContainer = Color(0xFFD7CCC8) override val TertiaryContainer = Color(0xFFFFECB3)
override val OnPrimaryContainer = Color(0xFF1A1600) override val OnPrimaryContainer = Color(0xFF332A00)
override val OnSecondaryContainer = Color(0xFF1A1100) override val OnSecondaryContainer = Color(0xFF332A00)
override val OnTertiaryContainer = Color(0xFF1A1717) override val OnTertiaryContainer = Color(0xFF221200)
override val ButtonContrast = Color(0xFFFFD700) 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 { companion object {
@@ -163,7 +299,7 @@ sealed class ThemeColors {
"orange" -> Orange "orange" -> Orange
"pink" -> Pink "pink" -> Pink
"gray" -> Gray "gray" -> Gray
"white" -> Yellow "yellow" -> Yellow
else -> Default else -> Default
} }
} }

View File

@@ -5,6 +5,10 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log 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.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -14,19 +18,25 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable 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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.paint import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background 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 androidx.compose.ui.unit.dp
import java.io.File import java.io.File
import java.io.FileOutputStream 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.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground import com.sukisu.ultra.ui.util.saveTransformedBackground
/**
* 主题配置对象,管理应用的主题相关状态
*/
object ThemeConfig { object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null) var customBackgroundUri by mutableStateOf<Uri?>(null)
var forceDarkMode by mutableStateOf<Boolean?>(null) var forceDarkMode by mutableStateOf<Boolean?>(null)
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default) var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
var useDynamicColor by mutableStateOf(false) 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 @Composable
private fun getDarkColorScheme() = darkColorScheme( fun KernelSUTheme(
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f), darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f), true -> true
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f), false -> false
onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f), null -> isSystemInDarkTheme()
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f), },
onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f), dynamicColor: Boolean = ThemeConfig.useDynamicColor,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f), content: @Composable () -> Unit
onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f), ) {
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f), val context = LocalContext.current
onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f), val systemIsDark = isSystemInDarkTheme()
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f), // 检测系统主题变化并保存状态
background = Color.Transparent, val themeChanged = ThemeConfig.detectThemeChange(systemIsDark)
surface = Color.Transparent, LaunchedEffect(systemIsDark, themeChanged) {
onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f), if (ThemeConfig.forceDarkMode == null && themeChanged) {
onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f), Log.d("ThemeSystem", "系统主题变化检测: 从 ${!systemIsDark} 变为 $systemIsDark")
surfaceVariant = Color(0xFF2F2F2F), ThemeConfig.resetBackgroundState()
onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
outline = Color.White.copy(alpha = 0.12f), // 强制重新加载自定义背景
outlineVariant = Color.White.copy(alpha = 0.12f) 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 @Composable
private fun getLightColorScheme() = lightColorScheme( 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 = Color.White,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
onPrimaryContainer = Color.White,
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
onSecondary = Color.White,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
onSecondaryContainer = Color.White,
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
onTertiary = Color.White,
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
onTertiaryContainer = Color.White,
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White,
onSurface = Color.White,
surfaceVariant = Color(0xFF2F2F2F),
onSurfaceVariant = Color.White.copy(alpha = 0.7f),
outline = 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 createLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.Primary, primary = ThemeConfig.currentTheme.Primary,
onPrimary = ThemeConfig.currentTheme.OnPrimary, onPrimary = ThemeConfig.currentTheme.OnPrimary,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer, primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
@@ -88,163 +339,44 @@ private fun getLightColorScheme() = lightColorScheme(
surfaceVariant = Color(0xFFF5F5F5), surfaceVariant = Color(0xFFF5F5F5),
onSurfaceVariant = Color.Black.copy(alpha = 0.78f), onSurfaceVariant = Color.Black.copy(alpha = 0.78f),
outline = Color.Black.copy(alpha = 0.12f), 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? { private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try { return try {
val contentResolver: ContentResolver = contentResolver val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!! val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
val fileName = "custom_background.jpg" val fileName = "custom_background.jpg"
val file = File(filesDir, fileName) val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file) val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024) val buffer = ByteArray(4 * 1024)
var read: Int var read: Int
while (inputStream.read(buffer).also { read = it } != -1) { while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read) outputStream.write(buffer, 0, read)
} }
outputStream.flush() outputStream.flush()
outputStream.close() outputStream.close()
inputStream.close() inputStream.close()
return Uri.fromFile(file)
Uri.fromFile(file)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}") Log.e("ImageCopy", "复制图片失败: ${e.message}")
return null null
} }
} }
// 保存变换后的背景图片到应用内部存储并更新配置 /**
* 保存并应用自定义背景
*/
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) { fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
val finalUri = if (transformation != null) { val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation) saveTransformedBackground(uri, transformation)
@@ -258,30 +390,49 @@ fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTra
} }
ThemeConfig.customBackgroundUri = finalUri ThemeConfig.customBackgroundUri = finalUri
ThemeConfig.backgroundImageLoaded = false
CardConfig.cardElevation = 0.dp CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true CardConfig.isCustomBackgroundEnabled = true
} }
// 保存背景图片到应用内部存储并更新配置 /**
* 保存自定义背景
*/
fun Context.saveCustomBackground(uri: Uri?) { fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) } val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit { .edit {
putString("custom_background", newUri?.toString()) putString("custom_background", newUri?.toString())
} }
ThemeConfig.customBackgroundUri = newUri ThemeConfig.customBackgroundUri = newUri
ThemeConfig.backgroundImageLoaded = false
if (uri != null) { if (uri != null) {
CardConfig.cardElevation = 0.dp CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true CardConfig.isCustomBackgroundEnabled = true
} }
} }
/**
* 加载自定义背景
*/
fun Context.loadCustomBackground() { fun Context.loadCustomBackground() {
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("custom_background", null) .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?) { fun Context.saveThemeMode(forceDark: Boolean?) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit { .edit {
@@ -294,52 +445,49 @@ fun Context.saveThemeMode(forceDark: Boolean?) {
) )
} }
ThemeConfig.forceDarkMode = forceDark ThemeConfig.forceDarkMode = forceDark
ThemeConfig.needsResetOnThemeChange = forceDark == null
} }
/**
* 加载主题模式
*/
fun Context.loadThemeMode() { fun Context.loadThemeMode() {
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_mode", "system") .getString("theme_mode", "system")
ThemeConfig.forceDarkMode = when(mode) { ThemeConfig.forceDarkMode = when(mode) {
"dark" -> true "dark" -> true
"light" -> false "light" -> false
else -> null else -> null
} }
ThemeConfig.needsResetOnThemeChange = ThemeConfig.forceDarkMode == null
} }
/**
* 保存主题颜色
*/
fun Context.saveThemeColors(themeName: String) { fun Context.saveThemeColors(themeName: String) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit { .edit {
putString("theme_colors", themeName) putString("theme_colors", themeName)
} }
ThemeConfig.currentTheme = when(themeName) { ThemeConfig.currentTheme = ThemeColors.fromName(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
}
} }
/**
* 加载主题颜色
*/
fun Context.loadThemeColors() { fun Context.loadThemeColors() {
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_colors", "default") .getString("theme_colors", "default")
ThemeConfig.currentTheme = when(themeName) { ThemeConfig.currentTheme = ThemeColors.fromName(themeName ?: "default")
"blue" -> ThemeColors.Blue
"green" -> ThemeColors.Green
"purple" -> ThemeColors.Purple
"orange" -> ThemeColors.Orange
"pink" -> ThemeColors.Pink
"gray" -> ThemeColors.Gray
"yellow" -> ThemeColors.Yellow
else -> ThemeColors.Default
}
} }
/**
* 保存动态颜色状态
*/
fun Context.saveDynamicColorState(enabled: Boolean) { fun Context.saveDynamicColorState(enabled: Boolean) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit { .edit {
@@ -348,28 +496,12 @@ fun Context.saveDynamicColorState(enabled: Boolean) {
ThemeConfig.useDynamicColor = enabled ThemeConfig.useDynamicColor = enabled
} }
/**
* 加载动态颜色状态
*/
fun Context.loadDynamicColorState() { fun Context.loadDynamicColorState() {
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getBoolean("use_dynamic_color", true) .getBoolean("use_dynamic_color", true)
ThemeConfig.useDynamicColor = enabled 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 package com.sukisu.ultra.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with val Typography = Typography(
val Typography = androidx.compose.material3.Typography( // 大标题
bodyLarge = TextStyle( displayLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 16.sp, fontSize = 57.sp,
lineHeight = 24.sp, lineHeight = 64.sp,
letterSpacing = 0.5.sp letterSpacing = (-0.25).sp
) ),
/* Other default text styles to override 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( titleLarge = TextStyle(
fontFamily = FontFamily.Default, fontWeight = FontWeight.SemiBold,
fontWeight = FontWeight.Normal,
fontSize = 22.sp, fontSize = 22.sp,
lineHeight = 28.sp, lineHeight = 28.sp,
letterSpacing = 0.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( labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 11.sp, fontSize = 11.sp,
lineHeight = 16.sp, lineHeight = 16.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
) )
*/
) )

View File

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

View File

@@ -297,4 +297,25 @@
<string name="GKI_install_methods">GKI安装</string> <string name="GKI_install_methods">GKI安装</string>
<string name="kernel_version_log">内核版本:%1$s</string> <string name="kernel_version_log">内核版本:%1$s</string>
<string name="tool_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> </resources>

View File

@@ -301,4 +301,25 @@
<string name="GKI_install_methods">GKI installation</string> <string name="GKI_install_methods">GKI installation</string>
<string name="kernel_version_log">Kernel version%1$s</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="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> </resources>