Rename files and update package structure

Add tool classes related to displaying and refreshing data
This commit is contained in:
ShirkNeko
2025-06-03 01:02:08 +08:00
parent d58ec6952c
commit 1d34ea4995
12 changed files with 522 additions and 398 deletions

View File

@@ -1,60 +1,36 @@
package com.sukisu.ultra.ui package com.sukisu.ultra.ui
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
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.core.tween
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
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.platform.LocalContext import androidx.lifecycle.lifecycleScope
import androidx.compose.ui.res.stringResource import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
import com.ramcosta.composedestinations.spec.NavHostGraphSpec import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import io.sukisu.ultra.UltraToolInstall import io.sukisu.ultra.UltraToolInstall
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.data.AppData import zako.zako.zako.zakoui.activity.util.AppData
import com.sukisu.ultra.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.* import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha import zako.zako.zako.zakoui.activity.util.*
import com.sukisu.ultra.ui.util.* import zako.zako.zako.zakoui.activity.component.BottomBar
import androidx.core.content.edit import com.sukisu.ultra.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.data.AppData.DataRefreshManager import com.sukisu.ultra.ui.util.install
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.initPlatform
import java.util.Locale
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.lifecycle.lifecycleScope
import com.sukisu.ultra.ui.data.AppData.getKpmVersionUse
import com.sukisu.ultra.ui.viewmodel.HomeViewModel import com.sukisu.ultra.ui.viewmodel.HomeViewModel
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import kotlinx.coroutines.delay import com.sukisu.ultra.ui.webui.initPlatform
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var superUserViewModel: SuperUserViewModel private lateinit var superUserViewModel: SuperUserViewModel
@@ -66,62 +42,19 @@ class MainActivity : ComponentActivity() {
val showKpmInfo: Boolean = true val showKpmInfo: Boolean = true
) )
private inner class ThemeChangeContentObserver( private lateinit var themeChangeObserver: ThemeChangeContentObserver
handler: Handler,
private val onThemeChanged: () -> Unit
) : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
onThemeChanged()
}
}
// 应用保存的语言设置
@SuppressLint("ObsoleteSdkInt")
private fun applyLanguageSetting() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
override fun attachBaseContext(newBase: Context) { override fun attachBaseContext(newBase: Context) {
val prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE) val context = LocaleUtils.applyLocale(newBase)
val languageCode = prefs.getString("app_language", "") ?: ""
var context = newBase
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(newBase.resources.configuration)
config.setLocale(locale)
context = newBase.createConfigurationContext(config)
}
super.attachBaseContext(context) super.attachBaseContext(context)
} }
@SuppressLint("RestrictedApi")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// 确保应用正确的语言设置 // 确保应用正确的语言设置
applyLanguageSetting() LocaleUtils.applyLanguageSetting(this)
// 应用自定义 DPI // 应用自定义 DPI
applyCustomDpi() DisplayUtils.applyCustomDpi(this)
// Enable edge to edge // Enable edge to edge
enableEdgeToEdge() enableEdgeToEdge()
@@ -148,51 +81,15 @@ class MainActivity : ComponentActivity() {
} }
// 数据刷新协程 // 数据刷新协程
startDataRefreshCoroutine() DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
startSettingsMonitorCoroutine() DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
val prefs = getSharedPreferences("settings", MODE_PRIVATE) // 初始化主题相关设置
val isFirstRun = prefs.getBoolean("is_first_run", true) ThemeUtils.initializeThemeSettings(this, settingsStateFlow)
settingsStateFlow.value = SettingsState( // 设置主题变化监听器
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false), themeChangeObserver = ThemeUtils.registerThemeChangeObserver(this)
showKpmInfo = prefs.getBoolean("show_kpm_info", true)
)
if (isFirstRun) {
ThemeConfig.preventBackgroundRefresh = false
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", false)
}
prefs.edit { putBoolean("is_first_run", false) }
}
// 加载保存的背景设置
loadThemeMode()
loadThemeColors()
loadDynamicColorState()
CardConfig.load(applicationContext)
val contentObserver = ThemeChangeContentObserver(Handler(mainLooper)) {
runOnUiThread {
if (!ThemeConfig.preventBackgroundRefresh) {
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 = AppData.isManager(ksuApp.packageName) val isManager = AppData.isManager(ksuApp.packageName)
if (isManager) { if (isManager) {
@@ -221,13 +118,10 @@ class MainActivity : ComponentActivity() {
) { ) {
Scaffold( Scaffold(
bottomBar = { bottomBar = {
AnimatedVisibility( AnimatedBottomBar.AnimatedBottomBarWrapper(
visible = showBottomBar, showBottomBar = showBottomBar,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), content = { BottomBar(navController) }
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut() )
) {
BottomBar(navController)
}
}, },
contentWindowInsets = WindowInsets(0, 0, 0, 0) contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding -> ) { innerPadding ->
@@ -235,12 +129,7 @@ class MainActivity : ComponentActivity() {
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 = NavigationUtils.defaultTransitions()
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
get() = { fadeIn(animationSpec = tween(340)) }
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
get() = { fadeOut(animationSpec = tween(340)) }
}
) )
} }
} }
@@ -248,64 +137,15 @@ class MainActivity : ComponentActivity() {
} }
} }
// 数据刷新协程
private fun startDataRefreshCoroutine() {
lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
DataRefreshManager.refreshData()
delay(5000)
}
}
}
private fun startSettingsMonitorCoroutine() {
lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
settingsStateFlow.value = SettingsState(
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false),
showKpmInfo = prefs.getBoolean("show_kpm_info", true)
)
delay(1000)
}
}
}
// 应用自定义DPI设置
private fun applyCustomDpi() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val customDpi = prefs.getInt("app_dpi", 0)
if (customDpi > 0) {
try {
val resources = resources
val metrics = resources.displayMetrics
metrics.density = customDpi / 160f
@Suppress("DEPRECATION")
metrics.scaledDensity = customDpi / 160f
metrics.densityDpi = customDpi
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
CardConfig.save(applicationContext) ThemeUtils.onActivityPause(this)
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", true)
}
ThemeConfig.preventBackgroundRefresh = true
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
applyLanguageSetting() LocaleUtils.applyLanguageSetting(this)
ThemeUtils.onActivityResume()
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
loadCustomBackground()
}
lifecycleScope.launch { lifecycleScope.launch {
superUserViewModel.fetchAppList() superUserViewModel.fetchAppList()
@@ -315,213 +155,16 @@ class MainActivity : ComponentActivity() {
homeViewModel.initializeData() homeViewModel.initializeData()
} }
lifecycleScope.launch { DataRefreshUtils.refreshData(lifecycleScope)
DataRefreshManager.refreshData()
}
} }
private val destroyListeners = mutableListOf<() -> Unit>()
override fun onDestroy() { override fun onDestroy() {
destroyListeners.forEach { it() } ThemeUtils.unregisterThemeChangeObserver(this, themeChangeObserver)
super.onDestroy() super.onDestroy()
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
applyLanguageSetting() LocaleUtils.applyLanguageSetting(this)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName)
val kpmVersion = getKpmVersionUse()
val cardColor = MaterialTheme.colorScheme.surfaceContainer
val activity = LocalContext.current as MainActivity
val settings by activity.settingsStateFlow.collectAsState()
// 检查是否隐藏红点
val isHideOtherInfo = settings.isHideOtherInfo
val showKpmInfo = settings.showKpmInfo
// 收集计数数据
val superuserCount by DataRefreshManager.superuserCount.collectAsState()
val moduleCount by DataRefreshManager.moduleCount.collectAsState()
val kpmModuleCount by DataRefreshManager.kpmModuleCount.collectAsState()
NavigationBar(
modifier = Modifier.windowInsetsPadding(
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
),
containerColor = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
).containerColor,
tonalElevation = cardElevation
) {
BottomBarDestination.entries.forEach { destination ->
if (destination == BottomBarDestination.Kpm) {
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
if (!isFullFeatured && 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 = {
BadgedBox(
badge = {
if (kpmModuleCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary
) {
Text(
text = kpmModuleCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
}
} else if (destination == BottomBarDestination.SuperUser) {
if (!isFullFeatured && 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) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
BadgedBox(
badge = {
if (superuserCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary
) {
Text(
text = superuserCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
} else if (destination == BottomBarDestination.Module) {
if (!isFullFeatured && 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) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
BadgedBox(
badge = {
if (moduleCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary ) {
Text(
text = moduleCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
} else {
if (!isFullFeatured && 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) {
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),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
}
}
} }
} }

View File

@@ -0,0 +1,222 @@
package zako.zako.zako.zakoui.activity.component
import android.annotation.SuppressLint
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavHostController
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.generated.NavGraphs
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.MainActivity
import zako.zako.zako.zakoui.activity.util.AppData
import zako.zako.zako.zakoui.activity.util.AppData.getKpmVersionUse
import com.sukisu.ultra.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.navigationBars
import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager
@SuppressLint("ContextCastToActivity")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName)
val kpmVersion = getKpmVersionUse()
val cardColor = MaterialTheme.colorScheme.surfaceContainer
val activity = LocalContext.current as MainActivity
val settings by activity.settingsStateFlow.collectAsState()
// 检查是否隐藏红点
val isHideOtherInfo = settings.isHideOtherInfo
val showKpmInfo = settings.showKpmInfo
// 收集计数数据
val superuserCount by DataRefreshManager.superuserCount.collectAsState()
val moduleCount by DataRefreshManager.moduleCount.collectAsState()
val kpmModuleCount by DataRefreshManager.kpmModuleCount.collectAsState()
NavigationBar(
modifier = Modifier.windowInsetsPadding(
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
),
containerColor = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
).containerColor,
tonalElevation = cardElevation
) {
BottomBarDestination.entries.forEach { destination ->
if (destination == BottomBarDestination.Kpm) {
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
if (!isFullFeatured && 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 = {
BadgedBox(
badge = {
if (kpmModuleCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary
) {
Text(
text = kpmModuleCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
}
} else if (destination == BottomBarDestination.SuperUser) {
if (!isFullFeatured && 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) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
BadgedBox(
badge = {
if (superuserCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary
) {
Text(
text = superuserCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
} else if (destination == BottomBarDestination.Module) {
if (!isFullFeatured && 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) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
BadgedBox(
badge = {
if (moduleCount > 0 && !isHideOtherInfo) {
Badge(
containerColor = MaterialTheme.colorScheme.secondary)
{
Text(
text = moduleCount.toString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
) {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
}
},
label = { Text(stringResource(destination.label),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
} else {
if (!isFullFeatured && 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) {
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),style = MaterialTheme.typography.labelMedium) },
alwaysShowLabel = false
)
}
}
}
}

View File

@@ -0,0 +1,24 @@
package zako.zako.zako.zakoui.activity.util
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.runtime.Composable
object AnimatedBottomBar {
@Composable
fun AnimatedBottomBarWrapper(
showBottomBar: Boolean,
content: @Composable () -> Unit
) {
AnimatedVisibility(
visible = showBottomBar,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
) {
content()
}
}
}

View File

@@ -1,16 +1,16 @@
package com.sukisu.ultra.ui.data package zako.zako.zako.zakoui.activity.util
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.util.getKpmModuleCount import com.sukisu.ultra.ui.util.getKpmModuleCount
import com.sukisu.ultra.ui.util.getKpmVersion import com.sukisu.ultra.ui.util.getKpmVersion
import com.sukisu.ultra.ui.util.getModuleCount
import com.sukisu.ultra.ui.util.getSuperuserCount
import com.sukisu.ultra.ui.util.rootAvailable import com.sukisu.ultra.ui.util.rootAvailable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.ui.util.getModuleCount
import com.sukisu.ultra.ui.util.getSuperuserCount
object AppData { object AppData {
object DataRefreshManager { object DataRefreshManager {

View File

@@ -0,0 +1,46 @@
package zako.zako.zako.zakoui.activity.util
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
import com.sukisu.ultra.ui.MainActivity
import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
object DataRefreshUtils {
fun startDataRefreshCoroutine(scope: LifecycleCoroutineScope) {
scope.launch(Dispatchers.IO) {
while (isActive) {
DataRefreshManager.refreshData()
delay(5000)
}
}
}
fun startSettingsMonitorCoroutine(
scope: LifecycleCoroutineScope,
activity: MainActivity,
settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>
) {
scope.launch(Dispatchers.IO) {
while (isActive) {
val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE)
settingsStateFlow.value = MainActivity.SettingsState(
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false),
showKpmInfo = prefs.getBoolean("show_kpm_info", true)
)
delay(1000)
}
}
}
fun refreshData(scope: LifecycleCoroutineScope) {
scope.launch {
DataRefreshManager.refreshData()
}
}
}

View File

@@ -0,0 +1,24 @@
package zako.zako.zako.zakoui.activity.util
import android.content.Context
object DisplayUtils {
fun applyCustomDpi(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val customDpi = prefs.getInt("app_dpi", 0)
if (customDpi > 0) {
try {
val resources = context.resources
val metrics = resources.displayMetrics
metrics.density = customDpi / 160f
@Suppress("DEPRECATION")
metrics.scaledDensity = customDpi / 160f
metrics.densityDpi = customDpi
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View File

@@ -0,0 +1,48 @@
package zako.zako.zako.zakoui.activity.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import java.util.Locale
object LocaleUtils {
@SuppressLint("ObsoleteSdkInt")
fun applyLanguageSetting(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = context.resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
fun applyLocale(context: Context): Context {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
var newContext = context
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
newContext = context.createConfigurationContext(config)
}
return newContext
}
}

View File

@@ -0,0 +1,19 @@
package zako.zako.zako.zakoui.activity.util
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.navigation.NavBackStackEntry
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
object NavigationUtils {
fun defaultTransitions() = object : NavHostAnimatedDestinationStyle() {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
get() = { fadeIn(animationSpec = tween(340)) }
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
get() = { fadeOut(animationSpec = tween(340)) }
}
}

View File

@@ -0,0 +1,96 @@
package zako.zako.zako.zakoui.activity.util
import android.content.Context
import android.database.ContentObserver
import android.os.Handler
import androidx.core.content.edit
import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.theme.CardConfig
import com.sukisu.ultra.ui.theme.ThemeConfig
import kotlinx.coroutines.flow.MutableStateFlow
class ThemeChangeContentObserver(
handler: Handler,
private val onThemeChanged: () -> Unit
) : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
onThemeChanged()
}
}
object ThemeUtils {
fun initializeThemeSettings(activity: MainActivity, settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>) {
val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE)
val isFirstRun = prefs.getBoolean("is_first_run", true)
settingsStateFlow.value = MainActivity.SettingsState(
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false),
showKpmInfo = prefs.getBoolean("show_kpm_info", true)
)
if (isFirstRun) {
ThemeConfig.preventBackgroundRefresh = false
activity.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", false)
}
prefs.edit { putBoolean("is_first_run", false) }
}
// 加载保存的背景设置
loadThemeMode()
loadThemeColors()
loadDynamicColorState()
CardConfig.load(activity.applicationContext)
}
fun registerThemeChangeObserver(activity: MainActivity): ThemeChangeContentObserver {
val contentObserver = ThemeChangeContentObserver(Handler(activity.mainLooper)) {
activity.runOnUiThread {
if (!ThemeConfig.preventBackgroundRefresh) {
ThemeConfig.backgroundImageLoaded = false
loadCustomBackground()
}
}
}
activity.contentResolver.registerContentObserver(
android.provider.Settings.System.getUriFor("ui_night_mode"),
false,
contentObserver
)
return contentObserver
}
fun unregisterThemeChangeObserver(activity: MainActivity, observer: ThemeChangeContentObserver) {
activity.contentResolver.unregisterContentObserver(observer)
}
fun onActivityPause(activity: MainActivity) {
CardConfig.save(activity.applicationContext)
activity.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", true)
}
ThemeConfig.preventBackgroundRefresh = true
}
fun onActivityResume() {
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
loadCustomBackground()
}
}
private fun loadThemeMode() {
}
private fun loadThemeColors() {
}
private fun loadDynamicColorState() {
}
private fun loadCustomBackground() {
}
}

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.flash package zako.zako.zako.zakoui.flash
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.zakoui.screen
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
@@ -29,8 +29,8 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.flash.HorizonKernelState import zako.zako.zako.zakoui.flash.HorizonKernelState
import com.sukisu.ultra.flash.HorizonKernelWorker import zako.zako.zako.zakoui.flash.HorizonKernelWorker
import com.sukisu.ultra.ui.component.KeyEventBlocker import com.sukisu.ultra.ui.component.KeyEventBlocker
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -42,6 +42,8 @@ import java.util.*
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
import com.sukisu.ultra.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.CardConfig
import zako.zako.zako.zakoui.flash.FlashState
import kotlinx.coroutines.delay
/** /**
* @author ShirkNeko * @author ShirkNeko
@@ -117,7 +119,7 @@ fun KernelFlashScreen(
logContent.clear() logContent.clear()
logContent.append(logText) logContent.append(logText)
} }
kotlinx.coroutines.delay(100) delay(100)
} }
if (flashState.error.isNotEmpty()) { if (flashState.error.isNotEmpty()) {
@@ -239,7 +241,7 @@ fun KernelFlashScreen(
} }
@Composable @Composable
private fun FlashProgressIndicator(flashState: com.sukisu.ultra.flash.FlashState) { private fun FlashProgressIndicator(flashState: FlashState) {
val progressColor = when { val progressColor = when {
flashState.error.isNotEmpty() -> MaterialTheme.colorScheme.error flashState.error.isNotEmpty() -> MaterialTheme.colorScheme.error
flashState.isCompleted -> MaterialTheme.colorScheme.tertiary flashState.isCompleted -> MaterialTheme.colorScheme.tertiary
@@ -355,7 +357,7 @@ private fun FlashProgressIndicator(flashState: com.sukisu.ultra.flash.FlashState
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar( private fun TopBar(
flashState: com.sukisu.ultra.flash.FlashState, flashState: FlashState,
onBack: () -> Unit, onBack: () -> Unit,
onSave: () -> Unit = {}, onSave: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.zakoui.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity