Stand alone theme configuration for webuiX
- Add secondary color interface: isSecondaryPage (bool)
This commit is contained in:
@@ -546,9 +546,6 @@ fun Context.loadDynamicColorState() {
|
|||||||
ThemeConfig.useDynamicColor = enabled
|
ThemeConfig.useDynamicColor = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* webui X样式
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SystemBarStyle(
|
private fun SystemBarStyle(
|
||||||
darkMode: Boolean,
|
darkMode: Boolean,
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.paint
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
|
import com.sukisu.ultra.ui.theme.Typography
|
||||||
|
import com.sukisu.ultra.ui.theme.loadCustomBackground
|
||||||
|
|
||||||
|
// 提供界面类型的本地组合
|
||||||
|
val LocalIsSecondaryScreen = staticCompositionLocalOf { false }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebUI专用主题配置
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun WebUIXTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
isSecondaryScreen: Boolean = false,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
context.loadCustomBackground()
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
if (darkTheme) {
|
||||||
|
dynamicDarkColorScheme(context).let { scheme ->
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
scheme.copy(
|
||||||
|
background = scheme.surfaceContainerHighest,
|
||||||
|
surface = scheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
scheme.copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicLightColorScheme(context).let { scheme ->
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
scheme.copy(
|
||||||
|
background = scheme.surfaceContainerHighest,
|
||||||
|
surface = scheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
scheme.copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
darkTheme -> {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
darkColorScheme().copy(
|
||||||
|
background = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
surface = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
darkColorScheme().copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
lightColorScheme().copy(
|
||||||
|
background = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
surface = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lightColorScheme().copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureSystemBars(darkTheme)
|
||||||
|
|
||||||
|
val backgroundUri = remember { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
||||||
|
|
||||||
|
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
||||||
|
backgroundUri.value = ThemeConfig.customBackgroundUri
|
||||||
|
}
|
||||||
|
val bgImagePainter = backgroundUri.value?.let {
|
||||||
|
rememberAsyncImagePainter(
|
||||||
|
model = it,
|
||||||
|
onError = {
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
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 }
|
||||||
|
CompositionLocalProvider(LocalIsSecondaryScreen provides isSecondaryScreen) {
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
) {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前界面是否为二级界面
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun isSecondaryScreen(): Boolean {
|
||||||
|
return LocalIsSecondaryScreen.current
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置WebUI的系统栏样式
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ConfigureSystemBars(
|
||||||
|
darkMode: Boolean,
|
||||||
|
statusBarScrim: Color = Color.Transparent,
|
||||||
|
navigationBarScrim: Color = Color.Transparent
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as ComponentActivity
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
activity.enableEdgeToEdge(
|
||||||
|
statusBarStyle = SystemBarStyle.auto(
|
||||||
|
statusBarScrim.toArgb(),
|
||||||
|
statusBarScrim.toArgb()
|
||||||
|
) { darkMode },
|
||||||
|
navigationBarStyle = when {
|
||||||
|
darkMode -> SystemBarStyle.dark(
|
||||||
|
navigationBarScrim.toArgb()
|
||||||
|
)
|
||||||
|
else -> SystemBarStyle.light(
|
||||||
|
navigationBarScrim.toArgb(),
|
||||||
|
navigationBarScrim.toArgb()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,6 @@ import com.dergoogler.mmrl.ui.component.Loading
|
|||||||
import com.dergoogler.mmrl.webui.screen.WebUIScreen
|
import com.dergoogler.mmrl.webui.screen.WebUIScreen
|
||||||
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
|
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
|
||||||
import com.sukisu.ultra.BuildConfig
|
import com.sukisu.ultra.BuildConfig
|
||||||
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ class WebUIXActivity : ComponentActivity() {
|
|||||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
KernelSUTheme {
|
WebUIXTheme {
|
||||||
var isLoading by remember { mutableStateOf(true) }
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
LaunchedEffect(Platform.isAlive) {
|
LaunchedEffect(Platform.isAlive) {
|
||||||
@@ -82,8 +81,7 @@ class WebUIXActivity : ComponentActivity() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
Loading()
|
Loading()
|
||||||
|
return@WebUIXTheme
|
||||||
return@KernelSUTheme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.sukisu.ultra.ui.util.controlKpmModule
|
|||||||
import com.sukisu.ultra.ui.util.listKpmModules
|
import com.sukisu.ultra.ui.util.listKpmModules
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
class WebViewInterface(
|
class WebViewInterface(
|
||||||
wxOptions: WXOptions,
|
wxOptions: WXOptions,
|
||||||
@@ -36,6 +37,12 @@ class WebViewInterface(
|
|||||||
|
|
||||||
private val modDir get() = "/data/adb/modules/${modId.id}"
|
private val modDir get() = "/data/adb/modules/${modId.id}"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@JavascriptInterface
|
||||||
|
fun isSecondaryPage(): Boolean {
|
||||||
|
return isSecondaryScreen()
|
||||||
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun exec(cmd: String): String {
|
fun exec(cmd: String): String {
|
||||||
return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) }
|
return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) }
|
||||||
|
|||||||
Reference in New Issue
Block a user