Skip to content

Commit 7dd1a5c

Browse files
committed
feat: custom smali bundle selection
1 parent 562826c commit 7dd1a5c

File tree

3 files changed

+172
-3
lines changed

3 files changed

+172
-3
lines changed

app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt

+22-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,14 @@ class PathManager(context: Context) {
7373
/**
7474
* Resolve a specific path for a versioned smali patches archive.
7575
*/
76-
fun cachedSmaliPatches(version: SemVer) = externalCacheDir
76+
fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = externalCacheDir
7777
.resolve("patches").apply { mkdirs() }
78-
.resolve("$version.zip")
78+
.resolve("$version${if (custom) ".custom" else ""}.zip")
79+
80+
/**
81+
* Get all the versions of custom smali bundles.
82+
*/
83+
fun customSmaliPatches() = listCustomFiles(externalCacheDir.resolve("patches"))
7984

8085
/**
8186
* Singular Kotlin file of the most up-to-date version
@@ -89,4 +94,19 @@ class PathManager(context: Context) {
8994
*/
9095
fun patchingWorkingDir() = externalCacheDir
9196
.resolve("patched")
97+
98+
private companion object {
99+
/**
100+
* List all the files that follow the ```[SemVer].custom.*``` naming scheme.
101+
*/
102+
private fun listCustomFiles(dir: File): List<SemVer> {
103+
val files = dir.listFiles() ?: return emptyList()
104+
val customVersions = files
105+
.map { it.nameWithoutExtension }
106+
.filter { it.endsWith(".custom") }
107+
.map { it.removeSuffix(".custom") }
108+
109+
return customVersions.mapNotNull(SemVer::parseOrNull)
110+
}
111+
}
92112
}

app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadPatchesStep.kt

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.aliucord.manager.patcher.steps.download
22

3+
import android.util.Log
34
import androidx.compose.runtime.Stable
5+
import com.aliucord.manager.BuildConfig
46
import com.aliucord.manager.R
57
import com.aliucord.manager.manager.OverlayManager
68
import com.aliucord.manager.manager.PathManager
79
import com.aliucord.manager.network.utils.SemVer
810
import com.aliucord.manager.patcher.StepRunner
911
import com.aliucord.manager.patcher.steps.base.DownloadStep
12+
import com.aliucord.manager.patcher.steps.base.StepState
1013
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
14+
import com.aliucord.manager.ui.components.dialogs.CustomComponentVersionPicker
1115
import org.koin.core.component.KoinComponent
1216
import org.koin.core.component.inject
1317

@@ -22,14 +26,48 @@ class DownloadPatchesStep : DownloadStep(), KoinComponent {
2226
/**
2327
* This is populated right before the download starts (ref: [execute])
2428
*/
29+
private var isCustomVersion: Boolean = false
2530
lateinit var targetVersion: SemVer
2631
private set
2732

2833
override val localizedName = R.string.patch_step_dl_smali
2934
override val targetUrl get() = URL
30-
override val targetFile get() = paths.cachedSmaliPatches(targetVersion)
35+
override val targetFile get() = paths.cachedSmaliPatches(targetVersion, isCustomVersion)
3136

3237
override suspend fun execute(container: StepRunner) {
38+
var customVersions = mutableListOf<SemVer>()
39+
.apply { addAll(paths.customSmaliPatches()) }
40+
41+
// Prompt to select or manage custom versions instead of downloading
42+
if (customVersions.isNotEmpty()) {
43+
val selectedVersion = overlays.startComposableForResult { callback ->
44+
CustomComponentVersionPicker(
45+
componentTitle = "Smali Patches",
46+
versions = customVersions,
47+
onConfirm = { version -> callback(version) },
48+
onDelete = { version ->
49+
try {
50+
paths.cachedSmaliPatches(version, custom = true).delete()
51+
customVersions.remove(version)
52+
53+
// Dismiss if no custom versions left
54+
if (customVersions.isEmpty())
55+
callback(null)
56+
} catch (t: Throwable) {
57+
Log.e(BuildConfig.TAG, "Failed to delete custom component", t)
58+
}
59+
},
60+
onCancel = { callback(null) },
61+
)
62+
}
63+
if (selectedVersion != null) {
64+
isCustomVersion = true
65+
targetVersion = selectedVersion
66+
state = StepState.Skipped
67+
return
68+
}
69+
}
70+
3371
targetVersion = container.getStep<FetchInfoStep>()
3472
.data.patchesVersion
3573

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.aliucord.manager.ui.components.dialogs
2+
3+
import androidx.compose.foundation.*
4+
import androidx.compose.foundation.interaction.MutableInteractionSource
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.material3.*
7+
import androidx.compose.runtime.*
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.res.painterResource
11+
import androidx.compose.ui.res.stringResource
12+
import androidx.compose.ui.text.font.FontWeight
13+
import androidx.compose.ui.text.style.TextAlign
14+
import androidx.compose.ui.unit.dp
15+
import androidx.compose.ui.window.DialogProperties
16+
import com.aliucord.manager.R
17+
import com.aliucord.manager.network.utils.SemVer
18+
19+
@Composable
20+
fun CustomComponentVersionPicker(
21+
componentTitle: String,
22+
versions: List<SemVer>,
23+
onConfirm: (SemVer) -> Unit,
24+
onDelete: (SemVer) -> Unit,
25+
onCancel: () -> Unit,
26+
) {
27+
var selectedVersion by remember { mutableStateOf<SemVer?>(null) }
28+
29+
AlertDialog(
30+
properties = DialogProperties(
31+
dismissOnBackPress = true,
32+
dismissOnClickOutside = false,
33+
),
34+
onDismissRequest = onCancel,
35+
confirmButton = {
36+
FilledTonalButton(
37+
onClick = { onConfirm(selectedVersion!!) },
38+
enabled = selectedVersion != null,
39+
colors = ButtonDefaults.filledTonalButtonColors(
40+
containerColor = MaterialTheme.colorScheme.primary,
41+
contentColor = MaterialTheme.colorScheme.onPrimary,
42+
),
43+
) {
44+
Text(stringResource(R.string.action_confirm))
45+
}
46+
},
47+
dismissButton = {
48+
TextButton(
49+
onClick = onCancel,
50+
colors = ButtonDefaults.textButtonColors(
51+
contentColor = MaterialTheme.colorScheme.error,
52+
),
53+
) {
54+
Text(stringResource(R.string.action_dismiss))
55+
}
56+
},
57+
title = { Text("Custom Component") },
58+
icon = { Icon(painterResource(R.drawable.ic_download), contentDescription = null) },
59+
text = {
60+
Column {
61+
Text(
62+
text = "A custom local version for a patching component ($componentTitle) has been found. Would you like to use it?",
63+
textAlign = TextAlign.Center,
64+
modifier = Modifier.padding(bottom = 6.dp),
65+
)
66+
67+
Column(
68+
verticalArrangement = Arrangement.spacedBy(6.dp),
69+
modifier = Modifier.verticalScroll(rememberScrollState())
70+
) {
71+
for (version in versions) key(version) {
72+
val clickSource = remember(::MutableInteractionSource)
73+
Row(
74+
horizontalArrangement = Arrangement.spacedBy(12.dp),
75+
verticalAlignment = Alignment.CenterVertically,
76+
modifier = Modifier
77+
.fillMaxWidth()
78+
.clickable(
79+
interactionSource = clickSource,
80+
indication = LocalIndication.current,
81+
onClick = { selectedVersion = version },
82+
),
83+
) {
84+
RadioButton(
85+
selected = selectedVersion == version,
86+
onClick = { selectedVersion = version },
87+
interactionSource = clickSource,
88+
)
89+
90+
Text(
91+
text = "v$version",
92+
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold),
93+
)
94+
95+
Spacer(Modifier.weight(1f))
96+
IconButton(
97+
onClick = { onDelete(version) },
98+
) {
99+
Icon(
100+
painter = painterResource(R.drawable.ic_delete_forever),
101+
tint = MaterialTheme.colorScheme.error,
102+
contentDescription = null,
103+
)
104+
}
105+
}
106+
}
107+
}
108+
}
109+
},
110+
)
111+
}

0 commit comments

Comments
 (0)