2
manager/.idea/compiler.xml
generated
2
manager/.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="19" />
|
||||
</component>
|
||||
</project>
|
||||
3
manager/.idea/gradle.xml
generated
3
manager/.idea/gradle.xml
generated
@@ -7,8 +7,7 @@
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="/usr/local/Cellar/gradle/6.4/libexec" />
|
||||
<option name="gradleJvm" value="11" />
|
||||
<option name="gradleJvm" value="19" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
||||
2
manager/.idea/misc.xml
generated
2
manager/.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -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')
|
||||
80
manager/app/build.gradle.kts
Normal file
80
manager/app/build.gradle.kts
Normal 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")
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent>
|
||||
</queries>
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:name=".KernelSUApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@@ -18,9 +17,8 @@
|
||||
android:theme="@style/Theme.KernelSU"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.KernelSU">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "" }
|
||||
)
|
||||
}
|
||||
@@ -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, {})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.system.Os
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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.CompositionLocalProvider
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
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.sp
|
||||
import me.weishu.kernelsu.Natives
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.*
|
||||
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
|
||||
fun Info(label: String, value: String) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("$label: ")
|
||||
withStyle(
|
||||
style = SpanStyle(
|
||||
fontWeight = FontWeight.W500,
|
||||
)
|
||||
) {
|
||||
append(value)
|
||||
}
|
||||
},
|
||||
softWrap = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Home() {
|
||||
|
||||
val statusIcon: Int
|
||||
val statusText: String
|
||||
val secondaryText: String
|
||||
|
||||
val kernelVersion = getKernelVersion()
|
||||
val isManager = Natives.becomeManager(LocalContext.current.packageName)
|
||||
|
||||
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(
|
||||
fun HomeScreen() {
|
||||
Scaffold(
|
||||
topBar = { TopBar() }
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(6.dp)
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI() && !isManager) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Unimplemented",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
val kernelVersion = getKernelVersion()
|
||||
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
|
||||
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
})
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
.clickable {
|
||||
// TODO: Install kernel
|
||||
}
|
||||
.padding(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = statusIcon),
|
||||
null,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
kernelVersion.isGKI() -> {
|
||||
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_not_installed),
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_click_to_install),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
ElevatedCard {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp),
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = statusText,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
val contents = StringBuilder()
|
||||
val uname = Os.uname()
|
||||
|
||||
Text(
|
||||
text = secondaryText,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal
|
||||
@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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Card(
|
||||
@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(6.dp)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp)
|
||||
) {
|
||||
|
||||
Os.uname().let { uname ->
|
||||
Info("Kernel", uname.release)
|
||||
Info("Arch", uname.machine)
|
||||
Info("Version", uname.version)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun Module() {
|
||||
fun ModuleScreen() {
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
|
||||
Text(text = "Coming Soon..")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun Preview() {
|
||||
Module()
|
||||
}
|
||||
@@ -1,55 +1,95 @@
|
||||
@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.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.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.*
|
||||
|
||||
private const val TAG = "SuperUser"
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SuperUserScreen() {
|
||||
val viewModel = viewModel<SuperUserViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
class SuperUserData(
|
||||
val name: () -> CharSequence,
|
||||
val description: String,
|
||||
val icon: () -> Drawable,
|
||||
val uid: Int,
|
||||
initialChecked: Boolean = false
|
||||
) {
|
||||
var checked: Boolean by mutableStateOf(initialChecked)
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SuperUserItem(
|
||||
superUserData: SuperUserData,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onItemClick: () -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.module)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" }
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
|
||||
Column {
|
||||
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(viewModel.isRefreshing),
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.fetchAppList() }
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
LazyColumn {
|
||||
items(viewModel.appList) { app ->
|
||||
var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) }
|
||||
AppItem(app, isChecked) { checked ->
|
||||
val success = Natives.allowRoot(app.uid, checked)
|
||||
if (success) {
|
||||
isChecked = checked
|
||||
} else scope.launch {
|
||||
snackbarHost.showSnackbar(failMessage.format(app.uid))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppItem(
|
||||
app: SuperUserViewModel.AppInfo,
|
||||
isChecked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
ListItem(
|
||||
headlineText = { Text(superUserData.name().toString()) },
|
||||
supportingText = { Text(superUserData.description) },
|
||||
headlineText = { Text(app.label) },
|
||||
supportingText = { Text(app.packageName) },
|
||||
leadingContent = {
|
||||
Image(
|
||||
painter = rememberDrawablePainter(drawable = superUserData.icon()),
|
||||
contentDescription = superUserData.name.toString(),
|
||||
painter = rememberDrawablePainter(app.icon),
|
||||
contentDescription = app.label,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.width(48.dp)
|
||||
@@ -58,134 +98,10 @@ fun SuperUserItem(
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = checked,
|
||||
checked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
Divider(thickness = Dp.Hairline)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppList(context: Context): List<SuperUserData> {
|
||||
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
|
||||
@Composable
|
||||
fun Preview_SuperUser() {
|
||||
SuperUser()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.weishu.kernelsu
|
||||
package me.weishu.kernelsu.ui.util
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
@@ -17,7 +16,10 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Composable
|
||||
fun LinkifyText(text: String, modifier: Modifier = Modifier) {
|
||||
fun LinkifyText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val layoutResult = remember {
|
||||
mutableStateOf<TextLayoutResult?>(null)
|
||||
@@ -42,7 +44,9 @@ fun LinkifyText(text: String, modifier: Modifier = Modifier) {
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(text = annotatedString, modifier = modifier.pointerInput(Unit) {
|
||||
Text(
|
||||
text = annotatedString,
|
||||
modifier = modifier.pointerInput(Unit) {
|
||||
detectTapGestures { offsetPosition ->
|
||||
layoutResult.value?.let {
|
||||
val position = it.getOffsetForPosition(offsetPosition)
|
||||
@@ -66,27 +70,18 @@ private val urlPattern: Pattern = Pattern.compile(
|
||||
Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
|
||||
)
|
||||
|
||||
fun extractUrls(text: String): List<LinkInfos> {
|
||||
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(
|
||||
private data class LinkInfo(
|
||||
val url: String,
|
||||
val start: 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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,6 +1,18 @@
|
||||
<resources>
|
||||
<string name="app_name">KernelSU</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_failed_to_grant_root">Failed to grant root for %d</string>
|
||||
|
||||
<string name="module">Module</string>
|
||||
</resources>
|
||||
@@ -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
102
manager/build.gradle.kts
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#Thu Dec 08 17:40:48 CST 2022
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -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'
|
||||
28
manager/settings.gradle.kts
Normal file
28
manager/settings.gradle.kts
Normal 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")
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user