manager: Refactor the click logic of ModuleItem (#2105)

Drop `com.google.accompanist` that we needn't it

Remove unused metadata, abi

Optimize app icon (No visual changes)

Update Gradle to 8.10.2

Enable per app language support

Optimize `SwitchItem`


https://github.com/user-attachments/assets/777729e6-5108-4060-91a7-28b5b9d98441

Refactor the click logic of `ModuleItem`


https://github.com/user-attachments/assets/e61da54a-6c1c-45d7-bf27-52b452134b7e

Use compose's Text in AboutCard to support dynamicColor


![Screenshot_20241001-094116](https://github.com/user-attachments/assets/9882a4c1-719d-4622-a316-063cf349a753)

Add scroll behavior for TopAppBar


![Screenshot_20241001-133657.png](https://github.com/user-attachments/assets/1a884648-bc91-4f8a-9940-f2a5f0b8f6da)

![Screenshot_20241001-133645.png](https://github.com/user-attachments/assets/cd4712d3-a2c7-47f1-bba4-4312b779f6b1)

Fix padding for BottomNavigationBar
This commit is contained in:
Light_summer
2024-10-01 20:40:16 +08:00
committed by GitHub
parent 7be82d29ee
commit 60fcd27b84
24 changed files with 363 additions and 179 deletions

3
manager/.gitignore vendored
View File

@@ -1,9 +1,10 @@
*.iml *.iml
.gradle .gradle
local.properties
.idea .idea
.kotlin
.DS_Store .DS_Store
build build
captures captures
.cxx .cxx
local.properties
key.jks key.jks

View File

@@ -1,4 +1,7 @@
@file:Suppress("UnstableApiUsage")
import com.android.build.gradle.internal.api.BaseVariantOutputImpl import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import com.android.build.gradle.tasks.PackageAndroidArtifact
plugins { plugins {
alias(libs.plugins.agp.app) alias(libs.plugins.agp.app)
@@ -46,7 +49,13 @@ android {
useLegacyPackaging = true useLegacyPackaging = true
} }
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" // https://stackoverflow.com/a/58956288
// It will break Layout Inspector, but it's unused for release build.
excludes += "META-INF/*.version"
// https://github.com/Kotlin/kotlinx.coroutines?tab=readme-ov-file#avoiding-including-the-debug-infrastructure-in-the-resulting-apk
excludes += "DebugProbesKt.bin"
// https://issueantenna.com/repo/kotlin/kotlinx.coroutines/issues/3158
excludes += "kotlin-tooling-metadata.json"
} }
} }
@@ -67,6 +76,20 @@ android {
} }
} }
} }
// https://stackoverflow.com/a/77745844
tasks.withType<PackageAndroidArtifact> {
doFirst { appMetadata.asFile.orNull?.writeText("") }
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
androidResources {
generateLocaleConfig = true
}
} }
dependencies { dependencies {
@@ -87,10 +110,6 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.com.google.accompanist.drawablepainter)
implementation(libs.com.google.accompanist.navigation.animation)
implementation(libs.com.google.accompanist.webview)
implementation(libs.compose.destinations.core) implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.ksp) ksp(libs.compose.destinations.ksp)

View File

@@ -1,9 +0,0 @@
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@@ -12,8 +12,8 @@
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
tools:targetApi="34"> tools:targetApi="34">
<activity <activity
@@ -24,13 +24,10 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity> </activity>
<activity android:name=".ui.webui.WebUIActivity" <activity
android:name=".ui.webui.WebUIActivity"
android:autoRemoveFromRecents="true" android:autoRemoveFromRecents="true"
android:documentLaunchMode="intoExisting" android:documentLaunchMode="intoExisting"
android:exported="false" android:exported="false"

View File

@@ -7,9 +7,11 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
@@ -80,7 +82,9 @@ private fun BottomBar(navController: NavHostController) {
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
NavigationBar( NavigationBar(
tonalElevation = 8.dp, tonalElevation = 8.dp,
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
) { ) {
BottomBarDestination.entries.forEach { destination -> BottomBarDestination.entries.forEach { destination ->
if (!fullFeatured && destination.rootRequired) return@forEach if (!fullFeatured && destination.rootRequired) return@forEach

View File

@@ -1,7 +1,5 @@
package me.weishu.kernelsu.ui.component package me.weishu.kernelsu.ui.component
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -11,24 +9,28 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.text.style.TextDecoration
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.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.core.content.res.ResourcesCompat
import androidx.core.text.HtmlCompat
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import me.weishu.kernelsu.BuildConfig import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
@@ -36,9 +38,8 @@ import me.weishu.kernelsu.R
@Composable @Composable
fun AboutCard() { fun AboutCard() {
ElevatedCard( ElevatedCard(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth(), shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(8.dp),
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -52,7 +53,9 @@ fun AboutCard() {
@Composable @Composable
fun AboutDialog(dismiss: () -> Unit) { fun AboutDialog(dismiss: () -> Unit) {
Dialog(onDismissRequest = { dismiss() }) { Dialog(
onDismissRequest = { dismiss() }
) {
AboutCard() AboutCard()
} }
} }
@@ -60,21 +63,20 @@ fun AboutDialog(dismiss: () -> Unit) {
@Composable @Composable
private fun AboutCardContent() { private fun AboutCardContent() {
Column( Column(
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
) { ) {
val drawable = ResourcesCompat.getDrawable(
LocalContext.current.resources,
R.mipmap.ic_launcher,
LocalContext.current.theme
)
Row { Row {
Surface(
modifier = Modifier.size(40.dp),
color = colorResource(id = R.color.ic_launcher_background),
shape = CircleShape
) {
Image( Image(
painter = rememberDrawablePainter(drawable), painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "icon", contentDescription = "icon",
modifier = Modifier.size(40.dp) modifier = Modifier.scale(1.4f)
) )
}
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
@@ -93,31 +95,31 @@ private fun AboutCardContent() {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
HtmlText( val annotatedString = AnnotatedString.Companion.fromHtml(
html = stringResource( htmlString = stringResource(
id = R.string.about_source_code, id = R.string.about_source_code,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>", "<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>" "<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
),
linkStyles = TextLinkStyles(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
),
pressedStyle = SpanStyle(
color = MaterialTheme.colorScheme.primary,
background = MaterialTheme.colorScheme.secondaryContainer,
textDecoration = TextDecoration.Underline
)
)
)
Text(
text = annotatedString,
style = TextStyle(
fontSize = 14.sp
) )
) )
} }
} }
} }
} }
@Composable
fun HtmlText(html: String, modifier: Modifier = Modifier) {
val contentColor = LocalContentColor.current
AndroidView(
modifier = modifier,
factory = { context ->
TextView(context).also {
it.movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
it.setTextColor(contentColor.toArgb())
}
)
}

View File

@@ -23,6 +23,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -52,6 +53,7 @@ fun SearchAppBar(
onBackClick: (() -> Unit)? = null, onBackClick: (() -> Unit)? = null,
onConfirm: (() -> Unit)? = null, onConfirm: (() -> Unit)? = null,
dropdownContent: @Composable (() -> Unit)? = null, dropdownContent: @Composable (() -> Unit)? = null,
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@@ -137,10 +139,12 @@ fun SearchAppBar(
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Preview @Preview
@Composable @Composable
private fun SearchAppBarPreview() { private fun SearchAppBarPreview() {

View File

@@ -1,14 +1,18 @@
package me.weishu.kernelsu.ui.component package me.weishu.kernelsu.ui.component
import androidx.compose.foundation.clickable import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.semantics.Role
@Composable @Composable
fun SwitchItem( fun SwitchItem(
@@ -19,10 +23,18 @@ fun SwitchItem(
enabled: Boolean = true, enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) { ) {
val interactionSource = remember { MutableInteractionSource() }
ListItem( ListItem(
modifier = Modifier.clickable { modifier = Modifier
onCheckedChange.invoke(!checked) .toggleable(
}, value = checked,
interactionSource = interactionSource,
role = Role.Switch,
enabled = enabled,
indication = LocalIndication.current,
onValueChange = onCheckedChange
),
headlineContent = { headlineContent = {
Text(title) Text(title)
}, },
@@ -30,7 +42,12 @@ fun SwitchItem(
{ Icon(icon, title) } { Icon(icon, title) }
}, },
trailingContent = { trailingContent = {
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange) Switch(
checked = checked,
enabled = enabled,
onCheckedChange = onCheckedChange,
interactionSource = interactionSource
)
}, },
supportingContent = { supportingContent = {
if (summary != null) { if (summary != null) {
@@ -52,6 +69,6 @@ fun RadioItem(
}, },
leadingContent = { leadingContent = {
RadioButton(selected = selected, onClick = onClick) RadioButton(selected = selected, onClick = onClick)
}, }
) )
} }

View File

@@ -34,6 +34,8 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -43,6 +45,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@@ -78,6 +81,7 @@ import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
* @author weishu * @author weishu
* @date 2023/5/16. * @date 2023/5/16.
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun AppProfileScreen( fun AppProfileScreen(
@@ -86,6 +90,7 @@ fun AppProfileScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val snackbarHost = LocalSnackbarHost.current val snackbarHost = LocalSnackbarHost.current
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label) val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
val failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label) val failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
@@ -100,19 +105,24 @@ fun AppProfileScreen(
} }
Scaffold( Scaffold(
topBar = { TopBar { navigator.popBackStack() } }, topBar = {
TopBar(
onBack = { navigator.popBackStack() },
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { paddingValues -> ) { paddingValues ->
AppProfileInner( AppProfileInner(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .padding(paddingValues)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
packageName = appInfo.packageName, packageName = appInfo.packageName,
appLabel = appInfo.label, appLabel = appInfo.label,
appIcon = { appIcon = {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context).data(appInfo.packageInfo).crossfade(true) model = ImageRequest.Builder(context).data(appInfo.packageInfo).crossfade(true).build(),
.build(),
contentDescription = appInfo.label, contentDescription = appInfo.label,
modifier = Modifier modifier = Modifier
.padding(4.dp) .padding(4.dp)
@@ -242,7 +252,10 @@ private enum class Mode(@StringRes private val res: Int) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(onBack: () -> Unit) { private fun TopBar(
onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar( TopAppBar(
title = { title = {
Text(stringResource(R.string.profile)) Text(stringResource(R.string.profile))
@@ -252,7 +265,8 @@ private fun TopBar(onBack: () -> Unit) {
onClick = onBack onClick = onBack
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -24,6 +24,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -34,6 +37,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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 androidx.compose.ui.input.nestedscroll.nestedScroll
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.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -70,6 +74,7 @@ enum class FlashingStatus {
* @author weishu * @author weishu
* @date 2023/1/1. * @date 2023/1/1.
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Destination<RootGraph> @Destination<RootGraph>
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
@@ -81,6 +86,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var flashing by rememberSaveable { var flashing by rememberSaveable {
mutableStateOf(FlashingStatus.FLASHING) mutableStateOf(FlashingStatus.FLASHING)
} }
@@ -126,7 +132,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
file.writeText(logContent.toString()) file.writeText(logContent.toString())
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}") snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
} }
} },
scrollBehavior = scrollBehavior
) )
}, },
floatingActionButton = { floatingActionButton = {
@@ -154,6 +161,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
modifier = Modifier modifier = Modifier
.fillMaxSize(1f) .fillMaxSize(1f)
.padding(innerPadding) .padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(scrollState), .verticalScroll(scrollState),
) { ) {
LaunchedEffect(text) { LaunchedEffect(text) {
@@ -207,7 +215,12 @@ fun flashIt(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(status: FlashingStatus, onBack: () -> Unit = {}, onSave: () -> Unit = {}) { private fun TopBar(
status: FlashingStatus,
onBack: () -> Unit = {},
onSave: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
@@ -233,7 +246,8 @@ private fun TopBar(status: FlashingStatus, onBack: () -> Unit = {}, onSave: () -
) )
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -22,6 +22,7 @@ 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -41,26 +42,34 @@ import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.util.* import me.weishu.kernelsu.ui.util.*
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@Composable @Composable
fun HomeScreen(navigator: DestinationsNavigator) { fun HomeScreen(navigator: DestinationsNavigator) {
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
TopBar(kernelVersion, onSettingsClick = { TopBar(
kernelVersion,
onSettingsClick = {
navigator.navigate(SettingScreenDestination) navigator.navigate(SettingScreenDestination)
}, onInstallClick = { },
onInstallClick = {
navigator.navigate(InstallScreenDestination) navigator.navigate(InstallScreenDestination)
}) },
scrollBehavior = scrollBehavior
)
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = 16.dp) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
@@ -158,7 +167,8 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
private fun TopBar( private fun TopBar(
kernelVersion: KernelVersion, kernelVersion: KernelVersion,
onInstallClick: () -> Unit, onInstallClick: () -> Unit,
onSettingsClick: () -> Unit onSettingsClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.app_name)) }, title = { Text(stringResource(R.string.app_name)) },
@@ -187,8 +197,8 @@ private fun TopBar(
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")
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")
} }
@@ -206,7 +216,8 @@ private fun TopBar(
) )
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -6,7 +6,8 @@ import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -15,6 +16,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
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.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FileUpload import androidx.compose.material.icons.filled.FileUpload
@@ -27,6 +31,9 @@ import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -35,7 +42,9 @@ import androidx.compose.runtime.remember
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.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
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 com.maxkeppeker.sheets.core.models.base.Header import com.maxkeppeker.sheets.core.models.base.Header
@@ -63,6 +72,7 @@ import me.weishu.kernelsu.ui.util.rootAvailable
* @author weishu * @author weishu
* @date 2024/3/12. * @date 2024/3/12.
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun InstallScreen(navigator: DestinationsNavigator) { fun InstallScreen(navigator: DestinationsNavigator) {
@@ -118,15 +128,24 @@ fun InstallScreen(navigator: DestinationsNavigator) {
}) })
} }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
onBack = { navigator.popBackStack() }, onLkmUpload = onLkmUpload onBack = { navigator.popBackStack() },
onLkmUpload = onLkmUpload,
scrollBehavior = scrollBehavior
) )
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) { ) {
Column(modifier = Modifier.padding(it)) {
SelectInstallMethod { method -> SelectInstallMethod { method ->
installMethod = method installMethod = method
} }
@@ -239,16 +258,31 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
Column { Column {
radioOptions.forEach { option -> radioOptions.forEach { option ->
Row(verticalAlignment = Alignment.CenterVertically, val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .toggleable(
value = option.javaClass == selectedOption?.javaClass,
onValueChange = {
onClick(option) onClick(option)
}) { },
RadioButton(selected = option.javaClass == selectedOption?.javaClass, onClick = { role = Role.RadioButton,
indication = LocalIndication.current,
interactionSource = interactionSource
)
) {
RadioButton(
selected = option.javaClass == selectedOption?.javaClass,
onClick = {
onClick(option) onClick(option)
}) },
Column { interactionSource = interactionSource
)
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text( Text(
text = stringResource(id = option.label), text = stringResource(id = option.label),
fontSize = MaterialTheme.typography.titleMedium.fontSize, fontSize = MaterialTheme.typography.titleMedium.fontSize,
@@ -300,7 +334,11 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) { private fun TopBar(
onBack: () -> Unit = {},
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.install)) }, navigationIcon = { title = { Text(stringResource(R.string.install)) }, navigationIcon = {
IconButton( IconButton(
@@ -311,7 +349,8 @@ private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
Icon(Icons.Filled.FileUpload, contentDescription = null) Icon(Icons.Filled.FileUpload, contentDescription = null)
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -7,7 +7,8 @@ import android.util.Log
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.foundation.clickable import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -25,6 +26,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
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.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -45,6 +47,9 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -56,8 +61,10 @@ 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.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.semantics.Role
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.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
@@ -89,6 +96,7 @@ import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
import me.weishu.kernelsu.ui.webui.WebUIActivity import me.weishu.kernelsu.ui.webui.WebUIActivity
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun ModuleScreen(navigator: DestinationsNavigator) { fun ModuleScreen(navigator: DestinationsNavigator) {
@@ -106,9 +114,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val hideInstallButton = isSafeMode || hasMagisk val hideInstallButton = isSafeMode || hasMagisk
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
TopBar() TopBar(scrollBehavior = scrollBehavior)
}, },
floatingActionButton = { floatingActionButton = {
if (hideInstallButton) { if (hideInstallButton) {
@@ -146,7 +156,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding -> ) { innerPadding ->
when { when {
hasMagisk -> { hasMagisk -> {
Box( Box(
@@ -161,16 +170,15 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) )
} }
} }
else -> { else -> {
ModuleList( ModuleList(
viewModel = viewModel, modifier = Modifier viewModel = viewModel,
.padding(innerPadding) modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxSize(), boxModifier = Modifier.padding(innerPadding),
onInstallModule = onInstallModule = {
{
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it))) navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
}, onClickModule = { id, name, hasWebUi -> },
onClickModule = { id, name, hasWebUi ->
if (hasWebUi) { if (hasWebUi) {
context.startActivity( context.startActivity(
Intent(context, WebUIActivity::class.java) Intent(context, WebUIActivity::class.java)
@@ -179,7 +187,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
.putExtra("name", name) .putExtra("name", name)
) )
} }
}) }
)
} }
} }
} }
@@ -190,6 +199,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
private fun ModuleList( private fun ModuleList(
viewModel: ModuleViewModel, viewModel: ModuleViewModel,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
boxModifier: Modifier = Modifier,
onInstallModule: (Uri) -> Unit, onInstallModule: (Uri) -> Unit,
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit
) { ) {
@@ -197,12 +207,12 @@ private fun ModuleList(
val failedDisable = stringResource(R.string.module_failed_to_disable) val failedDisable = stringResource(R.string.module_failed_to_disable)
val failedUninstall = stringResource(R.string.module_uninstall_failed) val failedUninstall = stringResource(R.string.module_uninstall_failed)
val successUninstall = stringResource(R.string.module_uninstall_success) val successUninstall = stringResource(R.string.module_uninstall_success)
val reboot = stringResource(id = R.string.reboot) val reboot = stringResource(R.string.reboot)
val rebootToApply = stringResource(id = R.string.reboot_to_apply) val rebootToApply = stringResource(R.string.reboot_to_apply)
val moduleStr = stringResource(id = R.string.module) val moduleStr = stringResource(R.string.module)
val uninstall = stringResource(id = R.string.uninstall) val uninstall = stringResource(R.string.uninstall)
val cancel = stringResource(id = android.R.string.cancel) val cancel = stringResource(android.R.string.cancel)
val moduleUninstallConfirm = stringResource(id = R.string.module_uninstall_confirm) val moduleUninstallConfirm = stringResource(R.string.module_uninstall_confirm)
val updateText = stringResource(R.string.module_update) val updateText = stringResource(R.string.module_update)
val changelogText = stringResource(R.string.module_changelog) val changelogText = stringResource(R.string.module_changelog)
val downloadingText = stringResource(R.string.module_downloading) val downloadingText = stringResource(R.string.module_downloading)
@@ -316,13 +326,15 @@ private fun ModuleList(
} }
} }
val refreshState = rememberPullRefreshState(refreshing = viewModel.isRefreshing, val refreshState = rememberPullRefreshState(
onRefresh = { viewModel.fetchModuleList() }) refreshing = viewModel.isRefreshing,
Box(modifier.pullRefresh(refreshState)) { onRefresh = { viewModel.fetchModuleList() }
val context = LocalContext.current )
Box(
boxModifier.pullRefresh(refreshState)
) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = remember { contentPadding = remember {
PaddingValues( PaddingValues(
@@ -428,8 +440,11 @@ private fun ModuleList(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar() { private fun TopBar(
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar( TopAppBar(
scrollBehavior = scrollBehavior,
title = { Text(stringResource(R.string.module)) }, title = { Text(stringResource(R.string.module)) },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) )
@@ -448,12 +463,32 @@ private fun ModuleItem(
ElevatedCard( ElevatedCard(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
val interactionSource = remember { MutableInteractionSource() }
val indication = LocalIndication.current
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { onClick(module) } .run {
if (module.hasWebUi) {
toggleable(
value = isChecked,
interactionSource = interactionSource,
role = Role.Button,
indication = indication,
onValueChange = { onClick(module) }
)
} else {
toggleable(
value = isChecked,
interactionSource = interactionSource,
role = Role.Switch,
indication = indication,
onValueChange = onCheckChanged,
enabled = !module.update
)
}
}
.padding(24.dp, 16.dp, 24.dp, 0.dp) .padding(24.dp, 16.dp, 24.dp, 0.dp)
) { ) {
Row( Row(
@@ -499,7 +534,8 @@ private fun ModuleItem(
Switch( Switch(
enabled = !module.update, enabled = !module.update,
checked = isChecked, checked = isChecked,
onCheckedChange = onCheckChanged onCheckedChange = onCheckChanged,
interactionSource = if (!module.hasWebUi) interactionSource else null
) )
} }
} }
@@ -559,6 +595,7 @@ private fun ModuleItem(
if (module.hasWebUi) { if (module.hasWebUi) {
TextButton( TextButton(
onClick = { onClick(module) }, onClick = { onClick(module) },
interactionSource = interactionSource
) { ) {
Text( Text(
fontFamily = MaterialTheme.typography.labelMedium.fontFamily, fontFamily = MaterialTheme.typography.labelMedium.fontFamily,

View File

@@ -37,6 +37,9 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -47,6 +50,7 @@ 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.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
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.LineHeightStyle
@@ -91,11 +95,16 @@ import me.weishu.kernelsu.ui.util.shrinkModules
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun SettingScreen(navigator: DestinationsNavigator) { fun SettingScreen(navigator: DestinationsNavigator) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
TopBar(onBack = { TopBar(
onBack = {
navigator.popBackStack() navigator.popBackStack()
}) },
scrollBehavior = scrollBehavior
)
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { paddingValues -> ) { paddingValues ->
@@ -108,6 +117,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .padding(paddingValues)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
@@ -459,15 +469,21 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(onBack: () -> Unit = {}) { private fun TopBar(
onBack: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.settings)) }, title = { Text(stringResource(R.string.settings)) },
navigationIcon = { navigationIcon = {
IconButton( IconButton(
onClick = onBack onClick = onBack
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } ) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -17,6 +17,7 @@ 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.graphics.Color import androidx.compose.ui.graphics.Color
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.TextStyle import androidx.compose.ui.text.TextStyle
@@ -35,12 +36,13 @@ import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SearchAppBar import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
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())
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (viewModel.appList.isEmpty()) { if (viewModel.appList.isEmpty()) {
@@ -92,6 +94,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
} }
} }
}, },
scrollBehavior = scrollBehavior
) )
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
@@ -105,7 +108,11 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
.padding(innerPadding) .padding(innerPadding)
.pullRefresh(refreshState) .pullRefresh(refreshState)
) { ) {
LazyColumn(Modifier.fillMaxSize()) { LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
items(viewModel.appList, key = { it.packageName + it.uid }) { app -> items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
AppItem(app) { AppItem(app) {
navigator.navigate(AppProfileScreenDestination(app)) navigator.navigate(AppProfileScreenDestination(app))

View File

@@ -35,6 +35,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -44,6 +47,7 @@ import androidx.compose.runtime.rememberCoroutineScope
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.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -66,7 +70,7 @@ import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
* @date 2023/10/20. * @date 2023/10/20.
*/ */
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun AppProfileTemplateScreen( fun AppProfileTemplateScreen(
@@ -75,6 +79,7 @@ fun AppProfileTemplateScreen(
) { ) {
val viewModel = viewModel<TemplateViewModel>() val viewModel = viewModel<TemplateViewModel>()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (viewModel.templateList.isEmpty()) { if (viewModel.templateList.isEmpty()) {
@@ -98,7 +103,8 @@ fun AppProfileTemplateScreen(
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
} }
} }
TopBar(onBack = { navigator.popBackStack() }, TopBar(
onBack = { navigator.popBackStack() },
onSync = { onSync = {
scope.launch { viewModel.fetchTemplates(true) } scope.launch { viewModel.fetchTemplates(true) }
}, },
@@ -129,7 +135,8 @@ fun AppProfileTemplateScreen(
clipboardManager.setText(AnnotatedString(it)) clipboardManager.setText(AnnotatedString(it))
} }
} }
} },
scrollBehavior = scrollBehavior
) )
}, },
floatingActionButton = { floatingActionButton = {
@@ -157,9 +164,14 @@ fun AppProfileTemplateScreen(
.padding(innerPadding) .padding(innerPadding)
.pullRefresh(refreshState) .pullRefresh(refreshState)
) { ) {
LazyColumn(Modifier.fillMaxSize(), contentPadding = remember { LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
contentPadding = remember {
PaddingValues(bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */) PaddingValues(bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */)
}) { }
) {
items(viewModel.templateList, key = { it.id }) { app -> items(viewModel.templateList, key = { it.id }) { app ->
TemplateItem(navigator, app) TemplateItem(navigator, app)
} }
@@ -215,7 +227,8 @@ private fun TopBar(
onBack: () -> Unit, onBack: () -> Unit,
onSync: () -> Unit = {}, onSync: () -> Unit = {},
onImport: () -> Unit = {}, onImport: () -> Unit = {},
onExport: () -> Unit = {} onExport: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
TopAppBar( TopAppBar(
title = { title = {
@@ -261,6 +274,7 @@ private fun TopBar(
} }
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -26,6 +26,9 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -34,6 +37,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -56,7 +60,7 @@ import me.weishu.kernelsu.ui.viewmodel.toJSON
* @author weishu * @author weishu
* @date 2023/10/20. * @date 2023/10/20.
*/ */
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun TemplateEditorScreen( fun TemplateEditorScreen(
@@ -72,6 +76,8 @@ fun TemplateEditorScreen(
mutableStateOf(initialTemplate) mutableStateOf(initialTemplate)
} }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BackHandler { BackHandler {
navigator.navigateBack(result = !readOnly) navigator.navigateBack(result = !readOnly)
} }
@@ -111,13 +117,16 @@ fun TemplateEditorScreen(
} else { } else {
Toast.makeText(context, saveTemplateFailed, Toast.LENGTH_SHORT).show() Toast.makeText(context, saveTemplateFailed, Toast.LENGTH_SHORT).show()
} }
}) },
scrollBehavior = scrollBehavior
)
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.pointerInteropFilter { .pointerInteropFilter {
// disable click and ripple if readOnly // disable click and ripple if readOnly
@@ -246,7 +255,8 @@ private fun TopBar(
summary: String = "", summary: String = "",
onBack: () -> Unit, onBack: () -> Unit,
onDelete: () -> Unit = {}, onDelete: () -> Unit = {},
onSave: () -> Unit = {} onSave: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
TopAppBar( TopAppBar(
title = { title = {
@@ -280,7 +290,8 @@ private fun TopBar(
) )
} }
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
) )
} }
@@ -305,8 +316,6 @@ private fun TextEdit(
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
) )
} else {
null
} }
}, },
isError = isError, isError = isError,
@@ -322,7 +331,7 @@ private fun TextEdit(
} }
private fun isValidTemplateId(id: String): Boolean { private fun isValidTemplateId(id: String): Boolean {
return Regex("""^([A-Za-z]{1}[A-Za-z\d_]*\.)*[A-Za-z][A-Za-z\d_]*$""").matches(id) return Regex("""^([A-Za-z][A-Za-z\d_]*\.)*[A-Za-z][A-Za-z\d_]*$""").matches(id)
} }
private fun isTemplateExist(id: String): Boolean { private fun isTemplateExist(id: String): Boolean {

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.135"
android:scaleY="0.135">
<path
android:fillColor="#ffffff"
android:pathData="M 0 0 H 800 V 800 H 0 V 0 Z" />
</group>
</vector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" /> <monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon> </adaptive-icon>

View File

@@ -0,0 +1 @@
unqualifiedResLocale=en-US

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFFFF</color>
</resources>

View File

@@ -79,6 +79,9 @@ subprojects {
versionCode = managerVersionCode versionCode = managerVersionCode
versionName = managerVersionName versionName = managerVersionName
} }
ndk {
abiFilters += listOf("arm64-v8a", "x86_64", "riscv64")
}
} }
lint { lint {

View File

@@ -4,7 +4,6 @@ kotlin = "2.0.20"
ksp = "2.0.20-1.0.25" ksp = "2.0.20-1.0.25"
compose-bom = "2024.09.02" compose-bom = "2024.09.02"
lifecycle = "2.8.6" lifecycle = "2.8.6"
accompanist = "0.36.0"
navigation = "2.8.1" navigation = "2.8.1"
activity-compose = "1.9.2" activity-compose = "1.9.2"
kotlinx-coroutines = "1.9.0" kotlinx-coroutines = "1.9.0"
@@ -51,10 +50,6 @@ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "l
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
com-google-accompanist-drawablepainter = { group = "com.google.accompanist", name = "accompanist-drawablepainter", version.ref = "accompanist" }
com-google-accompanist-navigation-animation = { group = "com.google.accompanist", name = "accompanist-navigation-animation", version.ref = "accompanist" }
com-google-accompanist-webview = { group = "com.google.accompanist", name = "accompanist-webview", version.ref = "accompanist" }
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }
com-github-topjohnwu-libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } com-github-topjohnwu-libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" }
com-github-topjohnwu-libsu-io= { group = "com.github.topjohnwu.libsu", name = "io", version.ref = "libsu" } com-github-topjohnwu-libsu-io= { group = "com.github.topjohnwu.libsu", name = "io", version.ref = "libsu" }

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME