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 { 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")

View File

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

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

View File

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