feat: select module(s) from storage (#225)

This commit is contained in:
Js0n 2023-10-12 14:10:53 +08:00 committed by GitHub
parent b0fbc7341c
commit 8462043963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 18 deletions

View File

@ -68,6 +68,7 @@ fun NewPatchScreen(
) { ) {
val viewModel = viewModel<NewPatchViewModel>() val viewModel = viewModel<NewPatchViewModel>()
val snackbarHost = LocalSnackbarHost.current val snackbarHost = LocalSnackbarHost.current
val errorUnknown = stringResource(R.string.error_unknown)
val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks -> val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
if (apks.isEmpty()) { if (apks.isEmpty()) {
navigator.navigateUp() navigator.navigateUp()
@ -76,15 +77,40 @@ fun NewPatchScreen(
runBlocking { runBlocking {
LSPPackageManager.getAppInfoFromApks(apks) LSPPackageManager.getAppInfoFromApks(apks)
.onSuccess { .onSuccess {
viewModel.dispatch(ViewAction.ConfigurePatch(it)) viewModel.dispatch(ViewAction.ConfigurePatch(it.first()))
} }
.onFailure { .onFailure {
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") } lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: errorUnknown) }
navigator.navigateUp() navigator.navigateUp()
} }
} }
} }
var showSelectModuleDialog by remember { mutableStateOf(false) }
val noXposedModules = stringResource(R.string.patch_no_xposed_module)
val storageModuleLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
if (apks.isEmpty()) {
return@rememberLauncherForActivityResult
}
runBlocking {
LSPPackageManager.getAppInfoFromApks(apks).onSuccess { it ->
viewModel.embeddedModules = it.filter { it.isXposedModule }.ifEmpty {
lspApp.globalScope.launch {
snackbarHost.showSnackbar(noXposedModules)
}
return@onSuccess
}
}.onFailure {
lspApp.globalScope.launch {
snackbarHost.showSnackbar(
it.message ?: errorUnknown
)
}
}
}
}
Log.d(TAG, "PatchState: ${viewModel.patchState}") Log.d(TAG, "PatchState: ${viewModel.patchState}")
when (viewModel.patchState) { when (viewModel.patchState) {
PatchState.INIT -> { PatchState.INIT -> {
@ -128,7 +154,7 @@ fun NewPatchScreen(
) { innerPadding -> ) { innerPadding ->
if (viewModel.patchState == PatchState.CONFIGURING) { if (viewModel.patchState == PatchState.CONFIGURING) {
PatchOptionsBody(Modifier.padding(innerPadding)) { PatchOptionsBody(Modifier.padding(innerPadding)) {
navigator.navigate(SelectAppsScreenDestination(true, viewModel.embeddedModules.mapTo(ArrayList()) { it.app.packageName })) showSelectModuleDialog = true
} }
resultRecipient.onNavResult { resultRecipient.onNavResult {
if (it is NavResult.Value) { if (it is NavResult.Value) {
@ -140,6 +166,53 @@ fun NewPatchScreen(
DoPatchBody(Modifier.padding(innerPadding), navigator) DoPatchBody(Modifier.padding(innerPadding), navigator)
} }
} }
if (showSelectModuleDialog) {
AlertDialog(onDismissRequest = { showSelectModuleDialog = false },
confirmButton = {},
dismissButton = {
TextButton(content = { Text(stringResource(android.R.string.cancel)) },
onClick = { showSelectModuleDialog = false })
},
title = {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.patch_embed_modules),
textAlign = TextAlign.Center
)
},
text = {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
TextButton(modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
onClick = {
storageModuleLauncher.launch(arrayOf("application/vnd.android.package-archive"))
showSelectModuleDialog = false
}) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = stringResource(R.string.patch_from_storage),
style = MaterialTheme.typography.bodyLarge
)
}
TextButton(modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
onClick = {
navigator.navigate(
SelectAppsScreenDestination(true,
viewModel.embeddedModules.mapTo(ArrayList()) { it.app.packageName })
)
showSelectModuleDialog = false
}) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = stringResource(R.string.patch_from_applist),
style = MaterialTheme.typography.bodyLarge
)
}
}
})
}
} }
} }
} }

View File

@ -152,11 +152,12 @@ object LSPPackageManager {
return Pair(status, message) return Pair(status, message)
} }
suspend fun getAppInfoFromApks(apks: List<Uri>): Result<AppInfo> { suspend fun getAppInfoFromApks(apks: List<Uri>): Result<List<AppInfo>> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
runCatching { runCatching {
var primary: ApplicationInfo? = null var primary: ApplicationInfo? = null
val splits = apks.mapNotNull { uri -> val splits = mutableListOf<String>()
val appInfos = apks.mapNotNull { uri ->
val src = DocumentFile.fromSingleUri(lspApp, uri) val src = DocumentFile.fromSingleUri(lspApp, uri)
?: throw IOException("DocumentFile is null") ?: throw IOException("DocumentFile is null")
val dst = lspApp.tmpApkDir.resolve(src.name!!) val dst = lspApp.tmpApkDir.resolve(src.name!!)
@ -167,21 +168,25 @@ object LSPPackageManager {
input.copyTo(output) input.copyTo(output)
} }
} }
if (primary == null) {
primary = lspApp.packageManager.getPackageArchiveInfo(dst.absolutePath, 0)?.applicationInfo
primary?.let {
it.sourceDir = dst.absolutePath
return@mapNotNull null
}
}
dst.absolutePath
}
val appInfo = lspApp.packageManager.getPackageArchiveInfo(
dst.absolutePath, PackageManager.GET_META_DATA
)?.applicationInfo
appInfo?.sourceDir = dst.absolutePath
if (appInfo == null) {
splits.add(dst.absolutePath)
return@mapNotNull null
}
if (primary == null) {
primary = appInfo
}
val label = lspApp.packageManager.getApplicationLabel(appInfo).toString()
AppInfo(appInfo, label)
}
// TODO: Check selected apks are from the same app // TODO: Check selected apks are from the same app
if (primary == null) throw IllegalArgumentException("No primary apk") primary?.splitSourceDirs = splits.toTypedArray()
val label = lspApp.packageManager.getApplicationLabel(primary!!).toString() if (appInfos.isEmpty()) throw IOException("No apks")
if (splits.isNotEmpty()) primary!!.splitSourceDirs = splits.toTypedArray() appInfos
AppInfo(primary!!, label)
}.recoverCatching { t -> }.recoverCatching { t ->
cleanTmpApkDir() cleanTmpApkDir()
Log.e(TAG, "Failed to load apks", t) Log.e(TAG, "Failed to load apks", t)

View File

@ -12,6 +12,7 @@
<string name="screen_repo">Repo</string> <string name="screen_repo">Repo</string>
<string name="screen_logs">Logs</string> <string name="screen_logs">Logs</string>
<string name="off">Off</string> <string name="off">Off</string>
<string name="error_unknown">Unknown error</string>
<!-- Home Screen --> <!-- Home Screen -->
<string name="home_shizuku_warning">Some functions unavailable</string> <string name="home_shizuku_warning">Some functions unavailable</string>
@ -68,6 +69,7 @@
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string> <string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
<string name="patch_install_successfully">Install successfully</string> <string name="patch_install_successfully">Install successfully</string>
<string name="patch_install_failed">Install failed</string> <string name="patch_install_failed">Install failed</string>
<string name="patch_no_xposed_module">No Xposed module(s) were found</string>
<!-- Select Apps Screen --> <!-- Select Apps Screen -->
<string name="screen_select_apps">Select Apps</string> <string name="screen_select_apps">Select Apps</string>