Port new download feature to apk-ng

Assist-by: Gemini
This commit is contained in:
topjohnwu
2026-04-15 00:35:07 -07:00
committed by John Wu
parent 77ea982963
commit 859ba80e43
3 changed files with 137 additions and 4 deletions
@@ -111,6 +111,13 @@ class FlashViewModel : BaseViewModel() {
MagiskInstaller.Patch(uri, outItems, logItems).exec()
})
}
Const.Value.DOWNLOAD -> {
uri ?: return@launch
_showReboot.value = false
onResult(withContext(Dispatchers.IO) {
MagiskInstaller.Download(uri.toString(), outItems, logItems).exec()
})
}
}
}
}
@@ -1,6 +1,7 @@
package com.topjohnwu.magisk.ui.home
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.widget.Toast
@@ -21,6 +22,8 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
@@ -67,9 +70,12 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config
@@ -111,6 +117,7 @@ fun HomeScreen(viewModel: HomeViewModel, installVm: InstallViewModel) {
var showHideDialog by rememberSaveable { mutableStateOf(false) }
var showRestoreDialog by rememberSaveable { mutableStateOf(false) }
val showInstallSheet = rememberSaveable { mutableStateOf(false) }
val showDownloadDialog = rememberSaveable { mutableStateOf(false) }
var envFixCode by remember { mutableIntStateOf(0) }
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
@@ -138,6 +145,13 @@ fun HomeScreen(viewModel: HomeViewModel, installVm: InstallViewModel) {
}
}
LaunchedEffect(installUiState.showDownloadDialog) {
if (installUiState.showDownloadDialog) {
showDownloadDialog.value = true
installVm.onDownloadDialogConsumed()
}
}
LaunchedEffect(uiState.showUninstall) {
if (uiState.showUninstall) {
showUninstallDialog.value = true
@@ -218,6 +232,13 @@ fun HomeScreen(viewModel: HomeViewModel, installVm: InstallViewModel) {
)
}
if (showDownloadDialog.value) {
DownloadComposableDialog(
showDialog = showDownloadDialog,
onConfirm = { url -> installVm.onDownloadUrlSelected(url) }
)
}
Scaffold(
topBar = {
TopAppBar(
@@ -788,7 +809,14 @@ private fun InstallBottomSheet(
show.value = false
installVm.selectMethod(InstallViewModel.Method.PATCH)
},
// enabled = installUiState.step >= 1 || installVm.skipOptions
)
SettingsArrow(
title = stringResource(CoreR.string.download_patch_file),
onClick = {
show.value = false
installVm.selectMethod(InstallViewModel.Method.DOWNLOAD)
},
)
if (installVm.isRooted) {
@@ -800,7 +828,6 @@ private fun InstallBottomSheet(
installVm.selectMethod(InstallViewModel.Method.DIRECT)
installVm.install()
},
// enabled = installUiState.step >= 1 || installVm.skipOptions
)
}
@@ -812,7 +839,6 @@ private fun InstallBottomSheet(
show.value = false
installVm.selectMethod(InstallViewModel.Method.INACTIVE_SLOT)
},
// enabled = installUiState.step >= 1 || installVm.skipOptions
)
}
}
@@ -891,6 +917,87 @@ private fun CheckboxRow(label: String, checked: Boolean, onCheckedChange: (Boole
}
}
@Composable
private fun DownloadComposableDialog(
showDialog: MutableState<Boolean>,
onConfirm: (Uri) -> Unit
) {
if (!showDialog.value) return
var url by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
fun isValidUrl(url: String): Uri? {
if (url.isEmpty()) return null
val uri = url.toUri()
if (!uri.scheme.equals("https", ignoreCase = true)) return null
if (uri.host.isNullOrEmpty()) return null
if (uri.path.isNullOrEmpty()) return null
return uri
}
AlertDialog(
onDismissRequest = { showDialog.value = false },
title = { Text(stringResource(CoreR.string.download_dialog_title)) },
text = {
Column(modifier = Modifier.padding(top = 8.dp)) {
OutlinedTextField(
value = url,
onValueChange = {
url = it
isError = false
},
modifier = Modifier.fillMaxWidth(),
label = { Text(stringResource(CoreR.string.download_dialog_msg)) },
isError = isError,
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
isValidUrl(url.trim())?.let {
showDialog.value = false
onConfirm(it)
} ?: run {
isError = true
}
}
)
)
if (isError) {
Text(
text = stringResource(CoreR.string.download_dialog_title),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
)
}
}
},
confirmButton = {
TextButton(
onClick = {
isValidUrl(url.trim())?.let {
showDialog.value = false
onConfirm(it)
} ?: run {
isError = true
}
}
) {
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { showDialog.value = false }) {
Text(stringResource(android.R.string.cancel))
}
}
)
}
@Composable
private fun UninstallComposableDialog(
showDialog: MutableState<Boolean>,
@@ -25,7 +25,7 @@ import com.topjohnwu.magisk.core.R as CoreR
class InstallViewModel(svc: NetworkService) : BaseViewModel() {
enum class Method { NONE, PATCH, DIRECT, INACTIVE_SLOT }
enum class Method { NONE, PATCH, DIRECT, INACTIVE_SLOT, DOWNLOAD }
data class UiState(
val step: Int = 0,
@@ -34,6 +34,7 @@ class InstallViewModel(svc: NetworkService) : BaseViewModel() {
val patchUri: Uri? = null,
val requestFilePicker: Boolean = false,
val showSecondSlotWarning: Boolean = false,
val showDownloadDialog: Boolean = false,
)
val isRooted get() = Info.isRooted
@@ -79,6 +80,9 @@ class InstallViewModel(svc: NetworkService) : BaseViewModel() {
Method.INACTIVE_SLOT -> {
_uiState.update { it.copy(showSecondSlotWarning = true) }
}
Method.DOWNLOAD -> {
_uiState.update { it.copy(showDownloadDialog = true) }
}
else -> {}
}
}
@@ -91,6 +95,10 @@ class InstallViewModel(svc: NetworkService) : BaseViewModel() {
_uiState.update { it.copy(showSecondSlotWarning = false) }
}
fun onDownloadDialogConsumed() {
_uiState.update { it.copy(showDownloadDialog = false) }
}
fun onPatchFileSelected(uri: Uri) {
_uiState.update { it.copy(patchUri = uri) }
if (_uiState.value.method == Method.PATCH) {
@@ -98,12 +106,23 @@ class InstallViewModel(svc: NetworkService) : BaseViewModel() {
}
}
fun onDownloadUrlSelected(uri: Uri) {
_uiState.update { it.copy(patchUri = uri) }
if (_uiState.value.method == Method.DOWNLOAD) {
install()
}
}
fun install() {
when (_uiState.value.method) {
Method.PATCH -> navigateTo(Route.Flash(
action = Const.Value.PATCH_FILE,
additionalData = _uiState.value.patchUri!!.toString()
))
Method.DOWNLOAD -> navigateTo(Route.Flash(
action = Const.Value.DOWNLOAD,
additionalData = _uiState.value.patchUri!!.toString()
))
Method.DIRECT -> navigateTo(Route.Flash(
action = Const.Value.FLASH_MAGISK
))