Add Compose reboot menu to Home and fix navigation icon padding

Replace the View-based RebootMenu with a native Compose
SuperListPopup in the Home top bar, with proper state tracking
for the safe mode toggle. Remove the now-unused RebootMenu.kt.

Also fix missing start padding on the SuperuserDetail back button
to match all other sub-screens.

Made-with: Cursor
This commit is contained in:
LoveSy
2026-03-03 22:39:18 +08:00
committed by topjohnwu
parent 125d2caffd
commit d8d64c8a75
3 changed files with 93 additions and 54 deletions

View File

@@ -3,8 +3,11 @@ package com.topjohnwu.magisk.ui.home
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.PowerManager
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
@@ -20,6 +23,10 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -27,18 +34,28 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.reboot
import com.topjohnwu.magisk.ui.component.ListPopupDefaults.MenuPositionProvider
import com.topjohnwu.magisk.core.R as CoreR
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.DropdownImpl
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.IconButton
import top.yukonga.miuix.kmp.basic.LinearProgressIndicator
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
import top.yukonga.miuix.kmp.basic.PopupPositionProvider
import top.yukonga.miuix.kmp.basic.Scaffold
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextButton
import top.yukonga.miuix.kmp.basic.TopAppBar
import top.yukonga.miuix.kmp.extra.SuperListPopup
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable
@@ -51,7 +68,12 @@ fun HomeScreen(viewModel: HomeViewModel) {
topBar = {
TopAppBar(
title = stringResource(CoreR.string.section_home),
scrollBehavior = scrollBehavior
scrollBehavior = scrollBehavior,
actions = {
if (Info.isRooted) {
RebootButton()
}
}
)
},
popupHost = { }
@@ -88,6 +110,72 @@ fun HomeScreen(viewModel: HomeViewModel) {
}
}
@Composable
private fun RebootButton() {
val showMenu = remember { mutableStateOf(false) }
val context = LocalContext.current
var safeModeEnabled by remember { mutableIntStateOf(Config.bootloop) }
val showUserspace = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
context.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true
val showSafeMode = Const.Version.atLeast_28_0()
val items = buildList {
add(RebootOption(CoreR.string.reboot) { reboot() })
if (showUserspace) {
add(RebootOption(CoreR.string.reboot_userspace) { reboot("userspace") })
}
add(RebootOption(CoreR.string.reboot_recovery) { reboot("recovery") })
add(RebootOption(CoreR.string.reboot_bootloader) { reboot("bootloader") })
add(RebootOption(CoreR.string.reboot_download) { reboot("download") })
add(RebootOption(CoreR.string.reboot_edl) { reboot("edl") })
if (showSafeMode) {
add(RebootOption(CoreR.string.reboot_safe_mode) {
val newVal = if (safeModeEnabled >= 2) 0 else 2
Config.bootloop = newVal
safeModeEnabled = newVal
})
}
}
Box {
IconButton(
modifier = Modifier.padding(end = 16.dp),
onClick = { showMenu.value = true },
holdDownState = showMenu.value,
) {
Icon(
painter = painterResource(R.drawable.ic_restart),
contentDescription = stringResource(CoreR.string.reboot),
)
}
SuperListPopup(
show = showMenu,
popupPositionProvider = MenuPositionProvider,
alignment = PopupPositionProvider.Align.End,
onDismissRequest = { showMenu.value = false }
) {
ListPopupColumn {
items.forEachIndexed { index, item ->
val isSafeMode = item.labelRes == CoreR.string.reboot_safe_mode
DropdownImpl(
text = stringResource(item.labelRes),
optionSize = items.size,
isSelected = isSafeMode && safeModeEnabled >= 2,
index = index,
onSelectedIndexChange = {
item.action()
if (!isSafeMode) showMenu.value = false
}
)
}
}
}
}
}
private class RebootOption(val labelRes: Int, val action: () -> Unit)
@Composable
private fun NoticeCard(onHide: () -> Unit) {
Card(modifier = Modifier.fillMaxWidth()) {

View File

@@ -1,52 +0,0 @@
package com.topjohnwu.magisk.ui.home
import android.app.Activity
import android.os.Build
import android.os.PowerManager
import android.view.ContextThemeWrapper
import android.view.MenuItem
import android.widget.PopupMenu
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.ktx.reboot as systemReboot
object RebootMenu {
private fun reboot(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_reboot_normal -> systemReboot()
R.id.action_reboot_userspace -> systemReboot("userspace")
R.id.action_reboot_bootloader -> systemReboot("bootloader")
R.id.action_reboot_download -> systemReboot("download")
R.id.action_reboot_edl -> systemReboot("edl")
R.id.action_reboot_recovery -> systemReboot("recovery")
R.id.action_reboot_safe_mode -> {
val status = !item.isChecked
item.isChecked = status
Config.bootloop = if (status) 2 else 0
}
else -> Unit
}
return true
}
fun inflate(activity: Activity): PopupMenu {
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
val menu = PopupMenu(themeWrapper, activity.window.decorView)
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
menu.setOnMenuItemClickListener(RebootMenu::reboot)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
}
if (Const.Version.atLeast_28_0()) {
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
} else {
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false
}
return menu
}
}

View File

@@ -55,7 +55,10 @@ fun SuperuserDetailScreen(
TopAppBar(
title = stringResource(CoreR.string.superuser_setting),
navigationIcon = {
IconButton(onClick = onBack) {
IconButton(
modifier = Modifier.padding(start = 16.dp),
onClick = onBack
) {
Icon(
imageVector = MiuixIcons.Back,
contentDescription = stringResource(CoreR.string.back),