|
|
|
|
@@ -1,52 +1,34 @@
|
|
|
|
|
package shirkneko.zako.sukisu.ui.screen
|
|
|
|
|
|
|
|
|
|
import android.app.Activity
|
|
|
|
|
import android.app.AlertDialog
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import android.widget.Toast
|
|
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
import androidx.annotation.StringRes
|
|
|
|
|
import androidx.compose.foundation.LocalIndication
|
|
|
|
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
|
import androidx.compose.foundation.layout.WindowInsets
|
|
|
|
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
import androidx.compose.foundation.layout.only
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.safeDrawing
|
|
|
|
|
import androidx.compose.foundation.layout.*
|
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
import androidx.compose.foundation.selection.toggleable
|
|
|
|
|
import androidx.compose.foundation.verticalScroll
|
|
|
|
|
import androidx.compose.material.icons.Icons
|
|
|
|
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
|
|
|
import androidx.compose.material.icons.filled.FileUpload
|
|
|
|
|
import androidx.compose.material3.Button
|
|
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
|
import androidx.compose.material3.Icon
|
|
|
|
|
import androidx.compose.material3.IconButton
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.material3.RadioButton
|
|
|
|
|
import androidx.compose.material3.Scaffold
|
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
|
import androidx.compose.material3.TopAppBar
|
|
|
|
|
import androidx.compose.material3.TopAppBarDefaults
|
|
|
|
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
|
|
|
import androidx.compose.material3.rememberTopAppBarState
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
import androidx.compose.runtime.getValue
|
|
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
import androidx.compose.runtime.produceState
|
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
|
import androidx.compose.runtime.setValue
|
|
|
|
|
import androidx.compose.material3.*
|
|
|
|
|
import androidx.compose.runtime.*
|
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
import androidx.compose.ui.res.stringResource
|
|
|
|
|
import androidx.compose.ui.semantics.Role
|
|
|
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import androidx.documentfile.provider.DocumentFile
|
|
|
|
|
import com.maxkeppeker.sheets.core.models.base.Header
|
|
|
|
|
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
|
|
|
|
import com.maxkeppeler.sheets.list.ListDialog
|
|
|
|
|
@@ -61,13 +43,12 @@ import shirkneko.zako.sukisu.R
|
|
|
|
|
import shirkneko.zako.sukisu.ui.component.DialogHandle
|
|
|
|
|
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
|
|
|
|
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.LkmSelection
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.getCurrentKmi
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.getSupportedKmis
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.isAbDevice
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.isInitBoot
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.rootAvailable
|
|
|
|
|
import androidx.lifecycle.compose.dropUnlessResumed
|
|
|
|
|
import shirkneko.zako.sukisu.ui.util.*
|
|
|
|
|
import shirkneko.zako.sukisu.utils.AssetsUtil
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
|
import java.io.IOException
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author weishu
|
|
|
|
|
@@ -77,26 +58,62 @@ import androidx.lifecycle.compose.dropUnlessResumed
|
|
|
|
|
@Destination<RootGraph>
|
|
|
|
|
@Composable
|
|
|
|
|
fun InstallScreen(navigator: DestinationsNavigator) {
|
|
|
|
|
var installMethod by remember {
|
|
|
|
|
mutableStateOf<InstallMethod?>(null)
|
|
|
|
|
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
|
|
|
|
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
|
|
|
|
|
var showRebootDialog by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
|
|
val onFlashComplete = {
|
|
|
|
|
showRebootDialog = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lkmSelection by remember {
|
|
|
|
|
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
|
|
|
|
|
if (showRebootDialog) {
|
|
|
|
|
RebootDialog(
|
|
|
|
|
show = true,
|
|
|
|
|
onDismiss = { showRebootDialog = false },
|
|
|
|
|
onConfirm = {
|
|
|
|
|
showRebootDialog = false
|
|
|
|
|
try {
|
|
|
|
|
val process = Runtime.getRuntime().exec("su")
|
|
|
|
|
process.outputStream.bufferedWriter().use { writer ->
|
|
|
|
|
writer.write("svc power reboot\n")
|
|
|
|
|
writer.write("exit\n")
|
|
|
|
|
}
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val onInstall = {
|
|
|
|
|
installMethod?.let { method ->
|
|
|
|
|
val flashIt = FlashIt.FlashBoot(
|
|
|
|
|
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
|
|
|
|
lkm = lkmSelection,
|
|
|
|
|
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
|
|
|
|
)
|
|
|
|
|
navigator.navigate(FlashScreenDestination(flashIt))
|
|
|
|
|
when (method) {
|
|
|
|
|
is InstallMethod.HorizonKernel -> {
|
|
|
|
|
method.uri?.let { uri ->
|
|
|
|
|
val worker = HorizonKernelWorker(context)
|
|
|
|
|
worker.uri = uri
|
|
|
|
|
worker.setOnFlashCompleteListener(onFlashComplete)
|
|
|
|
|
worker.start()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
val flashIt = FlashIt.FlashBoot(
|
|
|
|
|
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
|
|
|
|
lkm = lkmSelection,
|
|
|
|
|
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
|
|
|
|
)
|
|
|
|
|
navigator.navigate(FlashScreenDestination(flashIt))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Unit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() }
|
|
|
|
|
val currentKmi by produceState(initialValue = "") {
|
|
|
|
|
value = getCurrentKmi()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
|
|
|
|
|
kmi?.let {
|
|
|
|
|
@@ -107,21 +124,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|
|
|
|
|
|
|
|
|
val onClickNext = {
|
|
|
|
|
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
|
|
|
|
|
// no lkm file selected and cannot get current kmi
|
|
|
|
|
selectKmiDialog.show()
|
|
|
|
|
} else {
|
|
|
|
|
onInstall()
|
|
|
|
|
}
|
|
|
|
|
Unit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val selectLkmLauncher =
|
|
|
|
|
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
|
|
|
|
|
if (it.resultCode == Activity.RESULT_OK) {
|
|
|
|
|
it.data?.data?.let { uri ->
|
|
|
|
|
lkmSelection = LkmSelection.LkmUri(uri)
|
|
|
|
|
}
|
|
|
|
|
val selectLkmLauncher = rememberLauncherForActivityResult(
|
|
|
|
|
contract = ActivityResultContracts.StartActivityForResult()
|
|
|
|
|
) {
|
|
|
|
|
if (it.resultCode == Activity.RESULT_OK) {
|
|
|
|
|
it.data?.data?.let { uri ->
|
|
|
|
|
lkmSelection = LkmSelection.LkmUri(uri)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val onLkmUpload = {
|
|
|
|
|
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
|
|
|
@@ -134,12 +152,14 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|
|
|
|
Scaffold(
|
|
|
|
|
topBar = {
|
|
|
|
|
TopBar(
|
|
|
|
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
|
|
|
|
onBack = { navigator.popBackStack() },
|
|
|
|
|
onLkmUpload = onLkmUpload,
|
|
|
|
|
scrollBehavior = scrollBehavior
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
|
|
|
contentWindowInsets = WindowInsets.safeDrawing.only(
|
|
|
|
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
|
|
|
|
)
|
|
|
|
|
) { innerPadding ->
|
|
|
|
|
Column(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
@@ -164,11 +184,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
Button(modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
Button(
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
enabled = installMethod != null,
|
|
|
|
|
onClick = {
|
|
|
|
|
onClickNext()
|
|
|
|
|
}) {
|
|
|
|
|
onClick = onClickNext
|
|
|
|
|
) {
|
|
|
|
|
Text(
|
|
|
|
|
stringResource(id = R.string.install_next),
|
|
|
|
|
fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
|
|
|
|
@@ -179,6 +199,166 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun launchHorizonKernelFlash(context: Context, uri: Uri) {
|
|
|
|
|
val worker = HorizonKernelWorker(context)
|
|
|
|
|
worker.uri = uri
|
|
|
|
|
worker.setOnFlashCompleteListener {
|
|
|
|
|
}
|
|
|
|
|
worker.start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun RebootDialog(
|
|
|
|
|
show: Boolean,
|
|
|
|
|
onDismiss: () -> Unit,
|
|
|
|
|
onConfirm: () -> Unit
|
|
|
|
|
) {
|
|
|
|
|
if (show) {
|
|
|
|
|
AlertDialog(
|
|
|
|
|
onDismissRequest = onDismiss,
|
|
|
|
|
title = { Text(stringResource(id = R.string.reboot_complete_title)) },
|
|
|
|
|
text = { Text(stringResource(id = R.string.reboot_complete_msg)) },
|
|
|
|
|
confirmButton = {
|
|
|
|
|
TextButton(onClick = onConfirm) {
|
|
|
|
|
Text(stringResource(id = R.string.yes))
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
dismissButton = {
|
|
|
|
|
TextButton(onClick = onDismiss) {
|
|
|
|
|
Text(stringResource(id = R.string.no))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class HorizonKernelWorker(private val context: Context) : Thread() {
|
|
|
|
|
var uri: Uri? = null
|
|
|
|
|
private lateinit var filePath: String
|
|
|
|
|
private lateinit var binaryPath: String
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private var onFlashComplete: (() -> Unit)? = null
|
|
|
|
|
|
|
|
|
|
fun setOnFlashCompleteListener(listener: () -> Unit) {
|
|
|
|
|
onFlashComplete = listener
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun run() {
|
|
|
|
|
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
|
|
|
|
|
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
cleanup()
|
|
|
|
|
if (!rootAvailable()) {
|
|
|
|
|
showError(context.getString(R.string.root_required))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
copy()
|
|
|
|
|
if (!File(filePath).exists()) {
|
|
|
|
|
showError(context.getString(R.string.copy_failed))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getBinary()
|
|
|
|
|
patch()
|
|
|
|
|
flash()
|
|
|
|
|
|
|
|
|
|
(context as? Activity)?.runOnUiThread {
|
|
|
|
|
onFlashComplete?.invoke()
|
|
|
|
|
}
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
showError(e.message ?: context.getString(R.string.unknown_error))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun cleanup() {
|
|
|
|
|
runCommand(false, "rm -rf ${context.filesDir.absolutePath}/*")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun copy() {
|
|
|
|
|
uri?.let { safeUri ->
|
|
|
|
|
context.contentResolver.openInputStream(safeUri)?.use { input ->
|
|
|
|
|
FileOutputStream(File(filePath)).use { output ->
|
|
|
|
|
input.copyTo(output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getBinary() {
|
|
|
|
|
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
|
|
|
|
|
if (!File(binaryPath).exists()) {
|
|
|
|
|
throw IOException("Failed to extract update-binary")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun patch() {
|
|
|
|
|
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
|
|
|
|
|
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
|
|
|
|
|
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun flash() {
|
|
|
|
|
val process = ProcessBuilder("su")
|
|
|
|
|
.redirectErrorStream(true)
|
|
|
|
|
.start()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
process.outputStream.bufferedWriter().use { writer ->
|
|
|
|
|
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
|
|
|
|
|
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
|
|
|
|
|
writer.flush()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process.inputStream.bufferedReader().use { reader ->
|
|
|
|
|
reader.lineSequence().forEach { line ->
|
|
|
|
|
if (line.startsWith("ui_print")) {
|
|
|
|
|
showLog(line.removePrefix("ui_print"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
process.destroy()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!File("${context.filesDir.absolutePath}/done").exists()) {
|
|
|
|
|
throw IOException("Flash failed")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun runCommand(su: Boolean, cmd: String): Int {
|
|
|
|
|
val process = ProcessBuilder(if (su) "su" else "sh")
|
|
|
|
|
.redirectErrorStream(true)
|
|
|
|
|
.start()
|
|
|
|
|
|
|
|
|
|
return try {
|
|
|
|
|
process.outputStream.bufferedWriter().use { writer ->
|
|
|
|
|
writer.write("$cmd\n")
|
|
|
|
|
writer.write("exit\n")
|
|
|
|
|
writer.flush()
|
|
|
|
|
}
|
|
|
|
|
process.waitFor()
|
|
|
|
|
} finally {
|
|
|
|
|
process.destroy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun showError(message: String) {
|
|
|
|
|
(context as? Activity)?.runOnUiThread {
|
|
|
|
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun showLog(message: String) {
|
|
|
|
|
(context as? Activity)?.runOnUiThread {
|
|
|
|
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sealed class InstallMethod {
|
|
|
|
|
data class SelectFile(
|
|
|
|
|
val uri: Uri? = null,
|
|
|
|
|
@@ -196,6 +376,12 @@ sealed class InstallMethod {
|
|
|
|
|
get() = R.string.install_inactive_slot
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data class HorizonKernel(
|
|
|
|
|
val uri: Uri? = null,
|
|
|
|
|
@StringRes override val label: Int = R.string.horizon_kernel,
|
|
|
|
|
override val summary: String? = null
|
|
|
|
|
) : InstallMethod()
|
|
|
|
|
|
|
|
|
|
abstract val label: Int
|
|
|
|
|
open val summary: String? = null
|
|
|
|
|
}
|
|
|
|
|
@@ -205,52 +391,66 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|
|
|
|
val rootAvailable = rootAvailable()
|
|
|
|
|
val isAbDevice = isAbDevice()
|
|
|
|
|
val selectFileTip = stringResource(
|
|
|
|
|
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
|
|
|
|
|
id = R.string.select_file_tip,
|
|
|
|
|
if (isInitBoot()) "init_boot" else "boot"
|
|
|
|
|
)
|
|
|
|
|
val radioOptions =
|
|
|
|
|
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
|
|
|
|
|
|
|
|
|
val radioOptions = mutableListOf<InstallMethod>(
|
|
|
|
|
InstallMethod.SelectFile(summary = selectFileTip)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (rootAvailable) {
|
|
|
|
|
radioOptions.add(InstallMethod.DirectInstall)
|
|
|
|
|
|
|
|
|
|
if (isAbDevice) {
|
|
|
|
|
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
|
|
|
|
}
|
|
|
|
|
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
|
|
|
|
var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
|
|
|
|
|
|
|
|
|
val selectImageLauncher = rememberLauncherForActivityResult(
|
|
|
|
|
contract = ActivityResultContracts.StartActivityForResult()
|
|
|
|
|
) {
|
|
|
|
|
if (it.resultCode == Activity.RESULT_OK) {
|
|
|
|
|
it.data?.data?.let { uri ->
|
|
|
|
|
val option = InstallMethod.SelectFile(uri, summary = selectFileTip)
|
|
|
|
|
selectedOption = option
|
|
|
|
|
onSelected(option)
|
|
|
|
|
val option = when (currentSelectingMethod) {
|
|
|
|
|
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
|
|
|
|
|
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
|
|
|
|
|
else -> null
|
|
|
|
|
}
|
|
|
|
|
option?.let {
|
|
|
|
|
selectedOption = it
|
|
|
|
|
onSelected(it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val confirmDialog = rememberConfirmDialog(onConfirm = {
|
|
|
|
|
selectedOption = InstallMethod.DirectInstallToInactiveSlot
|
|
|
|
|
onSelected(InstallMethod.DirectInstallToInactiveSlot)
|
|
|
|
|
}, onDismiss = null)
|
|
|
|
|
val confirmDialog = rememberConfirmDialog(
|
|
|
|
|
onConfirm = {
|
|
|
|
|
selectedOption = InstallMethod.DirectInstallToInactiveSlot
|
|
|
|
|
onSelected(InstallMethod.DirectInstallToInactiveSlot)
|
|
|
|
|
},
|
|
|
|
|
onDismiss = null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
|
|
|
|
|
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
|
|
|
|
|
|
|
|
|
|
val onClick = { option: InstallMethod ->
|
|
|
|
|
|
|
|
|
|
currentSelectingMethod = option
|
|
|
|
|
when (option) {
|
|
|
|
|
is InstallMethod.SelectFile -> {
|
|
|
|
|
is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> {
|
|
|
|
|
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
|
|
|
type = "application/octet-stream"
|
|
|
|
|
type = "application/*"
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is InstallMethod.DirectInstall -> {
|
|
|
|
|
selectedOption = option
|
|
|
|
|
onSelected(option)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is InstallMethod.DirectInstallToInactiveSlot -> {
|
|
|
|
|
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
|
|
|
|
}
|
|
|
|
|
@@ -266,9 +466,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|
|
|
|
.fillMaxWidth()
|
|
|
|
|
.toggleable(
|
|
|
|
|
value = option.javaClass == selectedOption?.javaClass,
|
|
|
|
|
onValueChange = {
|
|
|
|
|
onClick(option)
|
|
|
|
|
},
|
|
|
|
|
onValueChange = { onClick(option) },
|
|
|
|
|
role = Role.RadioButton,
|
|
|
|
|
indication = LocalIndication.current,
|
|
|
|
|
interactionSource = interactionSource
|
|
|
|
|
@@ -276,9 +474,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|
|
|
|
) {
|
|
|
|
|
RadioButton(
|
|
|
|
|
selected = option.javaClass == selectedOption?.javaClass,
|
|
|
|
|
onClick = {
|
|
|
|
|
onClick(option)
|
|
|
|
|
},
|
|
|
|
|
onClick = { onClick(option) },
|
|
|
|
|
interactionSource = interactionSource
|
|
|
|
|
)
|
|
|
|
|
Column(
|
|
|
|
|
@@ -311,25 +507,33 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
|
|
|
|
|
val supportedKmi by produceState(initialValue = emptyList<String>()) {
|
|
|
|
|
value = getSupportedKmis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val options = supportedKmi.map { value ->
|
|
|
|
|
ListOption(
|
|
|
|
|
titleText = value
|
|
|
|
|
)
|
|
|
|
|
ListOption(titleText = value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var selection by remember { mutableStateOf<String?>(null) }
|
|
|
|
|
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
|
|
|
|
onSelected(selection)
|
|
|
|
|
}, onCloseRequest = {
|
|
|
|
|
dismiss()
|
|
|
|
|
}), header = Header.Default(
|
|
|
|
|
title = stringResource(R.string.select_kmi),
|
|
|
|
|
), selection = ListSelection.Single(
|
|
|
|
|
showRadioButtons = true,
|
|
|
|
|
options = options,
|
|
|
|
|
) { _, option ->
|
|
|
|
|
selection = option.titleText
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
ListDialog(
|
|
|
|
|
state = rememberUseCaseState(
|
|
|
|
|
visible = true,
|
|
|
|
|
onFinishedRequest = {
|
|
|
|
|
onSelected(selection)
|
|
|
|
|
},
|
|
|
|
|
onCloseRequest = {
|
|
|
|
|
dismiss()
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
header = Header.Default(
|
|
|
|
|
title = stringResource(R.string.select_kmi),
|
|
|
|
|
),
|
|
|
|
|
selection = ListSelection.Single(
|
|
|
|
|
showRadioButtons = true,
|
|
|
|
|
options = options,
|
|
|
|
|
) { _, option ->
|
|
|
|
|
selection = option.titleText
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -341,22 +545,26 @@ private fun TopBar(
|
|
|
|
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
|
|
|
) {
|
|
|
|
|
TopAppBar(
|
|
|
|
|
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
|
|
|
|
|
IconButton(
|
|
|
|
|
onClick = onBack
|
|
|
|
|
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
|
|
|
}, actions = {
|
|
|
|
|
title = { Text(stringResource(R.string.install)) },
|
|
|
|
|
navigationIcon = {
|
|
|
|
|
IconButton(onClick = onBack) {
|
|
|
|
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
actions = {
|
|
|
|
|
IconButton(onClick = onLkmUpload) {
|
|
|
|
|
Icon(Icons.Filled.FileUpload, contentDescription = null)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
|
|
|
windowInsets = WindowInsets.safeDrawing.only(
|
|
|
|
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
|
|
|
|
),
|
|
|
|
|
scrollBehavior = scrollBehavior
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
@Preview
|
|
|
|
|
@Composable
|
|
|
|
|
fun SelectInstallPreview() {
|
|
|
|
|
InstallScreen(EmptyDestinationsNavigator)
|
|
|
|
|
}
|