manager: ui refactor (#21)

* manager: ui refactor
This commit is contained in:
Nullptr
2022-12-26 08:59:37 +08:00
committed by GitHub
parent 4c9942dd3f
commit 894b2e99ed
33 changed files with 894 additions and 949 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" /> <bytecodeTargetLevel target="19" />
</component> </component>
</project> </project>

View File

@@ -7,8 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/local/Cellar/gradle/6.4/libexec" /> <option name="gradleJvm" value="19" />
<option name="gradleJvm" value="11" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -1,104 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'me.weishu.kernelsu'
compileSdk 33
signingConfigs {
sign
}
defaultConfig {
applicationId "me.weishu.kernelsu"
minSdk 26
targetSdk 32
versionCode 10013
versionName "0.1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
externalNativeBuild {
cmake {
cppFlags ''
}
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.sign
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.1.1'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.18.1'
}
}
applicationVariants.all { variant ->
variant.outputs.all { output ->
def versionName = variant.versionName
def buildType = variant.buildType.name
outputFileName = "KernelSU_${versionName}-${buildType}.apk"
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation "androidx.compose.material3:material3-window-size-class:1.0.1"
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-compose:$nav_version"
implementation "com.google.accompanist:accompanist-drawablepainter:0.28.0"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
}
apply from: rootProject.file('sign.gradle')

View File

@@ -0,0 +1,80 @@
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
plugins {
id("com.android.application")
id("com.google.devtools.ksp")
kotlin("android")
}
android {
namespace = "me.weishu.kernelsu"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
}
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
externalNativeBuild {
cmake {
path(file("src/main/cpp/CMakeLists.txt"))
version = "3.18.1"
}
}
applicationVariants.all {
outputs.forEach {
val output = it as BaseVariantOutputImpl
output.outputFileName = "KernelSU_$versionName-${buildType.name}.apk"
}
kotlin.sourceSets {
getByName(name) {
kotlin.srcDir("build/generated/ksp/$name/kotlin")
}
}
}
}
dependencies {
val accompanistVersion = "0.28.0"
val composeDestinationsVersion = "1.7.27-beta"
implementation(platform("androidx.compose:compose-bom:2022.12.00"))
debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.activity:activity-compose:1.6.1")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
implementation("androidx.navigation:navigation-compose:2.5.3")
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
implementation("com.google.accompanist:accompanist-swiperefresh:$accompanistVersion")
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}

View File

@@ -2,13 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<queries> <uses-permission
<intent> android:name="android.permission.QUERY_ALL_PACKAGES"
<action android:name="android.intent.action.MAIN" /> tools:ignore="QueryAllPackagesPermission" />
</intent>
</queries>
<application <application
android:name=".KernelSUApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -18,9 +17,8 @@
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" android:name=".ui.MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.KernelSU"> android:theme="@style/Theme.KernelSU">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -0,0 +1,13 @@
package me.weishu.kernelsu
import android.app.Application
lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() {
override fun onCreate() {
super.onCreate()
ksuApp = this
}
}

View File

@@ -1,165 +0,0 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package me.weishu.kernelsu
import AboutDialog
import Home
import Module
import SuperUser
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import me.weishu.kernelsu.ui.theme.KernelSUTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KernelSUTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
}
}
}
}
}
@Composable
fun MainTopAppBar(onMoreClick: () -> Unit) {
TopAppBar(
title = {
Text(
"KernelSU",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
actions = {
IconButton(onClick = onMoreClick) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = "Localized description"
)
}
}
)
}
@Composable
fun MainBottomNavigation(items: List<Screen>, navController: NavHostController) {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEachIndexed { index, item ->
NavigationBarItem(
icon = {
Icon(
painter = painterResource(id = item.icon),
contentDescription = ""
)
},
label = { Text(text = stringResource(id = item.resourceId)) },
selected = currentDestination?.hierarchy?.any { it.route == item.route } == true,
onClick = {
navController.navigate(item.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
@Composable
fun MainScreen() {
val items = listOf(
Screen.Home,
Screen.SuperUser,
Screen.Module
)
val navController = rememberNavController()
var showAboutDialog by remember { mutableStateOf(false) }
AboutDialog(openDialog = showAboutDialog, onDismiss = {
showAboutDialog = false
})
Scaffold(
topBar = {
MainTopAppBar {
showAboutDialog = true
}
},
bottomBar = {
MainBottomNavigation(items = items, navController = navController)
},
content = { innerPadding ->
NavHost(
navController,
startDestination = Screen.Home.route,
Modifier.padding(innerPadding)
) {
composable(Screen.Home.route) { Home() }
composable(Screen.SuperUser.route) { SuperUser() }
composable(Screen.Module.route) { Module() }
}
}
)
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
KernelSUTheme {
MainScreen()
}
}
sealed class Screen(val route: String, @StringRes val resourceId: Int, val icon: Int) {
object Home : Screen("home", R.string.home, R.drawable.ic_home)
object SuperUser : Screen("superuser", R.string.superuser, R.drawable.ic_superuser)
object Module : Screen("module", R.string.module, R.drawable.ic_module)
}

View File

@@ -0,0 +1,86 @@
package me.weishu.kernelsu.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.ramcosta.composedestinations.DestinationsNavHost
import me.weishu.kernelsu.ui.screen.BottomBarDestination
import me.weishu.kernelsu.ui.screen.NavGraphs
import me.weishu.kernelsu.ui.screen.appCurrentDestinationAsState
import me.weishu.kernelsu.ui.screen.destinations.Destination
import me.weishu.kernelsu.ui.screen.startAppDestination
import me.weishu.kernelsu.ui.theme.KernelSUTheme
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KernelSUTheme {
val navController = rememberAnimatedNavController()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
bottomBar = { BottomBar(navController) },
snackbarHost = { SnackbarHost(snackbarHostState) }
) { innerPadding ->
CompositionLocalProvider(LocalSnackbarHost provides snackbarHostState) {
DestinationsNavHost(
modifier = Modifier.padding(innerPadding),
navGraph = NavGraphs.root,
navController = navController
)
}
}
}
}
}
}
@Composable
private fun BottomBar(navController: NavHostController) {
val currentDestination: Destination = navController.appCurrentDestinationAsState().value
?: NavGraphs.root.startAppDestination
var topDestination by rememberSaveable { mutableStateOf(currentDestination.route) }
LaunchedEffect(currentDestination) {
val queue = navController.backQueue
if (queue.size == 2) topDestination = queue[1].destination.route!!
else if (queue.size > 2) topDestination = queue[2].destination.route!!
}
NavigationBar(tonalElevation = 8.dp) {
BottomBarDestination.values().forEach { destination ->
NavigationBarItem(
selected = topDestination == destination.direction.route,
onClick = {
navController.navigate(destination.direction.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
if (topDestination == destination.direction.route) Icon(destination.iconSelected, stringResource(destination.label))
else Icon(destination.iconNotSelected, stringResource(destination.label))
},
label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false
)
}
}
}

View File

@@ -0,0 +1,133 @@
package me.weishu.kernelsu.ui.component
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
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.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.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
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.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
private const val TAG = "SearchBar"
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun SearchAppBar(
title: @Composable () -> Unit,
searchText: String,
onSearchTextChange: (String) -> Unit,
onClearClick: () -> Unit,
onBackClick: (() -> Unit)? = null,
onConfirm: (() -> Unit)? = null
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }
var onSearch by remember { mutableStateOf(false) }
if (onSearch) {
LaunchedEffect(Unit) { focusRequester.requestFocus() }
}
DisposableEffect(Unit) {
onDispose {
keyboardController?.hide()
}
}
TopAppBar(
title = {
Box {
AnimatedVisibility(
modifier = Modifier.align(Alignment.CenterStart),
visible = !onSearch,
enter = fadeIn(),
exit = fadeOut(),
content = { title() }
)
AnimatedVisibility(
visible = onSearch,
enter = fadeIn(),
exit = fadeOut()
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(top = 2.dp, bottom = 2.dp, end = if (onBackClick != null) 0.dp else 14.dp)
.focusRequester(focusRequester)
.onFocusChanged { focusState ->
if (focusState.isFocused) onSearch = true
Log.d(TAG, "onFocusChanged: $focusState")
},
value = searchText,
onValueChange = onSearchTextChange,
trailingIcon = {
IconButton(
onClick = {
onSearch = false
keyboardController?.hide()
onClearClick()
},
content = { Icon(Icons.Filled.Close, null) }
)
},
maxLines = 1,
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {
keyboardController?.hide()
onConfirm?.invoke()
})
)
}
}
},
navigationIcon = {
if (onBackClick != null) {
IconButton(
onClick = onBackClick,
content = { Icon(Icons.Outlined.ArrowBack, null) }
)
}
},
actions = {
AnimatedVisibility(
visible = !onSearch
) {
IconButton(
onClick = { onSearch = true },
content = { Icon(Icons.Filled.Search, null) }
)
}
}
)
}
@Preview
@Composable
private fun SearchAppBarPreview() {
var searchText by remember { mutableStateOf("") }
SearchAppBar(
title = { Text("Search text") },
searchText = searchText,
onSearchTextChange = { searchText = it },
onClearClick = { searchText = "" }
)
}

View File

@@ -1,70 +0,0 @@
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.util.Patterns
import android.widget.TextView
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.util.LinkifyCompat
import me.weishu.kernelsu.LinkifyText
@Composable
fun DefaultLinkifyText(modifier: Modifier = Modifier, text: String?) {
val context = LocalContext.current
val customLinkifyTextView = remember {
TextView(context)
}
AndroidView(modifier = modifier, factory = { customLinkifyTextView }) { textView ->
textView.text = text ?: ""
LinkifyCompat.addLinks(textView, Linkify.ALL)
Linkify.addLinks(textView, Patterns.PHONE,"tel:",
Linkify.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter)
textView.movementMethod = LinkMovementMethod.getInstance()
}
}
@Composable
fun AboutDialog(openDialog: Boolean, onDismiss: () -> Unit) {
if (!openDialog) {
return
}
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = {
Text(text = "About")
},
text = {
Column {
LinkifyText(text = "Author: weishu")
LinkifyText(text = "Github: https://github.com/tiann/KernelSU")
LinkifyText(text = "Telegram: https://t.me/KernelSU")
LinkifyText(text = "QQ: https://pd.qq.com/s/8lipl1brp")
}
},
confirmButton = {
TextButton(
onClick = {
onDismiss()
}) {
Text("OK")
}
},
)
}
@Preview
@Composable
fun Preview_AboutDialog() {
AboutDialog(true, {})
}

View File

@@ -0,0 +1,23 @@
package me.weishu.kernelsu.ui.screen
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.ui.graphics.vector.ImageVector
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.screen.destinations.HomeScreenDestination
import me.weishu.kernelsu.ui.screen.destinations.SuperUserScreenDestination
import me.weishu.kernelsu.ui.screen.destinations.ModuleScreenDestination
enum class BottomBarDestination(
val direction: DirectionDestinationSpec,
@StringRes val label: Int,
val iconSelected: ImageVector,
val iconNotSelected: ImageVector
) {
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)
}

View File

@@ -1,144 +1,228 @@
package me.weishu.kernelsu.ui.screen
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build import android.os.Build
import android.system.Os import android.system.Os
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Snackbar import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle 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 com.ramcosta.composedestinations.annotation.Destination
import me.weishu.kernelsu.Natives import com.ramcosta.composedestinations.annotation.RootNavGraph
import kotlinx.coroutines.launch
import me.weishu.kernelsu.*
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.getKernelVersion import me.weishu.kernelsu.ui.util.LinkifyText
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
@OptIn(ExperimentalMaterial3Api::class)
@RootNavGraph(start = true)
@Destination
@Composable @Composable
fun Info(label: String, value: String) { fun HomeScreen() {
Text( Scaffold(
text = buildAnnotatedString { topBar = { TopBar() }
append("$label: ") ) { innerPadding ->
withStyle( Column(
style = SpanStyle( modifier = Modifier
fontWeight = FontWeight.W500, .padding(innerPadding)
) .padding(horizontal = 16.dp)
) { .verticalScroll(rememberScrollState()),
append(value) verticalArrangement = Arrangement.spacedBy(16.dp)
} ) {
}, val kernelVersion = getKernelVersion()
softWrap = true, val isManager = Natives.becomeManager(ksuApp.packageName)
val ksuVersion = if (isManager) Natives.getVersion() else null
StatusCard(kernelVersion, ksuVersion)
InfoCard()
SupportCard()
Spacer(Modifier)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar() {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) }
) )
} }
@Composable @Composable
fun Home() { private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
ElevatedCard(
val statusIcon: Int colors = CardDefaults.elevatedCardColors(containerColor = run {
val statusText: String if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
val secondaryText: String else MaterialTheme.colorScheme.errorContainer
})
val kernelVersion = getKernelVersion() ) {
val isManager = Natives.becomeManager(LocalContext.current.packageName) Row(
if (kernelVersion.isGKI()) {
// GKI kernel
if (isManager) {
statusIcon = R.drawable.ic_status_working
statusText = "Working"
secondaryText = "Version: ${Natives.getVersion()}"
} else {
statusIcon = R.drawable.ic_status_supported
statusText = "Not installed"
secondaryText = "Click to install"
}
} else {
statusIcon = R.drawable.ic_status_unsupported
statusText = "Unsupported kernel"
secondaryText = "KernelSU only supports GKI kernels now"
}
val context = LocalContext.current
Column(modifier = Modifier.fillMaxWidth()) {
Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(6.dp)
.clickable { .clickable {
if (kernelVersion.isGKI() && !isManager) { // TODO: Install kernel
Toast.makeText( }
context, .padding(24.dp),
"Unimplemented", verticalAlignment = Alignment.CenterVertically
Toast.LENGTH_SHORT ) {
).show() when {
ksuVersion != null -> {
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))
Column(Modifier.padding(start = 20.dp)) {
Text(
text = stringResource(R.string.home_working),
fontFamily = FontFamily.Serif,
style = MaterialTheme.typography.titleMedium
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_working_version, ksuVersion),
style = MaterialTheme.typography.bodyMedium
)
} }
},
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Image(
painter = painterResource(id = statusIcon),
null,
modifier = Modifier
.size(64.dp)
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp),
) {
Text(
text = statusText,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = secondaryText,
fontSize = 15.sp,
fontWeight = FontWeight.Normal
)
} }
} kernelVersion.isGKI() -> {
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
} Column(Modifier.padding(start = 20.dp)) {
Text(
Card( text = stringResource(R.string.home_not_installed),
modifier = Modifier fontFamily = FontFamily.Serif,
.fillMaxWidth() style = MaterialTheme.typography.titleMedium
.padding(6.dp) )
) { Spacer(Modifier.height(4.dp))
Column( Text(
modifier = Modifier.padding(10.dp) text = stringResource(R.string.home_click_to_install),
) { style = MaterialTheme.typography.bodyMedium
)
Os.uname().let { uname -> }
Info("Kernel", uname.release) }
Info("Arch", uname.machine) else -> {
Info("Version", uname.version) Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported))
Column(Modifier.padding(start = 20.dp)) {
Text(
text = stringResource(R.string.home_unsupported),
fontFamily = FontFamily.Serif,
style = MaterialTheme.typography.titleMedium
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_unsupported_reason),
style = MaterialTheme.typography.bodyMedium
)
}
} }
Info("API Level", Build.VERSION.SDK_INT.toString())
Info("ABI", Build.SUPPORTED_ABIS.joinToString(", "))
Info("Fingerprint", Build.FINGERPRINT)
Info("Security Patch", Build.VERSION.SECURITY_PATCH)
} }
} }
} }
}
@Composable
private fun InfoCard() {
val context = LocalContext.current
val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
ElevatedCard {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
) {
val contents = StringBuilder()
val uname = Os.uname()
@Composable
fun InfoCardItem(label: String, content: String) {
contents.appendLine(label).appendLine(content).appendLine()
Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium)
}
InfoCardItem("Kernel", uname.release)
Spacer(Modifier.height(24.dp))
InfoCardItem("Arch", uname.machine)
Spacer(Modifier.height(24.dp))
InfoCardItem("Version", uname.version)
Spacer(Modifier.height(24.dp))
InfoCardItem("API Level", Build.VERSION.SDK_INT.toString())
Spacer(Modifier.height(24.dp))
InfoCardItem("ABI", Build.SUPPORTED_ABIS.joinToString(", "))
Spacer(Modifier.height(24.dp))
InfoCardItem("Fingerprint", Build.FINGERPRINT)
Spacer(Modifier.height(24.dp))
InfoCardItem("Security Patch", Build.VERSION.SECURITY_PATCH)
val copiedMessage = stringResource(R.string.home_copied_to_clipboard)
TextButton(
modifier = Modifier.align(Alignment.End),
onClick = {
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("KernelSU", contents.toString()))
scope.launch { snackbarHost.showSnackbar(copiedMessage) }
},
content = { Text(stringResource(android.R.string.copy)) }
)
}
}
}
@Preview
@Composable
private fun StatusCardPreview() {
Column {
StatusCard(KernelVersion(5, 10, 101), 1)
StatusCard(KernelVersion(5, 10, 101), null)
StatusCard(KernelVersion(4, 10, 101), null)
}
}
@Preview
@Composable
private fun SupportCard() {
ElevatedCard {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp)
) {
Text(
text = stringResource(R.string.home_support),
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.titleMedium
)
Spacer(Modifier.height(8.dp))
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodyMedium) {
LinkifyText("Author: weishu")
LinkifyText("Github: https://github.com/tiann/KernelSU")
LinkifyText("Telegram: https://t.me/KernelSU")
LinkifyText("QQ: https://pd.qq.com/s/8lipl1brp")
}
}
}
} }

View File

@@ -1,20 +1,17 @@
package me.weishu.kernelsu.ui.screen
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import com.ramcosta.composedestinations.annotation.Destination
@Destination
@Composable @Composable
fun Module() { fun ModuleScreen() {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Text(text = "Coming Soon..") Text(text = "Coming Soon..")
} }
} }
@Preview
@Composable
fun Preview() {
Module()
}

View File

@@ -1,191 +1,107 @@
@file:OptIn(ExperimentalMaterial3Api::class) package me.weishu.kernelsu.ui.screen
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
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.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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 androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.ramcosta.composedestinations.annotation.Destination
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
import java.util.* import java.util.*
private const val TAG = "SuperUser" @OptIn(ExperimentalMaterial3Api::class)
@Destination
class SuperUserData(
val name: () -> CharSequence,
val description: String,
val icon: () -> Drawable,
val uid: Int,
initialChecked: Boolean = false
) {
var checked: Boolean by mutableStateOf(initialChecked)
}
@Composable @Composable
fun SuperUserItem( fun SuperUserScreen() {
superUserData: SuperUserData, val viewModel = viewModel<SuperUserViewModel>()
checked: Boolean, val snackbarHost = LocalSnackbarHost.current
onCheckedChange: (Boolean) -> Unit, val scope = rememberCoroutineScope()
onItemClick: () -> Unit
) {
Column { LaunchedEffect(Unit) {
ListItem( if (viewModel.appList.isEmpty()) {
headlineText = { Text(superUserData.name().toString()) }, viewModel.fetchAppList()
supportingText = { Text(superUserData.description) }, }
leadingContent = { }
Image(
painter = rememberDrawablePainter(drawable = superUserData.icon()), Scaffold(
contentDescription = superUserData.name.toString(), topBar = {
modifier = Modifier SearchAppBar(
.padding(4.dp) title = { Text(stringResource(R.string.module)) },
.width(48.dp) searchText = viewModel.search,
.height(48.dp) onSearchTextChange = { viewModel.search = it },
) onClearClick = { viewModel.search = "" }
)
}
) { innerPadding ->
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = {
scope.launch { viewModel.fetchAppList() }
}, },
trailingContent = { modifier = Modifier
Switch( .padding(innerPadding)
checked = checked, .fillMaxSize()
onCheckedChange = onCheckedChange, ) {
modifier = Modifier.padding(4.dp) LazyColumn {
) items(viewModel.appList) { app ->
} var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) }
) AppItem(app, isChecked) { checked ->
Divider(thickness = Dp.Hairline) val success = Natives.allowRoot(app.uid, checked)
} if (success) {
} isChecked = checked
} else scope.launch {
private fun getAppList(context: Context): List<SuperUserData> { snackbarHost.showSnackbar(failMessage.format(app.uid))
val pm = context.packageManager }
val allowList = Natives.getAllowList()
val denyList = Natives.getDenyList();
Log.i(TAG, "allowList: ${Arrays.toString(allowList)}")
Log.i(TAG, "denyList: ${Arrays.toString(denyList)}")
val result = mutableListOf<SuperUserData>()
// add allow list
for (uid in allowList) {
val packagesForUid = pm.getPackagesForUid(uid)
if (packagesForUid == null || packagesForUid.isEmpty()) {
Log.w(TAG, "uid $uid has no package")
continue
}
packagesForUid.forEach { packageName ->
val applicationInfo = pm.getApplicationInfo(packageName, 0)
result.add(
SuperUserData(
name = { applicationInfo.loadLabel(pm) },
description = applicationInfo.packageName,
icon = { applicationInfo.loadIcon(pm) },
uid = uid,
initialChecked = true
)
)
}
}
// add deny list
for (uid in denyList) {
val packagesForUid = pm.getPackagesForUid(uid)
if (packagesForUid == null || packagesForUid.isEmpty()) {
Log.w(TAG, "uid $uid has no package")
continue
}
packagesForUid.forEach { packageName ->
val applicationInfo = pm.getApplicationInfo(packageName, 0)
result.add(
SuperUserData(
name = { applicationInfo.loadLabel(pm) },
description = applicationInfo.packageName,
icon = { applicationInfo.loadIcon(pm) },
uid = uid,
initialChecked = false
)
)
}
}
// todo: use root to get all uids if possible
val apps = pm.getInstalledApplications(0)
// add other apps
for (app in apps) {
if (allowList.contains(app.uid) || denyList.contains(app.uid)) {
continue
}
result.add(
SuperUserData(
name = { app.loadLabel(pm) },
description = app.packageName,
icon = { app.loadIcon(pm) },
uid = app.uid,
initialChecked = false
)
)
}
return result
}
@SuppressLint("QueryPermissionsNeeded")
@Composable
fun SuperUser() {
val context = LocalContext.current
val list = getAppList(context)
val apps = remember { list.toMutableStateList() }
if (apps.isEmpty()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("No apps request superuser")
}
return
}
LazyColumn() {
items(apps, key = { it.description }) { app ->
SuperUserItem(
superUserData = app,
checked = app.checked,
onCheckedChange = { checked ->
val success = Natives.allowRoot(app.uid, checked)
if (success) {
app.checked = checked
} else {
Toast.makeText(
context,
"Failed to allow root: ${app.uid}",
Toast.LENGTH_SHORT
).show()
} }
},
onItemClick = {
// TODO
} }
) }
} }
} }
} }
@Preview @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Preview_SuperUser() { private fun AppItem(
SuperUser() app: SuperUserViewModel.AppInfo,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
ListItem(
headlineText = { Text(app.label) },
supportingText = { Text(app.packageName) },
leadingContent = {
Image(
painter = rememberDrawablePainter(app.icon),
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
trailingContent = {
Switch(
checked = isChecked,
onCheckedChange = onCheckedChange,
modifier = Modifier.padding(4.dp)
)
}
)
} }

View File

@@ -0,0 +1,8 @@
package me.weishu.kernelsu.ui.util
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
error("CompositionLocal LocalSnackbarController not present")
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu package me.weishu.kernelsu.ui.util
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
@@ -17,7 +16,10 @@ import androidx.compose.ui.text.style.TextDecoration
import java.util.regex.Pattern import java.util.regex.Pattern
@Composable @Composable
fun LinkifyText(text: String, modifier: Modifier = Modifier) { fun LinkifyText(
text: String,
modifier: Modifier = Modifier
) {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val layoutResult = remember { val layoutResult = remember {
mutableStateOf<TextLayoutResult?>(null) mutableStateOf<TextLayoutResult?>(null)
@@ -42,19 +44,21 @@ fun LinkifyText(text: String, modifier: Modifier = Modifier) {
) )
} }
} }
Text(text = annotatedString, modifier = modifier.pointerInput(Unit) { Text(
detectTapGestures { offsetPosition -> text = annotatedString,
layoutResult.value?.let { modifier = modifier.pointerInput(Unit) {
val position = it.getOffsetForPosition(offsetPosition) detectTapGestures { offsetPosition ->
annotatedString.getStringAnnotations(position, position).firstOrNull() layoutResult.value?.let {
?.let { result -> val position = it.getOffsetForPosition(offsetPosition)
if (result.tag == "URL") { annotatedString.getStringAnnotations(position, position).firstOrNull()
uriHandler.openUri(result.item) ?.let { result ->
if (result.tag == "URL") {
uriHandler.openUri(result.item)
}
} }
} }
} }
} },
},
onTextLayout = { layoutResult.value = it } onTextLayout = { layoutResult.value = it }
) )
} }
@@ -66,27 +70,18 @@ private val urlPattern: Pattern = Pattern.compile(
Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
) )
fun extractUrls(text: String): List<LinkInfos> { private data class LinkInfo(
val matcher = urlPattern.matcher(text)
var matchStart: Int
var matchEnd: Int
val links = arrayListOf<LinkInfos>()
while (matcher.find()) {
matchStart = matcher.start(1)
matchEnd = matcher.end()
var url = text.substring(matchStart, matchEnd)
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "https://$url"
links.add(LinkInfos(url, matchStart, matchEnd))
}
return links
}
data class LinkInfos(
val url: String, val url: String,
val start: Int, val start: Int,
val end: Int val end: Int
) )
private fun extractUrls(text: String): List<LinkInfo> = buildList {
val matcher = urlPattern.matcher(text)
while (matcher.find()) {
val matchStart = matcher.start(1)
val matchEnd = matcher.end()
val url = text.substring(matchStart, matchEnd).replaceFirst("http://", "https://")
add(LinkInfo(url, matchStart, matchEnd))
}
}

View File

@@ -0,0 +1,76 @@
package me.weishu.kernelsu.ui.viewmodel
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import java.text.Collator
import java.util.*
class SuperUserViewModel : ViewModel() {
companion object {
private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
}
class AppInfo(
val label: String,
val packageName: String,
val icon: Drawable,
val uid: Int,
val onAllowList: Boolean,
val onDenyList: Boolean
)
var search by mutableStateOf("")
var isRefreshing by mutableStateOf(false)
private set
private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> {
when {
it.onAllowList -> 0
it.onDenyList -> 1
else -> 2
}
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
apps.sortedWith(comparator).also {
isRefreshing = false
}
}
val appList by derivedStateOf {
sortedList.filter {
it.label.contains(search) || it.packageName.contains(search)
}
}
suspend fun fetchAppList() {
withContext(Dispatchers.IO) {
isRefreshing = true
val pm = ksuApp.packageManager
val allowList = Natives.getAllowList().toSet()
val denyList = Natives.getDenyList().toSet()
Log.i(TAG, "allowList: $allowList")
Log.i(TAG, "denyList: $denyList")
apps = pm.getInstalledApplications(0).map {
AppInfo(
label = it.loadLabel(pm).toString(),
packageName = it.packageName,
icon = it.loadIcon(pm),
uid = it.uid,
onAllowList = it.uid in allowList,
onDenyList = it.uid in denyList
)
}
}
}
}

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" />
</vector>

View File

@@ -1,170 +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">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M16,5V11H21V5M10,11H15V5H10M16,18H21V12H16M10,18H15V12H10M4,18H9V12H4M4,11H9V5H4V11Z" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M12,12H19C18.47,16.11 15.72,19.78 12,20.92V12H5V6.3L12,3.19M12,1L3,5V11C3,16.55 6.84,21.73 12,23C17.16,21.73 21,16.55 21,11V5L12,1Z" />
</vector>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="yellow_700">#FFb7a400</color>
</resources>

View File

@@ -1,6 +1,18 @@
<resources> <resources>
<string name="app_name">KernelSU</string> <string name="app_name">KernelSU</string>
<string name="home">Home</string> <string name="home">Home</string>
<string name="home_not_installed">Not installed</string>
<string name="home_click_to_install">Click to install</string>
<string name="home_working">Working</string>
<string name="home_working_version">Version: %d</string>
<string name="home_unsupported">Unsupported</string>
<string name="home_unsupported_reason">KernelSU only supports GKI kernels now</string>
<string name="home_copied_to_clipboard">Copied to clipboard</string>
<string name="home_support">Support</string>
<string name="superuser">Superuser</string> <string name="superuser">Superuser</string>
<string name="superuser_failed_to_grant_root">Failed to grant root for %d</string>
<string name="module">Module</string> <string name="module">Module</string>
</resources> </resources>

View File

@@ -1,10 +0,0 @@
buildscript {
ext {
compose_ui_version = '1.3.0'
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}

102
manager/build.gradle.kts Normal file
View File

@@ -0,0 +1,102 @@
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.gradle.BaseExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.konan.properties.Properties
plugins {
id("com.android.application") apply false
id("com.android.library") apply false
kotlin("android") apply false
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath(kotlin("gradle-plugin", version = "1.7.20"))
}
}
val androidMinSdk = 26
val androidTargetSdk = 33
val androidCompileSdk = 33
val androidBuildToolsVersion = "33.0.1"
val androidSourceCompatibility = JavaVersion.VERSION_11
val androidTargetCompatibility = JavaVersion.VERSION_11
val managerVersionCode = 10013
val managerVersionName = "0.1.3"
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
fun Project.configureBaseExtension() {
extensions.findByType<BaseExtension>()?.run {
compileSdkVersion(androidCompileSdk)
buildToolsVersion = androidBuildToolsVersion
defaultConfig {
minSdk = androidMinSdk
targetSdk = androidTargetSdk
versionCode = managerVersionCode
versionName = managerVersionName
consumerProguardFiles("proguard-rules.pro")
}
val signFile = file("sign.properties")
val config = if (signFile.canRead()) {
val prop = Properties()
prop.load(file("sign.properties").inputStream())
signingConfigs.create("config") {
storeFile = file(prop.getProperty("KEYSTORE_FILE"))
storePassword = prop.getProperty("KEYSTORE_PASSWORD")
keyAlias = prop.getProperty("KEY_ALIAS")
keyPassword = prop.getProperty("KEY_PASSWORD")
}
} else {
signingConfigs["debug"]
}
buildTypes {
all {
signingConfig = config
}
named("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}
extensions.findByType<ApplicationExtension>()?.run {
buildTypes {
named("release") {
isShrinkResources = true
}
}
}
extensions.findByType<KotlinCompile>()?.run {
kotlinOptions {
jvmTarget = "11"
}
}
}
}
subprojects {
plugins.withId("com.android.application") {
configureBaseExtension()
}
plugins.withId("com.android.library") {
configureBaseExtension()
}
}

View File

@@ -1,6 +1,5 @@
#Thu Dec 08 17:40:48 CST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,16 +0,0 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "KernelSU"
include ':app'

View File

@@ -0,0 +1,28 @@
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
plugins {
val agp = "7.3.1"
val kotlin = "1.7.20"
id("com.android.application") version agp
id("com.android.library") version agp
id("com.google.devtools.ksp") version "$kotlin-1.0.8"
kotlin("android") version kotlin
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "KernelSU"
include(":app")

View File

@@ -1,16 +0,0 @@
def ksFile = rootProject.file('sign.properties')
def props = new Properties()
if (ksFile.canRead()) {
props.load(new FileInputStream(ksFile))
if (props != null) {
android.signingConfigs.sign.storeFile file(props['KEYSTORE_FILE'])
android.signingConfigs.sign.storePassword props['KEYSTORE_PASSWORD']
android.signingConfigs.sign.keyAlias props['KEY_ALIAS']
android.signingConfigs.sign.keyPassword props['KEY_PASSWORD']
}
} else {
android.signingConfigs.sign.storeFile = android.signingConfigs.debug.storeFile
android.signingConfigs.sign.storePassword = android.signingConfigs.debug.storePassword
android.signingConfigs.sign.keyAlias = android.signingConfigs.debug.keyAlias
android.signingConfigs.sign.keyPassword = android.signingConfigs.debug.keyPassword
}