manager: several updates (#510)
+ update deps + update app profile page + don't show su and module page if no root
This commit is contained in:
@@ -5,6 +5,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.lsplugin.apksign)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
val managerVersionCode: Int by rootProject.extra
|
||||
@@ -96,8 +97,6 @@ dependencies {
|
||||
implementation(libs.compose.destinations.animations.core)
|
||||
ksp(libs.compose.destinations.ksp)
|
||||
|
||||
implementation(libs.com.github.alorma.compose.settings.ui.m3)
|
||||
|
||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||
implementation(libs.com.github.topjohnwu.libsu.service)
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class AppProfile(
|
||||
val profileName: String,
|
||||
val allowRootRequest: Boolean = false,
|
||||
val unmountModules: Boolean = false,
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class RootProfile(
|
||||
val profileName: String,
|
||||
val namespace: Namespace = Namespace.Inherited,
|
||||
val uid: Int = 0,
|
||||
val gid: Int = 0,
|
||||
val groups: Int = 0,
|
||||
val capabilities: List<String> = emptyList(),
|
||||
val context: String = "u:r:su:s0",
|
||||
) : Parcelable {
|
||||
enum class Namespace {
|
||||
Inherited,
|
||||
Global,
|
||||
Individual,
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
@@ -25,6 +24,8 @@ import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.navigation.popBackStack
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
||||
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
||||
import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||
@@ -34,7 +35,7 @@ import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -64,8 +65,10 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
NavigationBar(tonalElevation = 8.dp) {
|
||||
BottomBarDestination.values().forEach { destination ->
|
||||
if (!isManager && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
|
||||
@@ -10,10 +10,22 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -21,11 +33,9 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.weishu.kernelsu.R
|
||||
|
||||
private const val TAG = "SearchBar"
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
@Composable
|
||||
fun SwitchItem(
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
},
|
||||
leadingContent = icon?.let {
|
||||
{ Icon(icon, title) }
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RadioItem(
|
||||
title: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
},
|
||||
leadingContent = {
|
||||
RadioButton(selected = selected, onClick = onClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.weishu.kernelsu.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.AppProfile
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
|
||||
@Composable
|
||||
fun AppProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
profile: AppProfile,
|
||||
onProfileChange: (AppProfile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.profileName,
|
||||
onValueChange = { onProfileChange(profile.copy(profileName = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
title = stringResource(R.string.profile_allow_root_request),
|
||||
checked = profile.allowRootRequest,
|
||||
onCheckedChange = { onProfileChange(profile.copy(allowRootRequest = it)) }
|
||||
)
|
||||
|
||||
SwitchItem(
|
||||
title = stringResource(R.string.profile_unmount_modules),
|
||||
checked = profile.unmountModules,
|
||||
onCheckedChange = { onProfileChange(profile.copy(unmountModules = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(AppProfile("")) }
|
||||
AppProfileConfig(fixedName = true, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package me.weishu.kernelsu.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.RootProfile
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RootProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
profile: RootProfile,
|
||||
onProfileChange: (RootProfile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.profileName,
|
||||
onValueChange = { onProfileChange(profile.copy(profileName = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
var namespaceBoxExpanded by remember { mutableStateOf(false) }
|
||||
val currentNamespace = when (profile.namespace) {
|
||||
RootProfile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited)
|
||||
RootProfile.Namespace.Global -> stringResource(R.string.profile_namespace_global)
|
||||
RootProfile.Namespace.Individual -> stringResource(R.string.profile_namespace_individual)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = namespaceBoxExpanded,
|
||||
onExpandedChange = { namespaceBoxExpanded = it }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_namespace)) },
|
||||
value = currentNamespace,
|
||||
onValueChange = {},
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = namespaceBoxExpanded,
|
||||
onDismissRequest = { namespaceBoxExpanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Inherited))
|
||||
namespaceBoxExpanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_global)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Global))
|
||||
namespaceBoxExpanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_individual)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Individual))
|
||||
namespaceBoxExpanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
label = { Text("uid") },
|
||||
value = profile.uid.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(uid = it.toInt())) }
|
||||
)
|
||||
})
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
label = { Text("gid") },
|
||||
value = profile.gid.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(gid = it.toInt())) }
|
||||
)
|
||||
})
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
label = { Text("groups") },
|
||||
value = profile.groups.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(groups = it.toInt())) }
|
||||
)
|
||||
})
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
label = { Text("context") },
|
||||
value = profile.context,
|
||||
onValueChange = { onProfileChange(profile.copy(context = it)) }
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RootProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(RootProfile("")) }
|
||||
RootProfileConfig(fixedName = true, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,24 @@
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import androidx.compose.foundation.clickable
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Android
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Group
|
||||
import androidx.compose.material.icons.filled.Groups3
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material.icons.filled.PermIdentity
|
||||
import androidx.compose.material.icons.filled.Rule
|
||||
import androidx.compose.material.icons.filled.SafetyDivider
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -37,9 +29,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
@@ -47,9 +39,17 @@ import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.AppProfile
|
||||
import me.weishu.kernelsu.profile.RootProfile
|
||||
import me.weishu.kernelsu.ui.component.RadioItem
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
|
||||
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -59,282 +59,171 @@ import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
@Composable
|
||||
fun AppProfileScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
packageName: String,
|
||||
grantRoot: Boolean,
|
||||
label: String,
|
||||
icon: PackageInfo
|
||||
appInfo: SuperUserViewModel.AppInfo,
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val failToGrantRoot = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
var isRootGranted by rememberSaveable { mutableStateOf(appInfo.onAllowList) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(onBack = {
|
||||
navigator.popBackStack()
|
||||
})
|
||||
}
|
||||
topBar = { TopBar { navigator.popBackStack() } }
|
||||
) { paddingValues ->
|
||||
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val uid = icon.applicationInfo.uid
|
||||
val isAllowlistModeInit = Natives.isAllowlistMode()
|
||||
|
||||
val isInAllowDenyListInit = if (isAllowlistModeInit) {
|
||||
Natives.isUidInAllowlist(uid)
|
||||
} else {
|
||||
Natives.isUidInDenylist(uid)
|
||||
}
|
||||
|
||||
GroupTitle(stringResource(id = R.string.app_profile_title0))
|
||||
|
||||
var allowlistMode by rememberSaveable {
|
||||
mutableStateOf(isAllowlistModeInit)
|
||||
}
|
||||
|
||||
var isInAllowDenyList by rememberSaveable {
|
||||
mutableStateOf(isInAllowDenyListInit)
|
||||
}
|
||||
|
||||
val setAllowlistFailedMsg = if (allowlistMode) {
|
||||
stringResource(R.string.failed_to_set_denylist_mode)
|
||||
} else {
|
||||
stringResource(R.string.failed_to_set_allowlist_mode)
|
||||
}
|
||||
WorkingMode(allowlistMode) { checked ->
|
||||
if (Natives.setAllowlistMode(checked)) {
|
||||
allowlistMode = !allowlistMode
|
||||
} else scope.launch {
|
||||
snackbarHost.showSnackbar(setAllowlistFailedMsg)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(thickness = Dp.Hairline)
|
||||
|
||||
GroupTitle(stringResource(id = R.string.app_profile_title1))
|
||||
|
||||
ListItem(
|
||||
headlineText = { Text(label) },
|
||||
supportingText = { Text(packageName) },
|
||||
leadingContent = {
|
||||
AppProfileInner(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
packageName = appInfo.packageName,
|
||||
appLabel = appInfo.label,
|
||||
appIcon = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(icon)
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(appInfo.packageInfo)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = label,
|
||||
contentDescription = appInfo.label,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.width(48.dp)
|
||||
.height(48.dp)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
var isGrantRoot by rememberSaveable {
|
||||
mutableStateOf(grantRoot)
|
||||
}
|
||||
|
||||
val failToGrantRoot = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
AppSwitch(
|
||||
Icons.Filled.Security,
|
||||
stringResource(id = R.string.superuser),
|
||||
checked = isGrantRoot
|
||||
) { checked ->
|
||||
|
||||
isRootGranted = isRootGranted,
|
||||
onSwitchRootPermission = { grant ->
|
||||
scope.launch {
|
||||
val success = Natives.allowRoot(uid, checked)
|
||||
val success = Natives.allowRoot(appInfo.uid, grant)
|
||||
if (success) {
|
||||
isGrantRoot = checked
|
||||
isRootGranted = grant
|
||||
} else {
|
||||
snackbarHost.showSnackbar(failToGrantRoot.format(uid))
|
||||
snackbarHost.showSnackbar(failToGrantRoot.format(appInfo.uid))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val failedToAddAllowListMsg = if (allowlistMode) {
|
||||
stringResource(R.string.failed_to_add_to_allowlist)
|
||||
} else {
|
||||
stringResource(R.string.failed_to_add_to_denylist)
|
||||
}
|
||||
AppSwitch(
|
||||
icon = Icons.Filled.List,
|
||||
title = if (allowlistMode) {
|
||||
stringResource(id = R.string.app_profile_allowlist)
|
||||
} else {
|
||||
stringResource(id = R.string.app_profile_denylist)
|
||||
},
|
||||
checked = isInAllowDenyList
|
||||
) { checked ->
|
||||
val success = if (allowlistMode) {
|
||||
Natives.addUidToAllowlist(uid)
|
||||
} else {
|
||||
Natives.addUidToDenylist(uid)
|
||||
}
|
||||
if (success) {
|
||||
isInAllowDenyList = checked
|
||||
} else scope.launch {
|
||||
snackbarHost.showSnackbar(failedToAddAllowListMsg.format(label))
|
||||
}
|
||||
}
|
||||
|
||||
Divider(thickness = Dp.Hairline)
|
||||
|
||||
GroupTitle(title = stringResource(id = R.string.app_profile_title2))
|
||||
|
||||
Uid()
|
||||
|
||||
Gid()
|
||||
|
||||
Groups()
|
||||
|
||||
Capabilities()
|
||||
|
||||
SELinuxDomain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupTitle(title: String) {
|
||||
Row(modifier = Modifier.padding(12.dp)) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontStyle = MaterialTheme.typography.titleSmall.fontStyle,
|
||||
fontSize = MaterialTheme.typography.titleSmall.fontSize,
|
||||
fontWeight = MaterialTheme.typography.titleSmall.fontWeight,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WorkingMode(allowlistMode: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
val mode = if (allowlistMode) {
|
||||
stringResource(id = R.string.app_profile_allowlist)
|
||||
} else {
|
||||
stringResource(id = R.string.app_profile_denylist)
|
||||
}
|
||||
ListItem(
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
showDropdown = true
|
||||
}),
|
||||
headlineText = {
|
||||
Text(stringResource(id = R.string.app_profile_mode))
|
||||
},
|
||||
supportingText = {
|
||||
Text(mode)
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.List,
|
||||
contentDescription = stringResource(id = R.string.app_profile_mode)
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(checked = allowlistMode, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppSwitch(
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
checked: Boolean,
|
||||
onCheckChange: (Boolean) -> Unit
|
||||
) {
|
||||
ListItem(
|
||||
headlineText = { Text(title) },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = title
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(checked = checked, onCheckedChange = onCheckChange)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Uid() {
|
||||
ListItem(
|
||||
headlineText = {
|
||||
Text("Uid: 0")
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.PermIdentity,
|
||||
contentDescription = "Uid"
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Gid() {
|
||||
ListItem(
|
||||
headlineText = { Text("Gid: 0") },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Group,
|
||||
contentDescription = "Gid"
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Groups() {
|
||||
ListItem(
|
||||
headlineText = { Text("Groups: 0") },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Groups3,
|
||||
contentDescription = "Groups"
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Capabilities() {
|
||||
ListItem(
|
||||
headlineText = { Text("Capabilities") },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.SafetyDivider,
|
||||
contentDescription = "Capabilities"
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SELinuxDomain() {
|
||||
ListItem(
|
||||
headlineText = { Text("u:r:su:s0") },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Rule,
|
||||
contentDescription = "SELinuxDomain"
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onBack: () -> Unit = {}) {
|
||||
private fun AppProfileInner(
|
||||
modifier: Modifier = Modifier,
|
||||
packageName: String,
|
||||
appLabel: String,
|
||||
appIcon: @Composable () -> Unit,
|
||||
isRootGranted: Boolean,
|
||||
onSwitchRootPermission: (Boolean) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Security,
|
||||
title = stringResource(id = R.string.superuser),
|
||||
checked = isRootGranted,
|
||||
onCheckedChange = onSwitchRootPermission,
|
||||
)
|
||||
|
||||
Divider(thickness = Dp.Hairline)
|
||||
|
||||
Crossfade(targetState = isRootGranted, label = "") { current ->
|
||||
if (current) {
|
||||
var mode: Mode<RootProfile> by rememberSaveable { mutableStateOf(Mode.Default()) }
|
||||
var template by rememberSaveable { mutableStateOf("None") }
|
||||
var profile by rememberSaveable { mutableStateOf(RootProfile("@$packageName")) }
|
||||
|
||||
Column {
|
||||
RadioItem(
|
||||
title = stringResource(R.string.profile_default),
|
||||
selected = mode is Mode.Default,
|
||||
onClick = { mode = Mode.Default() }
|
||||
)
|
||||
|
||||
RadioItem(
|
||||
title = stringResource(R.string.profile_template),
|
||||
selected = mode is Mode.Template,
|
||||
onClick = { mode = Mode.Template("") }
|
||||
)
|
||||
AnimatedVisibility(mode is Mode.Template) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_template)) },
|
||||
value = template,
|
||||
onValueChange = {}
|
||||
)
|
||||
// TODO: Template
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RadioItem(
|
||||
title = stringResource(R.string.profile_custom),
|
||||
selected = mode is Mode.Custom,
|
||||
onClick = { mode = Mode.Custom(profile) }
|
||||
)
|
||||
AnimatedVisibility(mode is Mode.Custom) {
|
||||
RootProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
onProfileChange = { profile = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var mode: Mode<AppProfile> by rememberSaveable { mutableStateOf(Mode.Default()) }
|
||||
var profile by rememberSaveable { mutableStateOf(AppProfile("@$packageName")) }
|
||||
|
||||
Column {
|
||||
RadioItem(
|
||||
title = stringResource(R.string.profile_default),
|
||||
selected = mode is Mode.Default,
|
||||
onClick = { mode = Mode.Default() }
|
||||
)
|
||||
|
||||
RadioItem(
|
||||
title = stringResource(R.string.profile_custom),
|
||||
selected = mode is Mode.Custom,
|
||||
onClick = { mode = Mode.Custom(profile) }
|
||||
)
|
||||
AnimatedVisibility(mode is Mode.Custom) {
|
||||
AppProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
onProfileChange = { profile = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private sealed class Mode<P : Parcelable> : Parcelable {
|
||||
|
||||
class Default<P : Parcelable> : Mode<P>()
|
||||
|
||||
class Template<P : Parcelable>(val template: String) : Mode<P>()
|
||||
|
||||
class Custom<P : Parcelable>(val profile: P) : Mode<P>()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onBack: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.app_profile))
|
||||
Text(stringResource(R.string.profile))
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
@@ -343,3 +232,16 @@ private fun TopBar(onBack: () -> Unit = {}) {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfilePreview() {
|
||||
var isRootGranted by remember { mutableStateOf(false) }
|
||||
AppProfileInner(
|
||||
packageName = "icu.nullptr.test",
|
||||
appLabel = "Test",
|
||||
appIcon = { Icon(Icons.Filled.Android, null) },
|
||||
isRootGranted = isRootGranted,
|
||||
onSwitchRootPermission = { isRootGranted = it },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ enum class BottomBarDestination(
|
||||
val direction: DirectionDestinationSpec,
|
||||
@StringRes val label: Int,
|
||||
val iconSelected: ImageVector,
|
||||
val iconNotSelected: ImageVector
|
||||
val iconNotSelected: ImageVector,
|
||||
val rootRequired: Boolean,
|
||||
) {
|
||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home),
|
||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security),
|
||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps)
|
||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
|
||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
|
||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RootNavGraph(start = true)
|
||||
@Destination
|
||||
@Composable
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.*
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Destination
|
||||
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
|
||||
@@ -39,7 +39,6 @@ import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
@@ -11,7 +12,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.FileProvider
|
||||
import com.alorma.compose.settings.ui.*
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -28,7 +28,6 @@ import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
@@ -50,11 +49,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialogHost = LocalDialogHost.current
|
||||
SettingsMenuLink(
|
||||
title = {
|
||||
Text(stringResource(id = R.string.send_log))
|
||||
},
|
||||
onClick = {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val bugreport = dialogHost.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -85,11 +82,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
|
||||
val about = stringResource(id = R.string.about)
|
||||
SettingsMenuLink(
|
||||
title = {
|
||||
Text(about)
|
||||
},
|
||||
onClick = {
|
||||
ListItem(
|
||||
headlineContent = { Text(about) },
|
||||
modifier = Modifier.clickable {
|
||||
showAboutDialog.value = true
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -24,18 +23,13 @@ import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.component.SearchAppBar
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
import java.util.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
@@ -107,18 +101,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(refreshState)
|
||||
) {
|
||||
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
navigator.navigate(
|
||||
AppProfileScreenDestination(
|
||||
packageName = app.packageName,
|
||||
grantRoot = app.onAllowList,
|
||||
label = app.label, icon = app.icon
|
||||
)
|
||||
)
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -133,7 +119,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppItem(
|
||||
app: SuperUserViewModel.AppInfo,
|
||||
@@ -141,12 +126,12 @@ private fun AppItem(
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable(onClick = onClickListener),
|
||||
headlineText = { Text(app.label) },
|
||||
supportingText = { Text(app.packageName) },
|
||||
headlineContent = { Text(app.label) },
|
||||
supportingContent = { Text(app.packageName) },
|
||||
leadingContent = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.icon)
|
||||
.data(app.packageInfo)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = app.label,
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -16,6 +17,7 @@ import androidx.lifecycle.ViewModel
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.weishu.kernelsu.IKsuInterface
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
@@ -34,14 +36,18 @@ class SuperUserViewModel : ViewModel() {
|
||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||
}
|
||||
|
||||
class AppInfo(
|
||||
@Parcelize
|
||||
data class AppInfo(
|
||||
val label: String,
|
||||
val packageName: String,
|
||||
val icon: PackageInfo,
|
||||
val uid: Int,
|
||||
val packageInfo: PackageInfo,
|
||||
val onAllowList: Boolean,
|
||||
val onDenyList: Boolean
|
||||
)
|
||||
val onDenyList: Boolean,
|
||||
) : Parcelable {
|
||||
val packageName: String
|
||||
get() = packageInfo.packageName
|
||||
val uid: Int
|
||||
get() = packageInfo.applicationInfo.uid
|
||||
}
|
||||
|
||||
var search by mutableStateOf("")
|
||||
var showSystemApps by mutableStateOf(false)
|
||||
@@ -67,7 +73,7 @@ class SuperUserViewModel : ViewModel() {
|
||||
.toPinyinString(it.label).contains(search)
|
||||
}.filter {
|
||||
it.uid == 2000 // Always show shell
|
||||
|| showSystemApps || it.icon.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
|| showSystemApps || it.packageInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +136,7 @@ class SuperUserViewModel : ViewModel() {
|
||||
val uid = appInfo.uid
|
||||
AppInfo(
|
||||
label = appInfo.loadLabel(pm).toString(),
|
||||
packageName = it.packageName,
|
||||
icon = it,
|
||||
uid = uid,
|
||||
packageInfo = it,
|
||||
onAllowList = uid in allowList,
|
||||
onDenyList = uid in denyList
|
||||
)
|
||||
|
||||
@@ -65,15 +65,15 @@
|
||||
<string name="home_support_title">Support Us</string>
|
||||
<string name="home_support_content">KernelSU is, and always will be, free, and open source. You can however show us that you care by making a donation.</string>
|
||||
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Join our %2$s channel]]></string>
|
||||
<string name="app_profile" translatable="false">App Profile</string>
|
||||
<string name="app_profile_title1">Application</string>
|
||||
<string name="app_profile_title2" translatable="false">Root Profile</string>
|
||||
<string name="app_profile_allowlist">Allowlist</string>
|
||||
<string name="app_profile_denylist">Denylist</string>
|
||||
<string name="app_profile_title0">Global</string>
|
||||
<string name="app_profile_mode">Working Mode</string>
|
||||
<string name="failed_to_set_allowlist_mode">Failed to switch to allowlist mode</string>
|
||||
<string name="failed_to_add_to_allowlist">Failed to add %s to allowlist</string>
|
||||
<string name="failed_to_add_to_denylist">Failed to add %s to denylist</string>
|
||||
<string name="failed_to_set_denylist_mode">Failed to switch to denylist mode</string>
|
||||
<string name="profile">App profile</string>
|
||||
<string name="profile_default">Use default profile</string>
|
||||
<string name="profile_template">Use template profile</string>
|
||||
<string name="profile_custom">Use custom profile</string>
|
||||
<string name="profile_name">Profile name</string>
|
||||
<string name="profile_namespace">Mount namespace</string>
|
||||
<string name="profile_namespace_inherited">Inherited</string>
|
||||
<string name="profile_namespace_global">Global</string>
|
||||
<string name="profile_namespace_individual">Individual</string>
|
||||
<string name="profile_unmount_modules">Unmount modules</string>
|
||||
<string name="profile_allow_root_request">Allow root request</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[versions]
|
||||
agp = "8.0.0"
|
||||
agp = "8.0.1"
|
||||
kotlin = "1.8.10"
|
||||
ksp = "1.8.10-1.0.9"
|
||||
compose-bom = "2023.04.01"
|
||||
compose-bom = "2023.05.01"
|
||||
lifecycle = "2.6.1"
|
||||
accompanist = "0.30.0"
|
||||
navigation = "2.5.3"
|
||||
compose-destination = "1.9.40-beta"
|
||||
compose-destination = "1.9.42-beta"
|
||||
libsu = "5.0.5"
|
||||
|
||||
[plugins]
|
||||
@@ -38,8 +38,6 @@ com-google-accompanist-drawablepainter = { group = "com.google.accompanist", nam
|
||||
com-google-accompanist-navigation-animation = { group = "com.google.accompanist", name = "accompanist-navigation-animation", version.ref = "accompanist" }
|
||||
com-google-accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" }
|
||||
|
||||
com-github-alorma-compose-settings-ui-m3 = { module = "com.github.alorma:compose-settings-ui-m3", version = "0.22.0" }
|
||||
|
||||
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" }
|
||||
|
||||
@@ -47,7 +45,7 @@ dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:pa
|
||||
|
||||
io-coil-kt-coil-compose = { group = "io.coil-kt", name = "coil-compose", version = "2.3.0" }
|
||||
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.6.4" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.1" }
|
||||
|
||||
me-zhanghai-android-appiconloader-coil = { group = "me.zhanghai.android.appiconloader", name = "appiconloader-coil", version = "1.5.0" }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in New Issue
Block a user