manager: make action execution screen have the same behavior as Magisk

based on pr https://github.com/tiann/KernelSU/pull/2321

* Magisk's behavior: Hide Bottom Navbar, Show close button if failed or success
and removed automatic exit when module execution success.
This commit is contained in:
Rifat Azad
2025-05-19 21:15:53 +07:00
committed by ShirkNeko
parent d408c9f4bf
commit cfdbba45c3
5 changed files with 69 additions and 28 deletions

View File

@@ -24,6 +24,7 @@ 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.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.spec.NavHostGraphSpec import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import com.ramcosta.composedestinations.spec.RouteOrDirection import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
@@ -39,6 +40,10 @@ import androidx.core.content.edit
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.initPlatform import com.sukisu.ultra.ui.webui.initPlatform
import java.util.Locale import java.util.Locale
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.navigation.compose.currentBackStackEntryAsState
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private inner class ThemeChangeContentObserver( private inner class ThemeChangeContentObserver(
@@ -94,6 +99,7 @@ class MainActivity : ComponentActivity() {
// 确保应用正确的语言设置 // 确保应用正确的语言设置
applyLanguageSetting() applyLanguageSetting()
// 应用自定义 DPI
applyCustomDpi() applyCustomDpi()
// Enable edge to edge // Enable edge to edge
@@ -152,6 +158,12 @@ class MainActivity : ComponentActivity() {
KernelSUTheme { KernelSUTheme {
val navController = rememberNavController() val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
val currentDestination = navController.currentBackStackEntryAsState().value?.destination
val showBottomBar = when (currentDestination?.route) {
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
else -> true
}
// pre-init platform to faster start WebUI X activities // pre-init platform to faster start WebUI X activities
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -159,7 +171,15 @@ class MainActivity : ComponentActivity() {
} }
Scaffold( Scaffold(
bottomBar = { BottomBar(navController) }, bottomBar = {
AnimatedVisibility(
visible = showBottomBar,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
) {
BottomBar(navController)
}
},
contentWindowInsets = WindowInsets(0, 0, 0, 0) contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding -> ) { innerPadding ->
CompositionLocalProvider( CompositionLocalProvider(

View File

@@ -1,15 +1,22 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.os.Environment import android.os.Environment
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.only
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -30,7 +37,6 @@ import androidx.compose.ui.input.key.key
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination 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
@@ -55,7 +61,11 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
var actionResult: Boolean var isActionRunning by rememberSaveable { mutableStateOf(true) }
BackHandler(enabled = isActionRunning) {
// Disable back button if action is running
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
@@ -76,33 +86,43 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
onStderr = { onStderr = {
logContent.append(it).append("\n") logContent.append(it).append("\n")
} }
).let { )
actionResult = it
}
} }
if (actionResult) navigator.popBackStack() isActionRunning = false
} }
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
onBack = dropUnlessResumed { isActionRunning = isActionRunning,
navigator.popBackStack()
},
onSave = { onSave = {
scope.launch { if (!isActionRunning) {
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()) scope.launch {
val date = format.format(Date()) val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
val file = File( val date = format.format(Date())
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), val file = File(
"KernelSU_module_action_log_${date}.log" Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
) "KernelSU_module_action_log_${date}.log"
file.writeText(logContent.toString()) )
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}") file.writeText(logContent.toString())
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
}
} }
} }
) )
}, },
floatingActionButton = {
if (!isActionRunning) {
ExtendedFloatingActionButton(
text = { Text(text = stringResource(R.string.close)) },
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
onClick = {
navigator.popBackStack()
}
)
}
},
contentWindowInsets = WindowInsets.safeDrawing,
snackbarHost = { SnackbarHost(snackBarHost) } snackbarHost = { SnackbarHost(snackBarHost) }
) { innerPadding -> ) { innerPadding ->
KeyEventBlocker { KeyEventBlocker {
@@ -130,16 +150,14 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) { private fun TopBar(isActionRunning: Boolean, onSave: () -> Unit = {}) {
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.action)) }, title = { Text(stringResource(R.string.action)) },
navigationIcon = {
IconButton(
onClick = onBack
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
},
actions = { actions = {
IconButton(onClick = onSave) { IconButton(
onClick = onSave,
enabled = !isActionRunning
) {
Icon( Icon(
imageVector = Icons.Filled.Save, imageVector = Icons.Filled.Save,
contentDescription = stringResource(id = R.string.save_log), contentDescription = stringResource(id = R.string.save_log),
@@ -147,4 +165,4 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
} }
} }
) )
} }

View File

@@ -112,6 +112,7 @@
<string name="install_inactive_slot">Instal ke slot nonaktif (setelah OTA)</string> <string name="install_inactive_slot">Instal ke slot nonaktif (setelah OTA)</string>
<string name="grant_root_failed">Gagal memberikan akses root!</string> <string name="grant_root_failed">Gagal memberikan akses root!</string>
<string name="open">Buka</string> <string name="open">Buka</string>
<string name="close">Tutup</string>
<string name="settings_check_update">Cek terbaru</string> <string name="settings_check_update">Cek terbaru</string>
<string name="settings_check_update_summary">Cek terbaru setiap membuka aplikasi</string> <string name="settings_check_update_summary">Cek terbaru setiap membuka aplikasi</string>
<string name="settings_uninstall_permanent_message">Hapus permanen KernelSU (root dan modul).</string> <string name="settings_uninstall_permanent_message">Hapus permanen KernelSU (root dan modul).</string>

View File

@@ -113,6 +113,7 @@
<string name="grant_root_failed">获取 root 失败!</string> <string name="grant_root_failed">获取 root 失败!</string>
<string name="action">执行</string> <string name="action">执行</string>
<string name="open">打开</string> <string name="open">打开</string>
<string name="close">关闭</string>
<string name="enable_web_debugging">启用 WebView 调试</string> <string name="enable_web_debugging">启用 WebView 调试</string>
<string name="enable_web_debugging_summary">可用于调试 WebUI 。请仅在需要时启用。</string> <string name="enable_web_debugging_summary">可用于调试 WebUI 。请仅在需要时启用。</string>
<string name="direct_install">直接安装(推荐)</string> <string name="direct_install">直接安装(推荐)</string>

View File

@@ -115,6 +115,7 @@
<string name="grant_root_failed">Failed to grant root!</string> <string name="grant_root_failed">Failed to grant root!</string>
<string name="action">Action</string> <string name="action">Action</string>
<string name="open">Open</string> <string name="open">Open</string>
<string name="close">Close</string>
<string name="enable_web_debugging">Enable WebView debugging</string> <string name="enable_web_debugging">Enable WebView debugging</string>
<string name="enable_web_debugging_summary">Can be used to debug WebUI. Please enable only when needed.</string> <string name="enable_web_debugging_summary">Can be used to debug WebUI. Please enable only when needed.</string>
<string name="direct_install">Direct install (Recommended)</string> <string name="direct_install">Direct install (Recommended)</string>