Manager PatchOptionsPage

This commit is contained in:
Nullptr 2022-02-13 01:29:37 +08:00
parent 6285ebe6c4
commit 6dda688adb
6 changed files with 278 additions and 57 deletions

View File

@ -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()
}
}
}

View File

@ -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
)

View File

@ -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"
)
}
}

View File

@ -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)

View File

@ -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) {
}

View File

@ -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>