From 7dd1a5c91472e019052235160c52876bb799f4ad Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:10:37 -0800 Subject: [PATCH] feat: custom smali bundle selection --- .../aliucord/manager/manager/PathManager.kt | 24 +++- .../steps/download/DownloadPatchesStep.kt | 40 ++++++- .../dialogs/CustomComponentVersionPicker.kt | 111 ++++++++++++++++++ 3 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/CustomComponentVersionPicker.kt diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt index 8d70353..fc9f018 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt @@ -73,9 +73,14 @@ class PathManager(context: Context) { /** * Resolve a specific path for a versioned smali patches archive. */ - fun cachedSmaliPatches(version: SemVer) = externalCacheDir + fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = externalCacheDir .resolve("patches").apply { mkdirs() } - .resolve("$version.zip") + .resolve("$version${if (custom) ".custom" else ""}.zip") + + /** + * Get all the versions of custom smali bundles. + */ + fun customSmaliPatches() = listCustomFiles(externalCacheDir.resolve("patches")) /** * Singular Kotlin file of the most up-to-date version @@ -89,4 +94,19 @@ class PathManager(context: Context) { */ fun patchingWorkingDir() = externalCacheDir .resolve("patched") + + private companion object { + /** + * List all the files that follow the ```[SemVer].custom.*``` naming scheme. + */ + private fun listCustomFiles(dir: File): List { + val files = dir.listFiles() ?: return emptyList() + val customVersions = files + .map { it.nameWithoutExtension } + .filter { it.endsWith(".custom") } + .map { it.removeSuffix(".custom") } + + return customVersions.mapNotNull(SemVer::parseOrNull) + } + } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadPatchesStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadPatchesStep.kt index d825ab7..1064281 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadPatchesStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadPatchesStep.kt @@ -1,13 +1,17 @@ package com.aliucord.manager.patcher.steps.download +import android.util.Log import androidx.compose.runtime.Stable +import com.aliucord.manager.BuildConfig import com.aliucord.manager.R import com.aliucord.manager.manager.OverlayManager import com.aliucord.manager.manager.PathManager import com.aliucord.manager.network.utils.SemVer import com.aliucord.manager.patcher.StepRunner import com.aliucord.manager.patcher.steps.base.DownloadStep +import com.aliucord.manager.patcher.steps.base.StepState import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep +import com.aliucord.manager.ui.components.dialogs.CustomComponentVersionPicker import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -22,14 +26,48 @@ class DownloadPatchesStep : DownloadStep(), KoinComponent { /** * This is populated right before the download starts (ref: [execute]) */ + private var isCustomVersion: Boolean = false lateinit var targetVersion: SemVer private set override val localizedName = R.string.patch_step_dl_smali override val targetUrl get() = URL - override val targetFile get() = paths.cachedSmaliPatches(targetVersion) + override val targetFile get() = paths.cachedSmaliPatches(targetVersion, isCustomVersion) override suspend fun execute(container: StepRunner) { + var customVersions = mutableListOf() + .apply { addAll(paths.customSmaliPatches()) } + + // Prompt to select or manage custom versions instead of downloading + if (customVersions.isNotEmpty()) { + val selectedVersion = overlays.startComposableForResult { callback -> + CustomComponentVersionPicker( + componentTitle = "Smali Patches", + versions = customVersions, + onConfirm = { version -> callback(version) }, + onDelete = { version -> + try { + paths.cachedSmaliPatches(version, custom = true).delete() + customVersions.remove(version) + + // Dismiss if no custom versions left + if (customVersions.isEmpty()) + callback(null) + } catch (t: Throwable) { + Log.e(BuildConfig.TAG, "Failed to delete custom component", t) + } + }, + onCancel = { callback(null) }, + ) + } + if (selectedVersion != null) { + isCustomVersion = true + targetVersion = selectedVersion + state = StepState.Skipped + return + } + } + targetVersion = container.getStep() .data.patchesVersion diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/CustomComponentVersionPicker.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/CustomComponentVersionPicker.kt new file mode 100644 index 0000000..4b9cc36 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/CustomComponentVersionPicker.kt @@ -0,0 +1,111 @@ +package com.aliucord.manager.ui.components.dialogs + +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties +import com.aliucord.manager.R +import com.aliucord.manager.network.utils.SemVer + +@Composable +fun CustomComponentVersionPicker( + componentTitle: String, + versions: List, + onConfirm: (SemVer) -> Unit, + onDelete: (SemVer) -> Unit, + onCancel: () -> Unit, +) { + var selectedVersion by remember { mutableStateOf(null) } + + AlertDialog( + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = false, + ), + onDismissRequest = onCancel, + confirmButton = { + FilledTonalButton( + onClick = { onConfirm(selectedVersion!!) }, + enabled = selectedVersion != null, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) { + Text(stringResource(R.string.action_confirm)) + } + }, + dismissButton = { + TextButton( + onClick = onCancel, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colorScheme.error, + ), + ) { + Text(stringResource(R.string.action_dismiss)) + } + }, + title = { Text("Custom Component") }, + icon = { Icon(painterResource(R.drawable.ic_download), contentDescription = null) }, + text = { + Column { + Text( + text = "A custom local version for a patching component ($componentTitle) has been found. Would you like to use it?", + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 6.dp), + ) + + Column( + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + for (version in versions) key(version) { + val clickSource = remember(::MutableInteractionSource) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = clickSource, + indication = LocalIndication.current, + onClick = { selectedVersion = version }, + ), + ) { + RadioButton( + selected = selectedVersion == version, + onClick = { selectedVersion = version }, + interactionSource = clickSource, + ) + + Text( + text = "v$version", + style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold), + ) + + Spacer(Modifier.weight(1f)) + IconButton( + onClick = { onDelete(version) }, + ) { + Icon( + painter = painterResource(R.drawable.ic_delete_forever), + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + ) + } + } + } + } + } + }, + ) +}