Manager PatchOptionsPage
This commit is contained in:
parent
6285ebe6c4
commit
6dda688adb
|
|
@ -4,7 +4,7 @@ import org.lsposed.patch.LSPatch
|
||||||
|
|
||||||
object Patcher {
|
object Patcher {
|
||||||
class Options(
|
class Options(
|
||||||
private val apkPaths: List<String>,
|
private val apkPaths: Array<String>,
|
||||||
private val outputPath: String,
|
private val outputPath: String,
|
||||||
private val debuggable: Boolean,
|
private val debuggable: Boolean,
|
||||||
private val sigbypassLevel: Int,
|
private val sigbypassLevel: Int,
|
||||||
|
|
@ -18,7 +18,7 @@ object Patcher {
|
||||||
) {
|
) {
|
||||||
fun toStringArray(): Array<String> {
|
fun toStringArray(): Array<String> {
|
||||||
return arrayListOf<String>().run {
|
return arrayListOf<String>().run {
|
||||||
add("-f");
|
add("-f")
|
||||||
addAll(apkPaths)
|
addAll(apkPaths)
|
||||||
add("-o"); add(outputPath)
|
add("-o"); add(outputPath)
|
||||||
if (debuggable) add("-d")
|
if (debuggable) add("-d")
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ object SelectionColumnScope {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectionItem(
|
fun SelectionItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
title: String,
|
title: String,
|
||||||
desc: String? = null,
|
desc: String? = null,
|
||||||
|
|
@ -45,7 +45,8 @@ object SelectionColumnScope {
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -59,9 +60,9 @@ object SelectionColumnScope {
|
||||||
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom)
|
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
desc?.let {
|
if (desc != null) {
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = desc,
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.lsposed.lspatch.ui.component.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Api
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsCheckBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
checked: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
title: String,
|
||||||
|
desc: String? = null,
|
||||||
|
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent) {
|
||||||
|
Checkbox(checked = checked, onCheckedChange = { onClick() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SettingsCheckBoxPreview() {
|
||||||
|
var checked1 by remember { mutableStateOf(false) }
|
||||||
|
var checked2 by remember { mutableStateOf(false) }
|
||||||
|
Column {
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = checked1,
|
||||||
|
onClick = { checked1 = !checked1 },
|
||||||
|
title = "Title",
|
||||||
|
desc = "Description"
|
||||||
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = checked2,
|
||||||
|
onClick = { checked2 = !checked2 },
|
||||||
|
icon = Icons.Outlined.Api,
|
||||||
|
title = "Title",
|
||||||
|
desc = "Description"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.lsposed.lspatch.ui.component.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsSlot(
|
||||||
|
modifier: Modifier,
|
||||||
|
enabled: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
title: String,
|
||||||
|
desc: String?,
|
||||||
|
extraContent: (@Composable ColumnScope.() -> Unit)? = null,
|
||||||
|
action: (@Composable RowScope.() -> Unit)?,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth() then (
|
||||||
|
if (enabled) Modifier
|
||||||
|
.alpha(1f)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
else Modifier.alpha(0.5f)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.size(24.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
Text(text = title, style = MaterialTheme.typography.titleMedium)
|
||||||
|
Column {
|
||||||
|
if (desc != null) {
|
||||||
|
Text(
|
||||||
|
text = desc,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.alpha(0.75f).padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
extraContent?.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action?.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
title: String,
|
||||||
|
desc: String? = null,
|
||||||
|
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||||
|
) = SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent, null)
|
||||||
|
|
@ -2,12 +2,15 @@ package org.lsposed.lspatch.ui.page
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Api
|
import androidx.compose.material.icons.outlined.Api
|
||||||
import androidx.compose.material.icons.outlined.AutoFixHigh
|
import androidx.compose.material.icons.outlined.AutoFixHigh
|
||||||
|
import androidx.compose.material.icons.outlined.BugReport
|
||||||
import androidx.compose.material.icons.outlined.WorkOutline
|
import androidx.compose.material.icons.outlined.WorkOutline
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
|
@ -17,46 +20,91 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import org.lsposed.lspatch.Patcher
|
||||||
import org.lsposed.lspatch.R
|
import org.lsposed.lspatch.R
|
||||||
import org.lsposed.lspatch.TAG
|
import org.lsposed.lspatch.TAG
|
||||||
import org.lsposed.lspatch.ui.component.SelectionColumn
|
import org.lsposed.lspatch.ui.component.SelectionColumn
|
||||||
|
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
||||||
|
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||||
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
||||||
|
|
||||||
|
class NewPatchPageViewModel : ViewModel() {
|
||||||
|
var patchApp by mutableStateOf<AppInfo?>(null)
|
||||||
|
var confirm by mutableStateOf(false)
|
||||||
|
var patchOptions by mutableStateOf<Patcher.Options?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPatchFab() {
|
fun NewPatchFab() {
|
||||||
val navController = LocalNavController.current
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
val patchApp by navController.currentBackStackEntry!!.savedStateHandle
|
if (viewModel.patchApp != null) {
|
||||||
.getLiveData<AppInfo>("appInfo").observeAsState()
|
|
||||||
if (patchApp != null) {
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(stringResource(R.string.patch_start)) },
|
text = { Text(stringResource(R.string.patch_start)) },
|
||||||
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
||||||
onClick = { /*TODO*/ }
|
onClick = { viewModel.confirm = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPatchPage() {
|
fun NewPatchPage() {
|
||||||
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val patchApp by navController.currentBackStackEntry!!.savedStateHandle
|
val appInfo by navController.currentBackStackEntry!!.savedStateHandle
|
||||||
.getLiveData<AppInfo>("appInfo").observeAsState()
|
.getLiveData<AppInfo>("appInfo").observeAsState()
|
||||||
|
viewModel.patchApp = appInfo
|
||||||
|
|
||||||
Log.d(TAG, "patchApp is $patchApp")
|
Log.d(TAG, "confirm = ${viewModel.confirm}")
|
||||||
if (patchApp == null) {
|
|
||||||
navController.navigate(PageList.SelectApps.name + "/false")
|
when {
|
||||||
} else {
|
viewModel.patchApp == null -> navController.navigate(PageList.SelectApps.name + "/false")
|
||||||
|
viewModel.patchOptions == null -> PatchOptionsPage(viewModel.patchApp!!, viewModel.confirm)
|
||||||
|
else -> PatchingPage(viewModel.patchOptions!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PatchOptionsPage(patchApp: AppInfo, confirm: Boolean) {
|
||||||
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
var useManager by rememberSaveable { mutableStateOf(true) }
|
var useManager by rememberSaveable { mutableStateOf(true) }
|
||||||
|
var debuggable by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var v1 by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var v2 by rememberSaveable { mutableStateOf(true) }
|
||||||
|
var v3 by rememberSaveable { mutableStateOf(true) }
|
||||||
|
val sigBypassLevel by rememberSaveable { mutableStateOf(2) }
|
||||||
|
var overrideVersionCode by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (confirm) LaunchedEffect(patchApp) {
|
||||||
|
viewModel.patchOptions = Patcher.Options(
|
||||||
|
apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk
|
||||||
|
debuggable = debuggable,
|
||||||
|
sigbypassLevel = sigBypassLevel,
|
||||||
|
v1 = v1, v2 = v2, v3 = v3,
|
||||||
|
useManager = useManager,
|
||||||
|
overrideVersionCode = overrideVersionCode,
|
||||||
|
verbose = true,
|
||||||
|
embeddedModules = emptyList() // TODO: Embed modules
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 24.dp)
|
|
||||||
.padding(top = 24.dp)
|
.padding(top = 24.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = patchApp!!.label, style = MaterialTheme.typography.headlineSmall)
|
Text(
|
||||||
Text(text = patchApp!!.app.packageName, style = MaterialTheme.typography.bodyLarge)
|
text = patchApp.label,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = patchApp.app.packageName,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.patch_mode),
|
text = stringResource(R.string.patch_mode),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
|
@ -64,7 +112,7 @@ fun NewPatchPage() {
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
.padding(top = 24.dp, bottom = 12.dp)
|
.padding(top = 24.dp, bottom = 12.dp)
|
||||||
)
|
)
|
||||||
SelectionColumn {
|
SelectionColumn(Modifier.padding(horizontal = 24.dp)) {
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
selected = useManager,
|
selected = useManager,
|
||||||
onClick = { useManager = true },
|
onClick = { useManager = true },
|
||||||
|
|
@ -87,6 +135,43 @@ fun NewPatchPage() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
SettingsCheckBox(
|
||||||
|
checked = debuggable,
|
||||||
|
onClick = { debuggable = !debuggable },
|
||||||
|
icon = Icons.Outlined.BugReport,
|
||||||
|
title = stringResource(R.string.patch_debuggable)
|
||||||
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = v1,
|
||||||
|
onClick = { v1 = !v1 },
|
||||||
|
title = stringResource(R.string.patch_v1)
|
||||||
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = v2,
|
||||||
|
onClick = { v2 = !v2 },
|
||||||
|
title = stringResource(R.string.patch_v2)
|
||||||
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = v3,
|
||||||
|
onClick = { v3 = !v3 },
|
||||||
|
title = stringResource(R.string.patch_v3)
|
||||||
|
)
|
||||||
|
SettingsItem(
|
||||||
|
onClick = { /*TODO*/ },
|
||||||
|
title = stringResource(R.string.patch_sigbypasslv),
|
||||||
|
desc = stringResource(R.string.patch_sigbypasslv_desc)
|
||||||
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
checked = overrideVersionCode,
|
||||||
|
onClick = { overrideVersionCode = !overrideVersionCode },
|
||||||
|
title = stringResource(R.string.patch_override_version_code),
|
||||||
|
desc = stringResource(R.string.patch_override_version_code_desc)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(56.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PatchingPage(patcherOptions: Patcher.Options) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,13 @@
|
||||||
<string name="patch_portable">Portable</string>
|
<string name="patch_portable">Portable</string>
|
||||||
<string name="patch_portable_desc">Patch an app with modules embedded.\nThe patched app can run without the manager, but cannot be managed dynamically.\nPortable patched apps can be used on devices that do not have LSPatch Manager installed.</string>
|
<string name="patch_portable_desc">Patch an app with modules embedded.\nThe patched app can run without the manager, but cannot be managed dynamically.\nPortable patched apps can be used on devices that do not have LSPatch Manager installed.</string>
|
||||||
<string name="patch_embed_modules">Embed modules</string>
|
<string name="patch_embed_modules">Embed modules</string>
|
||||||
|
<string name="patch_debuggable">Debuggable</string>
|
||||||
|
<string name="patch_v1">V1 signature</string>
|
||||||
|
<string name="patch_v2">V2 signature</string>
|
||||||
|
<string name="patch_v3">V3 signature</string>
|
||||||
|
<string name="patch_sigbypasslv">Signature bypass level</string>
|
||||||
|
<string name="patch_sigbypasslv_desc">lv0: off\nlv1: bypass pm\nlv2: bypass openat (libc)</string>
|
||||||
|
<string name="patch_override_version_code">Override version code</string>
|
||||||
|
<string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation</string>
|
||||||
<string name="patch_start">Start Patch</string>
|
<string name="patch_start">Start Patch</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue