Manager PatchOptionsPage
This commit is contained in:
parent
6285ebe6c4
commit
6dda688adb
|
|
@ -4,7 +4,7 @@ import org.lsposed.patch.LSPatch
|
|||
|
||||
object Patcher {
|
||||
class Options(
|
||||
private val apkPaths: List<String>,
|
||||
private val apkPaths: Array<String>,
|
||||
private val outputPath: String,
|
||||
private val debuggable: Boolean,
|
||||
private val sigbypassLevel: Int,
|
||||
|
|
@ -18,7 +18,7 @@ object Patcher {
|
|||
) {
|
||||
fun toStringArray(): Array<String> {
|
||||
return arrayListOf<String>().run {
|
||||
add("-f");
|
||||
add("-f")
|
||||
addAll(apkPaths)
|
||||
add("-o"); add(outputPath)
|
||||
if (debuggable) add("-d")
|
||||
|
|
@ -41,4 +41,4 @@ object Patcher {
|
|||
fun patch(options: Options) {
|
||||
LSPatch(*options.toStringArray()).doCommandLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ object SelectionColumnScope {
|
|||
|
||||
@Composable
|
||||
fun SelectionItem(
|
||||
modifier: Modifier = Modifier,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
desc: String? = null,
|
||||
|
|
@ -45,7 +45,8 @@ object SelectionColumnScope {
|
|||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
|
|
@ -59,9 +60,9 @@ object SelectionColumnScope {
|
|||
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom)
|
||||
) {
|
||||
Column {
|
||||
desc?.let {
|
||||
if (desc != null) {
|
||||
Text(
|
||||
text = it,
|
||||
text = desc,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
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 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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Api
|
||||
import androidx.compose.material.icons.outlined.AutoFixHigh
|
||||
import androidx.compose.material.icons.outlined.BugReport
|
||||
import androidx.compose.material.icons.outlined.WorkOutline
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -17,76 +20,158 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.TAG
|
||||
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.viewmodel.AppInfo
|
||||
|
||||
class NewPatchPageViewModel : ViewModel() {
|
||||
var patchApp by mutableStateOf<AppInfo?>(null)
|
||||
var confirm by mutableStateOf(false)
|
||||
var patchOptions by mutableStateOf<Patcher.Options?>(null)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewPatchFab() {
|
||||
val navController = LocalNavController.current
|
||||
val patchApp by navController.currentBackStackEntry!!.savedStateHandle
|
||||
.getLiveData<AppInfo>("appInfo").observeAsState()
|
||||
if (patchApp != null) {
|
||||
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||
if (viewModel.patchApp != null) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(stringResource(R.string.patch_start)) },
|
||||
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
||||
onClick = { /*TODO*/ }
|
||||
onClick = { viewModel.confirm = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewPatchPage() {
|
||||
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||
val navController = LocalNavController.current
|
||||
val patchApp by navController.currentBackStackEntry!!.savedStateHandle
|
||||
val appInfo by navController.currentBackStackEntry!!.savedStateHandle
|
||||
.getLiveData<AppInfo>("appInfo").observeAsState()
|
||||
viewModel.patchApp = appInfo
|
||||
|
||||
Log.d(TAG, "patchApp is $patchApp")
|
||||
if (patchApp == null) {
|
||||
navController.navigate(PageList.SelectApps.name + "/false")
|
||||
} else {
|
||||
var useManager by rememberSaveable { mutableStateOf(true) }
|
||||
Log.d(TAG, "confirm = ${viewModel.confirm}")
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(top = 24.dp)
|
||||
) {
|
||||
Text(text = patchApp!!.label, style = MaterialTheme.typography.headlineSmall)
|
||||
Text(text = patchApp!!.app.packageName, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(
|
||||
text = stringResource(R.string.patch_mode),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 24.dp, bottom = 12.dp)
|
||||
)
|
||||
SelectionColumn {
|
||||
SelectionItem(
|
||||
selected = useManager,
|
||||
onClick = { useManager = true },
|
||||
icon = Icons.Outlined.Api,
|
||||
title = stringResource(R.string.patch_local),
|
||||
desc = stringResource(R.string.patch_local_desc)
|
||||
)
|
||||
SelectionItem(
|
||||
selected = !useManager,
|
||||
onClick = { useManager = false },
|
||||
icon = Icons.Outlined.WorkOutline,
|
||||
title = stringResource(R.string.patch_portable),
|
||||
desc = stringResource(R.string.patch_portable_desc),
|
||||
extraContent = {
|
||||
TextButton(
|
||||
onClick = { /* TODO */ }
|
||||
) {
|
||||
Text(text = stringResource(R.string.patch_embed_modules), style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
when {
|
||||
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 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(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
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 = stringResource(R.string.patch_mode),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 24.dp, bottom = 12.dp)
|
||||
)
|
||||
SelectionColumn(Modifier.padding(horizontal = 24.dp)) {
|
||||
SelectionItem(
|
||||
selected = useManager,
|
||||
onClick = { useManager = true },
|
||||
icon = Icons.Outlined.Api,
|
||||
title = stringResource(R.string.patch_local),
|
||||
desc = stringResource(R.string.patch_local_desc)
|
||||
)
|
||||
SelectionItem(
|
||||
selected = !useManager,
|
||||
onClick = { useManager = false },
|
||||
icon = Icons.Outlined.WorkOutline,
|
||||
title = stringResource(R.string.patch_portable),
|
||||
desc = stringResource(R.string.patch_portable_desc),
|
||||
extraContent = {
|
||||
TextButton(
|
||||
onClick = { /* TODO */ }
|
||||
) {
|
||||
Text(text = stringResource(R.string.patch_embed_modules), style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
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_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_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>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue