Refactor the UI to rewrite the interface (#61)
This commit is contained in:
@@ -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,78 +145,112 @@ 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
|
||||||
|
|
||||||
NavigationBar(
|
Surface(
|
||||||
tonalElevation = cardElevation, // 动态设置阴影
|
modifier = Modifier
|
||||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
.fillMaxWidth()
|
||||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
.clip(RoundedCornerShape(cornerRadius)),
|
||||||
)
|
color = containerColor.copy(alpha = 0.95f),
|
||||||
|
tonalElevation = 0.dp
|
||||||
) {
|
) {
|
||||||
BottomBarDestination.entries.forEach { destination ->
|
NavigationBar(
|
||||||
if (destination == BottomBarDestination.Kpm) {
|
modifier = Modifier.windowInsetsPadding(
|
||||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||||
|
),
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
tonalElevation = 0.dp
|
||||||
|
) {
|
||||||
|
BottomBarDestination.entries.forEach { destination ->
|
||||||
|
if (destination == BottomBarDestination.Kpm) {
|
||||||
|
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
||||||
|
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||||
|
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = isCurrentDestOnBackStack,
|
||||||
|
onClick = {
|
||||||
|
if (!isCurrentDestOnBackStack) {
|
||||||
|
navigator.navigate(destination.direction) {
|
||||||
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isCurrentDestOnBackStack) {
|
||||||
|
destination.iconSelected
|
||||||
|
} else {
|
||||||
|
destination.iconNotSelected
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(destination.label),
|
||||||
|
tint = if (isCurrentDestOnBackStack) selectedColor else unselectedColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(destination.label),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = NavigationBarItemDefaults.colors(
|
||||||
|
selectedIconColor = selectedColor,
|
||||||
|
unselectedIconColor = unselectedColor,
|
||||||
|
selectedTextColor = selectedColor,
|
||||||
|
unselectedTextColor = unselectedColor,
|
||||||
|
indicatorColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = isCurrentDestOnBackStack,
|
selected = isCurrentDestOnBackStack,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isCurrentDestOnBackStack) {
|
if (!isCurrentDestOnBackStack) {
|
||||||
navigator.popBackStack(destination.direction, false)
|
navigator.navigate(destination.direction) {
|
||||||
}
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
navigator.navigate(destination.direction) {
|
saveState = true
|
||||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
}
|
||||||
saveState = true
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
if (isCurrentDestOnBackStack) {
|
Icon(
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
imageVector = if (isCurrentDestOnBackStack) {
|
||||||
} else {
|
destination.iconSelected
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
} else {
|
||||||
}
|
destination.iconNotSelected
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(destination.label),
|
||||||
|
tint = if (isCurrentDestOnBackStack) selectedColor else unselectedColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(destination.label),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
},
|
},
|
||||||
label = { Text(stringResource(destination.label)) },
|
|
||||||
alwaysShowLabel = false,
|
|
||||||
colors = NavigationBarItemDefaults.colors(
|
colors = NavigationBarItemDefaults.colors(
|
||||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
selectedIconColor = selectedColor,
|
||||||
|
unselectedIconColor = unselectedColor,
|
||||||
|
selectedTextColor = selectedColor,
|
||||||
|
unselectedTextColor = unselectedColor,
|
||||||
|
indicatorColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
|
||||||
NavigationBarItem(
|
|
||||||
selected = isCurrentDestOnBackStack,
|
|
||||||
onClick = {
|
|
||||||
if (isCurrentDestOnBackStack) {
|
|
||||||
navigator.popBackStack(destination.direction, false)
|
|
||||||
}
|
|
||||||
navigator.navigate(destination.direction) {
|
|
||||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
|
||||||
saveState = true
|
|
||||||
}
|
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
if (isCurrentDestOnBackStack) {
|
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
|
||||||
} else {
|
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label = { Text(stringResource(destination.label)) },
|
|
||||||
alwaysShowLabel = false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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,46 +239,53 @@ 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 {
|
||||||
updateDialog.showConfirm(
|
updateDialog.showConfirm(
|
||||||
title = title,
|
title = title,
|
||||||
content = changelog,
|
content = changelog,
|
||||||
markdown = true,
|
markdown = true,
|
||||||
confirm = updateText
|
confirm = updateText
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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)
|
||||||
if (kernelVersion.isGKI()) {
|
.shadow(
|
||||||
onClickInstall()
|
elevation = 0.dp,
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(enabled = kernelVersion.isGKI()) {
|
||||||
|
if (kernelVersion.isGKI()) {
|
||||||
|
onClickInstall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.padding(24.dp),
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
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
|
||||||
|
|
||||||
Row(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clip(MaterialTheme.shapes.large)
|
||||||
uriHandler.openUri(url)
|
.shadow(
|
||||||
}
|
elevation = 0.dp,
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
uriHandler.openUri(url)
|
||||||
|
}
|
||||||
|
.padding(24.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.School,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(28.dp)
|
||||||
|
)
|
||||||
|
|
||||||
Column {
|
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
|
||||||
|
|
||||||
Row(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clip(MaterialTheme.shapes.large)
|
||||||
uriHandler.openUri("https://patreon.com/weishu")
|
.shadow(
|
||||||
}
|
elevation = 0.dp,
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
uriHandler.openUri("https://patreon.com/weishu")
|
||||||
|
}
|
||||||
|
.padding(24.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Favorite,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(28.dp)
|
||||||
|
)
|
||||||
|
|
||||||
Column {
|
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,25 +864,28 @@ 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)
|
||||||
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
|
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
|
||||||
if (isSUS_SU) {
|
if (isSUS_SU) {
|
||||||
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
|
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
|
||||||
if (susSUMode.isNotEmpty()) {
|
if (susSUMode.isNotEmpty()) {
|
||||||
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
|
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfoCardItem(
|
||||||
|
stringResource(R.string.home_susfs_version),
|
||||||
|
infoText,
|
||||||
|
icon = Icons.Default.Storage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
InfoCardItem(
|
|
||||||
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,7 +901,7 @@ fun getManagerVersion(context: Context): Pair<String, Long> {
|
|||||||
@Preview
|
@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 = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,39 +301,78 @@ 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) {
|
||||||
Row(
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
.clip(MaterialTheme.shapes.medium)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = stringResource(R.string.kernel_module_notice),
|
modifier = Modifier
|
||||||
modifier = Modifier.weight(1f),
|
.fillMaxWidth()
|
||||||
textAlign = TextAlign.Center
|
.padding(16.dp),
|
||||||
)
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
IconButton(onClick = {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
isNoticeClosed = true
|
) {
|
||||||
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
|
|
||||||
}) {
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Filled.Info,
|
||||||
contentDescription = stringResource(R.string.close_notice)
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.kernel_module_notice),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
isNoticeClosed = true
|
||||||
|
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = stringResource(R.string.close_notice)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,15 +382,30 @@ fun KpmScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
stringResource(R.string.kpm_empty),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
textAlign = TextAlign.Center
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Code,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.kpm_empty),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,23 +269,34 @@ 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 {
|
||||||
putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
|
putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.fetchModuleList()
|
viewModel.fetchModuleList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
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,10 +378,25 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
stringResource(R.string.module_magisk_conflict),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
textAlign = TextAlign.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.module_magisk_conflict),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -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, "", {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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,237 +110,413 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// region 配置项列表
|
|
||||||
// 配置文件模板入口
|
|
||||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
|
||||||
if (ksuIsValid) {
|
|
||||||
ListItem(
|
|
||||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
|
||||||
headlineContent = { Text(profileTemplate) },
|
|
||||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 卸载模块开关
|
|
||||||
var umountChecked by rememberSaveable {
|
|
||||||
mutableStateOf(Natives.isDefaultUmountModules())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuIsValid) {
|
// 设置分组卡片 - 配置
|
||||||
SwitchItem(
|
Card(
|
||||||
icon = Icons.Filled.FolderDelete,
|
modifier = Modifier
|
||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
.fillMaxWidth()
|
||||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
checked = umountChecked
|
colors = CardDefaults.cardColors(
|
||||||
) {
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
if (Natives.setDefaultUmountModules(it)) {
|
),
|
||||||
umountChecked = it
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
}
|
) {
|
||||||
}
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
}
|
Text(
|
||||||
// SU 禁用开关(仅在兼容版本显示)
|
text = stringResource(R.string.configuration),
|
||||||
if (ksuIsValid) {
|
style = MaterialTheme.typography.titleMedium,
|
||||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
color = MaterialTheme.colorScheme.primary,
|
||||||
var isSuDisabled by rememberSaveable {
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
mutableStateOf(!Natives.isSuEnabled())
|
)
|
||||||
|
|
||||||
|
// 配置文件模板入口
|
||||||
|
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||||
|
if (ksuIsValid) {
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Fence,
|
||||||
|
title = profileTemplate,
|
||||||
|
summary = stringResource(id = R.string.settings_profile_template_summary),
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.RemoveModerator,
|
// 卸载模块开关
|
||||||
title = stringResource(id = R.string.settings_disable_su),
|
var umountChecked by rememberSaveable {
|
||||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
mutableStateOf(Natives.isDefaultUmountModules())
|
||||||
checked = isSuDisabled,
|
}
|
||||||
) { checked ->
|
|
||||||
val shouldEnable = !checked
|
if (ksuIsValid) {
|
||||||
if (Natives.setSuEnabled(shouldEnable)) {
|
SwitchSettingItem(
|
||||||
isSuDisabled = !shouldEnable
|
icon = Icons.Filled.FolderDelete,
|
||||||
|
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||||
|
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||||
|
checked = umountChecked,
|
||||||
|
onCheckedChange = {
|
||||||
|
if (Natives.setDefaultUmountModules(it)) {
|
||||||
|
umountChecked = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SU 禁用开关(仅在兼容版本显示)
|
||||||
|
if (ksuIsValid) {
|
||||||
|
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||||
|
var isSuDisabled by rememberSaveable {
|
||||||
|
mutableStateOf(!Natives.isSuEnabled())
|
||||||
|
}
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.RemoveModerator,
|
||||||
|
title = stringResource(id = R.string.settings_disable_su),
|
||||||
|
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||||
|
checked = isSuDisabled,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
val shouldEnable = !checked
|
||||||
|
if (Natives.setSuEnabled(shouldEnable)) {
|
||||||
|
isSuDisabled = !shouldEnable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
// 设置分组卡片 - 应用设置
|
||||||
|
Card(
|
||||||
// 更新检查开关
|
modifier = Modifier
|
||||||
var checkUpdate by rememberSaveable {
|
.fillMaxWidth()
|
||||||
mutableStateOf(
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
prefs.getBoolean("check_update", true)
|
colors = CardDefaults.cardColors(
|
||||||
)
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
}
|
),
|
||||||
SwitchItem(
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
icon = Icons.Filled.Update,
|
|
||||||
title = stringResource(id = R.string.settings_check_update),
|
|
||||||
summary = stringResource(id = R.string.settings_check_update_summary),
|
|
||||||
checked = checkUpdate
|
|
||||||
) {
|
) {
|
||||||
prefs.edit {putBoolean("check_update", it) }
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
checkUpdate = it
|
Text(
|
||||||
}
|
text = stringResource(R.string.app_settings),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
// Web调试开关
|
color = MaterialTheme.colorScheme.primary,
|
||||||
var enableWebDebugging by rememberSaveable {
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("enable_web_debugging", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.DeveloperMode,
|
|
||||||
title = stringResource(id = R.string.enable_web_debugging),
|
|
||||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
|
||||||
checked = enableWebDebugging
|
|
||||||
) {
|
|
||||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
|
||||||
enableWebDebugging = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 更多设置
|
|
||||||
val newButtonTitle = stringResource(id = R.string.more_settings)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Settings,
|
|
||||||
contentDescription = newButtonTitle
|
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(newButtonTitle) },
|
|
||||||
supportingContent = { Text(stringResource(id = R.string.more_settings)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(MoreSettingsScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var showBottomsheet by remember { mutableStateOf(false) }
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
ListItem(
|
// 更新检查开关
|
||||||
leadingContent = {
|
var checkUpdate by rememberSaveable {
|
||||||
Icon(
|
mutableStateOf(
|
||||||
Icons.Filled.BugReport,
|
prefs.getBoolean("check_update", true)
|
||||||
stringResource(id = R.string.send_log)
|
)
|
||||||
|
}
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.Update,
|
||||||
|
title = stringResource(id = R.string.settings_check_update),
|
||||||
|
summary = stringResource(id = R.string.settings_check_update_summary),
|
||||||
|
checked = checkUpdate,
|
||||||
|
onCheckedChange = {
|
||||||
|
prefs.edit {putBoolean("check_update", it) }
|
||||||
|
checkUpdate = it
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
showBottomsheet = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (showBottomsheet) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { showBottomsheet = false },
|
|
||||||
content = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
|
|
||||||
|
// Web调试开关
|
||||||
|
var enableWebDebugging by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("enable_web_debugging", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.DeveloperMode,
|
||||||
|
title = stringResource(id = R.string.enable_web_debugging),
|
||||||
|
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||||
|
checked = enableWebDebugging,
|
||||||
|
onCheckedChange = {
|
||||||
|
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||||
|
enableWebDebugging = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更多设置
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Settings,
|
||||||
|
title = stringResource(id = R.string.more_settings),
|
||||||
|
summary = stringResource(id = R.string.more_settings),
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(MoreSettingsScreenDestination)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置分组卡片 - 工具
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.tools),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var showBottomsheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.BugReport,
|
||||||
|
title = stringResource(id = R.string.send_log),
|
||||||
|
onClick = {
|
||||||
|
showBottomsheet = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showBottomsheet) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showBottomsheet = false },
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
) {
|
) {
|
||||||
Box {
|
Row(
|
||||||
Column(
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp),
|
||||||
.clickable {
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
) {
|
||||||
val current = LocalDateTime.now().format(formatter)
|
LogActionButton(
|
||||||
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
icon = Icons.Filled.Save,
|
||||||
|
text = stringResource(R.string.save_log),
|
||||||
|
onClick = {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||||
|
val current = LocalDateTime.now().format(formatter)
|
||||||
|
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
||||||
|
showBottomsheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LogActionButton(
|
||||||
|
icon = Icons.Filled.Share,
|
||||||
|
text = stringResource(R.string.send_log),
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val bugreport = loadingDialog.withLoading {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
getBugreportFile(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri: Uri =
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
||||||
|
bugreport
|
||||||
|
)
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
setDataAndType(uri, "application/gzip")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
shareIntent,
|
||||||
|
context.getString(R.string.send_log)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
showBottomsheet = false
|
showBottomsheet = false
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
Icon(
|
)
|
||||||
Icons.Filled.Save,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.save_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val bugreport = loadingDialog.withLoading {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
getBugreportFile(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri: Uri =
|
|
||||||
FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
|
||||||
bugreport
|
|
||||||
)
|
|
||||||
|
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
setDataAndType(uri, "application/gzip")
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(
|
|
||||||
Intent.createChooser(
|
|
||||||
shareIntent,
|
|
||||||
context.getString(R.string.send_log)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Share,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.send_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||||
if (lkmMode) {
|
if (lkmMode) {
|
||||||
UninstallItem(navigator) {
|
UninstallItem(navigator) {
|
||||||
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Info,
|
||||||
|
title = stringResource(R.string.about),
|
||||||
|
onClick = {
|
||||||
|
aboutDialog.show()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(about) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
aboutDialog.show()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LogActionButton(
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(56.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = text,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
summary: String? = null,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (summary != null) {
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SwitchSettingItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
summary: String? = null,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onCheckedChange(!checked) }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (summary != null) {
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedIconColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,16 +553,11 @@ fun UninstallItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val uninstall = stringResource(id = R.string.settings_uninstall)
|
|
||||||
ListItem(
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,44 +87,81 @@ 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 = {
|
||||||
scope.launch {
|
Icon(
|
||||||
viewModel.fetchAppList()
|
imageVector = Icons.Filled.Refresh,
|
||||||
}
|
contentDescription = null,
|
||||||
showDropdown = false
|
tint = MaterialTheme.colorScheme.primary
|
||||||
})
|
)
|
||||||
DropdownMenuItem(text = {
|
},
|
||||||
Text(
|
onClick = {
|
||||||
if (viewModel.showSystemApps) {
|
scope.launch {
|
||||||
stringResource(R.string.hide_system_apps)
|
viewModel.fetchAppList()
|
||||||
} else {
|
|
||||||
stringResource(R.string.show_system_apps)
|
|
||||||
}
|
}
|
||||||
)
|
showDropdown = false
|
||||||
}, onClick = {
|
}
|
||||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
)
|
||||||
showDropdown = false
|
DropdownMenuItem(
|
||||||
})
|
text = {
|
||||||
DropdownMenuItem(text = {
|
Text(
|
||||||
Text(stringResource(R.string.backup_allowlist))
|
if (viewModel.showSystemApps) {
|
||||||
}, onClick = {
|
stringResource(R.string.hide_system_apps)
|
||||||
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
|
} else {
|
||||||
showDropdown = false
|
stringResource(R.string.show_system_apps)
|
||||||
})
|
}
|
||||||
DropdownMenuItem(text = {
|
)
|
||||||
Text(stringResource(R.string.restore_allowlist))
|
},
|
||||||
}, onClick = {
|
leadingIcon = {
|
||||||
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
|
Icon(
|
||||||
showDropdown = false
|
imageVector = if (viewModel.showSystemApps)
|
||||||
})
|
Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
HorizontalDivider(thickness = 0.5.dp, modifier = Modifier.padding(vertical = 4.dp))
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.backup_allowlist)) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Save,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.restore_allowlist)) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.RestoreFromTrash,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -128,32 +172,81 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
// 批量操作按钮,直接放在底部栏
|
// 批量操作按钮,直接放在底部栏
|
||||||
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
AnimatedVisibility(
|
||||||
Row(
|
visible = viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty(),
|
||||||
modifier = Modifier
|
enter = slideInVertically(initialOffsetY = { it }),
|
||||||
.fillMaxWidth()
|
exit = slideOutVertically(targetOffsetY = { it })
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
) {
|
||||||
.padding(16.dp),
|
Surface(
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
tonalElevation = 0.dp,
|
||||||
|
shadowElevation = 0.dp
|
||||||
) {
|
) {
|
||||||
Button(
|
Row(
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
scope.launch {
|
.fillMaxWidth()
|
||||||
viewModel.updateBatchPermissions(true)
|
.padding(16.dp),
|
||||||
}
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.batch_authorization))
|
OutlinedButton(
|
||||||
}
|
onClick = {
|
||||||
|
// 修改为重新赋值为空集合
|
||||||
|
viewModel.selectedApps = emptySet()
|
||||||
|
viewModel.showBatchActions = false
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.updateBatchPermissions(false)
|
viewModel.updateBatchPermissions(true)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(stringResource(R.string.batch_authorization))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.updateBatchPermissions(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Block,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(stringResource(R.string.batch_cancel_authorization))
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.batch_cancel_authorization))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,15 +263,23 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
state = listState,
|
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) {
|
|
||||||
LabelText(label = "CUSTOM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
) {
|
||||||
leadingContent = {
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
cardElevation = 0.dp
|
}
|
||||||
|
if (!isShadowEnabled) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
||||||
|
true -> true
|
||||||
|
false -> false
|
||||||
|
null -> isSystemInDarkTheme()
|
||||||
|
},
|
||||||
|
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val systemIsDark = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
// 检测系统主题变化并保存状态
|
||||||
|
val themeChanged = ThemeConfig.detectThemeChange(systemIsDark)
|
||||||
|
LaunchedEffect(systemIsDark, themeChanged) {
|
||||||
|
if (ThemeConfig.forceDarkMode == null && themeChanged) {
|
||||||
|
Log.d("ThemeSystem", "系统主题变化检测: 从 ${!systemIsDark} 变为 $systemIsDark")
|
||||||
|
ThemeConfig.resetBackgroundState()
|
||||||
|
|
||||||
|
// 强制重新加载自定义背景
|
||||||
|
context.loadCustomBackground()
|
||||||
|
|
||||||
|
// 调整卡片样式以适应新主题
|
||||||
|
CardConfig.apply {
|
||||||
|
load(context)
|
||||||
|
if (!isCustomAlphaSet) {
|
||||||
|
cardAlpha = if (systemIsDark) 0.35f else 0.80f
|
||||||
|
}
|
||||||
|
save(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始加载配置
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
context.loadCustomBackground()
|
||||||
|
context.loadThemeColors()
|
||||||
|
context.loadDynamicColorState()
|
||||||
|
context.loadThemeMode()
|
||||||
|
CardConfig.load(context)
|
||||||
|
|
||||||
|
// 立即将加载状态设为false,确保首次会触发加载动画
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建颜色方案
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
darkTheme -> createDarkColorScheme()
|
||||||
|
else -> createLightColorScheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据暗色模式和自定义背景调整卡片配置
|
||||||
|
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
|
||||||
|
if (darkTheme && !dynamicColor) {
|
||||||
|
CardConfig.setDarkModeDefaults()
|
||||||
|
}
|
||||||
|
CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground)
|
||||||
|
|
||||||
|
// 使用rememberSaveable保留背景URI状态,防止在主题切换时丢失
|
||||||
|
val backgroundUri = rememberSaveable { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
||||||
|
|
||||||
|
// 确保状态同步
|
||||||
|
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
||||||
|
backgroundUri.value = ThemeConfig.customBackgroundUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// 背景图加载器 - 使用保存的URI状态
|
||||||
|
val bgImagePainter = backgroundUri.value?.let {
|
||||||
|
rememberAsyncImagePainter(
|
||||||
|
model = it,
|
||||||
|
onError = {
|
||||||
|
Log.e("ThemeSystem", "背景图加载失败: ${it.result.throwable.message}")
|
||||||
|
ThemeConfig.customBackgroundUri = null
|
||||||
|
context.saveCustomBackground(null)
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
Log.d("ThemeSystem", "背景图加载成功")
|
||||||
|
ThemeConfig.backgroundImageLoaded = true
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 背景透明度动画 - 使用更强健的动画配置
|
||||||
|
val transition = updateTransition(
|
||||||
|
targetState = ThemeConfig.backgroundImageLoaded,
|
||||||
|
label = "bgTransition"
|
||||||
|
)
|
||||||
|
val bgAlpha by transition.animateFloat(
|
||||||
|
label = "bgAlpha",
|
||||||
|
transitionSpec = {
|
||||||
|
spring(
|
||||||
|
dampingRatio = 0.8f,
|
||||||
|
stiffness = 300f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { loaded -> if (loaded) 1f else 0f }
|
||||||
|
|
||||||
|
// 清理函数,确保主题切换完成后重置状态
|
||||||
|
DisposableEffect(systemIsDark) {
|
||||||
|
onDispose {
|
||||||
|
if (ThemeConfig.isThemeChanging) {
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
// 底色层 - 确保有底色
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-2f)
|
||||||
|
.background(if (darkTheme) Color.Black else Color.White)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自定义背景层
|
||||||
|
backgroundUri.value?.let { uri ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-1f)
|
||||||
|
.alpha(bgAlpha)
|
||||||
|
) {
|
||||||
|
// 背景图片
|
||||||
|
bgImagePainter?.let { painter ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.paint(
|
||||||
|
painter = painter,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 亮度调节层
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.6f)
|
||||||
|
else Color.White.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 边缘渐变遮罩
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.5f)
|
||||||
|
else Color.Black.copy(alpha = 0.2f)
|
||||||
|
),
|
||||||
|
radius = 1200f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容层
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(1f)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态深色颜色方案
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
@Composable
|
||||||
|
private fun createDynamicDarkColorScheme(context: Context) =
|
||||||
|
dynamicDarkColorScheme(context).copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent,
|
||||||
|
onBackground = Color.White,
|
||||||
|
onSurface = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态浅色颜色方案
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
@Composable
|
||||||
|
private fun createDynamicLightColorScheme(context: Context) =
|
||||||
|
dynamicLightColorScheme(context).copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建深色颜色方案
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun createDarkColorScheme() = darkColorScheme(
|
||||||
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
|
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
|
||||||
onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onPrimary = Color.White,
|
||||||
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
|
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
|
||||||
onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onPrimaryContainer = Color.White,
|
||||||
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
|
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
|
||||||
onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
onSecondary = Color.White,
|
||||||
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
|
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
|
||||||
onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
onSecondaryContainer = Color.White,
|
||||||
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
|
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
|
||||||
onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
onTertiary = Color.White,
|
||||||
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
|
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
|
||||||
onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
onTertiaryContainer = Color.White,
|
||||||
background = Color.Transparent,
|
background = Color.Transparent,
|
||||||
surface = Color.Transparent,
|
surface = Color.Transparent,
|
||||||
onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
onBackground = Color.White,
|
||||||
onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
onSurface = Color.White,
|
||||||
surfaceVariant = Color(0xFF2F2F2F),
|
surfaceVariant = Color(0xFF2F2F2F),
|
||||||
onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onSurfaceVariant = Color.White.copy(alpha = 0.7f),
|
||||||
outline = Color.White.copy(alpha = 0.12f),
|
outline = Color.White.copy(alpha = 0.12f),
|
||||||
outlineVariant = Color.White.copy(alpha = 0.12f)
|
outlineVariant = Color.White.copy(alpha = 0.12f),
|
||||||
|
error = ThemeConfig.currentTheme.Error,
|
||||||
|
onError = ThemeConfig.currentTheme.OnError,
|
||||||
|
errorContainer = ThemeConfig.currentTheme.ErrorContainer.copy(alpha = 0.15f),
|
||||||
|
onErrorContainer = Color.White
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建浅色颜色方案
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun getLightColorScheme() = lightColorScheme(
|
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)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user