diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 97b83a97..ddc9c642 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
- id("org.jetbrains.kotlinx.atomicfu")
id("com.mikepenz.aboutlibraries.plugin")
}
@@ -111,11 +110,6 @@ android {
}
}
-atomicfu {
- dependenciesVersion = "0.25.0"
- jvmVariant = "FU"
-}
-
tasks.register("setAssetTs", Task::class) {
doLast {
File("$rootDir/app/src/main/assets/cp/_ts").writeText((System.currentTimeMillis() / 1000L).toString())
@@ -123,7 +117,6 @@ tasks.register("setAssetTs", Task::class) {
}
dependencies {
- compileOnly("org.jetbrains.kotlinx:atomicfu:0.25.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 53bc0f85..7f811c99 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,16 +28,11 @@
android:value="Update Android device bootloader / install OS to SD card"/>
-
+ android:theme="@style/SplashTheme">
diff --git a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt
index 7fb62111..254a0873 100644
--- a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt
@@ -1,6 +1,5 @@
package org.andbootmgr.app
-import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -28,7 +27,7 @@ class BackupRestoreWizardPageFactory(private val vm: WizardActivityState) {
fun get(): List {
val c = CreateBackupDataHolder(vm)
return listOf(WizardPage("start",
- NavButton(vm.activity.getString(R.string.cancel)) { it.startActivity(Intent(it, MainActivity::class.java)); it.finish() },
+ NavButton(vm.activity.getString(R.string.cancel)) { it.finish() },
NavButton("") {})
{
ChooseAction(c)
@@ -46,19 +45,18 @@ class BackupRestoreWizardPageFactory(private val vm: WizardActivityState) {
}
}
-private class CreateBackupDataHolder(val vm: WizardActivityState){
+private class CreateBackupDataHolder(val vm: WizardActivityState) {
var pi: Int = -1
var action: Int = 0
var path: Uri? = null
var meta: SDUtils.SDPartitionMeta? = null
-
}
@Composable
private fun ChooseAction(c: CreateBackupDataHolder) {
LaunchedEffect(Unit) {
c.meta = SDUtils.generateMeta(c.vm.deviceInfo)
- c.pi = c.vm.activity.intent.getIntExtra("partitionid", -1)
+ c.pi = c.vm.mvm.wizardCompatPid!!
}
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center,
@@ -163,6 +161,6 @@ private fun Flash(c: CreateBackupDataHolder) {
terminal.add(c.vm.activity.getString(R.string.term_success))
c.vm.btnsOverride = true
c.vm.nextText.value = c.vm.activity.getString(R.string.finish)
- c.vm.onNext.value = { it.startActivity(Intent(it, MainActivity::class.java)); it.finish() }
+ c.vm.onNext.value = { it.finish() }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt
index 4a54804e..a7fcdde5 100644
--- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt
@@ -1,7 +1,6 @@
package org.andbootmgr.app
import android.annotation.SuppressLint
-import android.content.Intent
import android.net.Uri
import android.util.Log
import android.widget.Toast
@@ -20,7 +19,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
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 com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
@@ -30,30 +28,25 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.*
import okio.*
-import org.andbootmgr.app.util.AbmTheme
import org.andbootmgr.app.util.ConfigFile
import org.andbootmgr.app.util.SDUtils
import org.andbootmgr.app.util.SOUtils
import org.andbootmgr.app.util.Terminal
+import org.json.JSONObject
+import org.json.JSONTokener
import java.io.File
import java.io.FileInputStream
+import java.io.FileNotFoundException
import java.io.InputStream
import java.math.BigDecimal
-import java.util.concurrent.TimeUnit
-import org.json.JSONObject
-import org.json.JSONTokener
-import java.io.FileNotFoundException
import java.net.URL
-import java.nio.charset.Charset
+import java.util.concurrent.TimeUnit
class CreatePartWizardPageFactory(private val vm: WizardActivityState) {
fun get(): List {
val c = CreatePartDataHolder(vm)
return listOf(WizardPage("start",
- NavButton(vm.activity.getString(R.string.cancel)) {
- it.startActivity(Intent(it, MainActivity::class.java))
- it.finish()
- },
+ NavButton(vm.activity.getString(R.string.cancel)) { it.finish() },
NavButton("") {}
) {
Start(c)
@@ -68,10 +61,7 @@ class CreatePartWizardPageFactory(private val vm: WizardActivityState) {
) {
Os(c)
}, WizardPage("dload",
- NavButton(vm.activity.getString(R.string.cancel)) {
- it.startActivity(Intent(it, MainActivity::class.java))
- it.finish()
- },
+ NavButton(vm.activity.getString(R.string.cancel)) { it.finish() },
NavButton("") {}
) {
Download(c)
@@ -996,10 +986,7 @@ private fun Flash(c: CreatePartDataHolder) {
terminal.add(vm.activity.getString(R.string.term_success))
vm.btnsOverride = true
vm.nextText.value = vm.activity.getString(R.string.finish)
- vm.onNext.value = {
- it.startActivity(Intent(it, MainActivity::class.java))
- it.finish()
- }
+ vm.onNext.value = { it.finish() }
}
// Fucking complicated code to fairly and flexibly partition space based on preset percentage & bytes values
@@ -1063,7 +1050,6 @@ private fun Flash(c: CreatePartDataHolder) {
vm.btnsOverride = true
vm.nextText.value = c.vm.activity.getString(R.string.finish)
vm.onNext.value = {
- it.startActivity(Intent(it, MainActivity::class.java))
it.finish()
}
terminal.add(vm.activity.getString(R.string.term_success))
@@ -1072,19 +1058,4 @@ private fun Flash(c: CreatePartDataHolder) {
}
}
}
-}
-
-@Composable
-@Preview
-private fun Preview() {
- val vm = WizardActivityState("null")
- val c = CreatePartDataHolder(vm)
- AbmTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Start(c)
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt
index 84b6db98..b970e7e9 100644
--- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt
@@ -1,31 +1,39 @@
package org.andbootmgr.app
-import android.content.Intent
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
-import androidx.compose.foundation.layout.*
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.andbootmgr.app.util.AbmTheme
import org.andbootmgr.app.util.ConfigFile
import org.andbootmgr.app.util.SDUtils
import org.andbootmgr.app.util.Terminal
@@ -38,7 +46,7 @@ import java.net.URL
class DroidBootWizardPageFactory(private val vm: WizardActivityState) {
fun get(): List {
return listOf(WizardPage("start",
- NavButton(vm.activity.getString(R.string.cancel)) { it.startActivity(Intent(it, MainActivity::class.java)); it.finish() },
+ NavButton(vm.activity.getString(R.string.cancel)) { it.finish() },
NavButton(vm.activity.getString(R.string.next)) { it.navigate("input") })
{
Start(vm)
@@ -370,26 +378,12 @@ private fun Flash(vm: WizardActivityState) {
vm.nextText.value = vm.activity.getString(R.string.finish)
vm.onNext.value = {
if (vm.deviceInfo.isBooted(vm.logic)) {
- it.startActivity(Intent(it, MainActivity::class.java))
+ it.finish()
} else {
// TODO prompt user to reboot?
+ it.finish()
}
- it.finish()
}
}
}
-}
-
-@Composable
-@Preview
-private fun Preview() {
- val vm = WizardActivityState("null")
- AbmTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Input(vm)
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt
index 9233be95..8b665941 100644
--- a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt
@@ -3,19 +3,15 @@ package org.andbootmgr.app
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.*
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.andbootmgr.app.util.AbmTheme
import org.andbootmgr.app.util.Terminal
import java.io.File
import java.io.IOException
@@ -103,18 +99,4 @@ private fun Flash(vm: WizardActivityState) {
}
}
}
-}
-
-@Composable
-@Preview
-private fun Preview() {
- val vm = WizardActivityState("null")
- AbmTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- SelectDroidBoot(vm)
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/MainActivity.kt b/app/src/main/java/org/andbootmgr/app/MainActivity.kt
index f3160258..2464975e 100644
--- a/app/src/main/java/org/andbootmgr/app/MainActivity.kt
+++ b/app/src/main/java/org/andbootmgr/app/MainActivity.kt
@@ -1,32 +1,53 @@
package org.andbootmgr.app
import android.content.Intent
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
+import android.view.WindowManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.DrawerValue
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalDrawerSheet
+import androidx.compose.material3.ModalNavigationDrawer
+import androidx.compose.material3.NavigationDrawerItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberDrawerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -37,7 +58,6 @@ import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.Shell.FLAG_MOUNT_MASTER
import com.topjohnwu.superuser.Shell.FLAG_REDIRECT_STDERR
import com.topjohnwu.superuser.io.SuFile
-import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -50,43 +70,31 @@ import org.andbootmgr.app.util.ConfigFile
import org.andbootmgr.app.util.SDUtils
import org.andbootmgr.app.util.StayAliveService
import org.andbootmgr.app.util.Terminal
-import org.andbootmgr.app.util.Toolkit
-import java.io.File
+import java.util.concurrent.atomic.AtomicBoolean
class MainActivityState(val activity: MainActivity?) {
+ var wizardCompat by mutableStateOf(null)
+
fun startFlow(flow: String) {
- val i = Intent(activity!!, WizardActivity::class.java)
- i.putExtra("codename", deviceInfo!!.codename)
- i.putExtra("flow", flow)
- activity.startActivity(i)
- activity.finish()
+ wizardCompat = flow
}
+ var wizardCompatSid: Long? = null
fun startCreateFlow(freeSpace: SDUtils.Partition.FreeSpace) {
- val i = Intent(activity!!, WizardActivity::class.java)
- i.putExtra("codename", deviceInfo!!.codename)
- i.putExtra("flow", "create_part")
- i.putExtra("part_sid", freeSpace.startSector)
- activity.startActivity(i)
- activity.finish()
+ wizardCompat = "create_part"
+ wizardCompatSid = freeSpace.startSector
}
+ var wizardCompatE: String? = null
fun startUpdateFlow(e: String) {
- val i = Intent(activity!!, WizardActivity::class.java)
- i.putExtra("codename", deviceInfo!!.codename)
- i.putExtra("flow", "update")
- i.putExtra("entryFilename", e)
- activity.startActivity(i)
- activity.finish()
+ wizardCompat = "update"
+ wizardCompatE = e
}
+ var wizardCompatPid: Int? = null
fun startBackupAndRestoreFlow(partition: SDUtils.Partition) {
- val i = Intent(activity!!, WizardActivity::class.java)
- i.putExtra("codename", deviceInfo!!.codename)
- i.putExtra("flow", "backup_restore")
- i.putExtra("partitionid", partition.id)
- activity.startActivity(i)
- activity.finish()
+ wizardCompat = "backup_restore"
+ wizardCompatPid = partition.id
}
var noobMode by mutableStateOf(false)
@@ -146,11 +154,51 @@ class MainActivityState(val activity: MainActivity?) {
class MainActivity : ComponentActivity() {
+ private lateinit var newFile: ActivityResultLauncher
+ private var onFileCreated: ((Uri) -> Unit)? = null
+ private lateinit var chooseFile: ActivityResultLauncher
+ private var onFileChosen: ((Uri) -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
- val ready = atomic(false)
- installSplashScreen().setKeepOnScreenCondition { !ready.value }
+ val ready = AtomicBoolean(false)
+ installSplashScreen().setKeepOnScreenCondition { !ready.get() }
super.onCreate(savedInstanceState)
+ chooseFile =
+ registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
+ if (uri == null) {
+ Toast.makeText(
+ this,
+ getString(R.string.file_unavailable),
+ Toast.LENGTH_LONG
+ ).show()
+ onFileChosen = null
+ return@registerForActivityResult
+ }
+ if (onFileChosen != null) {
+ onFileChosen!!(uri)
+ onFileChosen = null
+ } else {
+ throw IllegalStateException("expected onFileChosen to not be null")
+ }
+ }
+ newFile =
+ registerForActivityResult(ActivityResultContracts.CreateDocument("application/octet-stream")) { uri: Uri? ->
+ if (uri == null) {
+ Toast.makeText(
+ this,
+ getString(R.string.file_unavailable),
+ Toast.LENGTH_LONG
+ ).show()
+ onFileCreated = null
+ return@registerForActivityResult
+ }
+ if (onFileCreated != null) {
+ onFileCreated!!(uri)
+ onFileCreated = null
+ } else {
+ throw IllegalStateException("expected onFileCreated to not be null")
+ }
+ }
val vm = MainActivityState(this)
vm.logic = DeviceLogic(this)
CoroutineScope(Dispatchers.IO).launch {
@@ -198,9 +246,15 @@ class MainActivity : ComponentActivity() {
}
withContext(Dispatchers.Main) {
setContent {
- // TODO allow rotating device while viewing logs without loosing logs
+ // TODO allow rotating device while viewing logs without loosing logs (will require rememberSavable)
if (remember { StayAliveService.isRunning }) {
+ DisposableEffect(Unit) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ onDispose { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) }
+ }
Terminal(null, null)
+ } else if (vm.wizardCompat != null) {
+ WizardCompat(vm, vm.wizardCompat!!)
} else {
val navController = rememberNavController()
AppContent(vm, navController) {
@@ -208,10 +262,26 @@ class MainActivity : ComponentActivity() {
}
}
}
- ready.value = true
+ ready.set(true)
}
}
}
+
+ fun chooseFile(mime: String, callback: (Uri) -> Unit) {
+ if (onFileChosen != null) {
+ throw IllegalStateException("expected onFileChosen to be null")
+ }
+ onFileChosen = callback
+ chooseFile.launch(mime)
+ }
+
+ fun createFile(name: String, callback: (Uri) -> Unit) {
+ if (onFileCreated != null) {
+ throw IllegalStateException("expected onFileCreated to be null")
+ }
+ onFileCreated = callback
+ newFile.launch(name)
+ }
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -332,786 +402,6 @@ private fun NavGraph(vm: MainActivityState, navController: NavHostController, it
}
}
-@Composable
-private fun Start(vm: MainActivityState) {
- val installed: Boolean
- val booted: Boolean
- val mounted: Boolean
- val sdpresent: Boolean
- val corrupt: Boolean
- val metaonsd: Boolean
- if (vm.deviceInfo != null) {
- installed = remember { vm.deviceInfo!!.isInstalled(vm.logic!!) }
- booted = remember { vm.deviceInfo!!.isBooted(vm.logic!!) }
- corrupt = remember { vm.deviceInfo!!.isCorrupt(vm.logic!!) }
- mounted = vm.logic!!.mounted
- sdpresent = SuFile.open(vm.deviceInfo!!.bdev).exists()
- metaonsd = vm.deviceInfo!!.metaonsd
- } else {
- installed = false
- booted = false
- corrupt = true
- mounted = false
- sdpresent = false
- metaonsd = false
- }
- val notOkColor = CardDefaults.cardColors(
- containerColor = Color(0xFFFF0F0F)
- )
- val okColor = CardDefaults.cardColors(
- containerColor = Color(0xFF0DDF0F)
- )
- val notOkIcon = R.drawable.ic_baseline_error_24
- val okIcon = R.drawable.ic_baseline_check_circle_24
- val okText = stringResource(R.string.installed)
- val partiallyOkText = stringResource(R.string.installed_deactivated)
- val corruptDeactivatedText = stringResource(R.string.deactivated_corrupt)
- val corruptText = stringResource(R.string.activated_corrupt)
- val notOkText = stringResource(R.string.not_installed)
- val ok = installed and booted and mounted and !corrupt
- val usedText = if (ok) {
- okText
- } else if (installed and booted and (!mounted || corrupt)) {
- corruptText
- } else if (installed and !booted and (!mounted || corrupt)) {
- corruptDeactivatedText
- } else if (installed and !booted) {
- partiallyOkText
- } else {
- notOkText
- }
- Column {
- Card(
- colors = if (ok) okColor else notOkColor, modifier = Modifier
- .fillMaxWidth()
- .padding(5.dp)
- ) {
- Box(Modifier.padding(10.dp)) {
- Column {
- Row(
- Modifier
- .padding(5.dp)
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Icon(
- painterResource(if (ok) okIcon else notOkIcon),
- "",
- Modifier.size(48.dp)
- )
- Text(usedText, fontSize = 32.sp)
- }
- Text(stringResource(id = R.string.activated, stringResource(if (booted) R.string.yes else R.string.no)))
- Text(stringResource(id = R.string.mounted_b, stringResource(if (mounted) R.string.yes else R.string.no)))
- if (metaonsd) {
- Text(stringResource(id = R.string.sd_inserted, stringResource(if (sdpresent) R.string.yes else R.string.no)))
- Text(stringResource(id = R.string.sd_formatted, stringResource(if (installed) R.string.yes else R.string.no)))
- } else {
- Text(stringResource(id = R.string.installed_status, stringResource(if (installed) R.string.yes else R.string.no)))
- }
- if (mounted) {
- Text(stringResource(id = R.string.corrupt_b, stringResource(if (corrupt) R.string.yes else R.string.no)))
- }
- Text(stringResource(R.string.device, if (vm.deviceInfo == null) stringResource(id = R.string.unsupported) else vm.deviceInfo!!.codename))
- }
- }
- }
- if (Shell.isAppGrantedRoot() != true) {
- Text(
- stringResource(R.string.need_root),
- textAlign = TextAlign.Center
- )
- } else if (metaonsd && !sdpresent) {
- Text(stringResource(R.string.need_sd), textAlign = TextAlign.Center)
- } else if (!installed && !mounted) {
- Button(onClick = { vm.startFlow("droidboot") }) {
- Text(stringResource(if (metaonsd) R.string.setup_sd else R.string.install))
- }
- } else if (!booted && mounted) {
- Text(stringResource(R.string.installed_not_booted), textAlign = TextAlign.Center)
- Button(onClick = {
- vm.startFlow("fix_droidboot")
- }) {
- Text(stringResource(R.string.repair_droidboot))
- }
- } else if (!mounted) {
- Text(stringResource(R.string.cannot_mount), textAlign = TextAlign.Center)
- } else if (vm.isOk) {
- PartTool(vm)
- } else {
- Text(stringResource(R.string.invalid), textAlign = TextAlign.Center)
- }
- }
-}
-
-@Composable
-private fun PartTool(vm: MainActivityState) {
- var filterUnifiedView by remember { mutableStateOf(true) }
- var filterPartView by remember { mutableStateOf(false) }
- var filterEntryView by remember { mutableStateOf(false) }
- if (!vm.noobMode)
- Row {
- FilterChip(
- selected = filterUnifiedView,
- onClick = {
- filterUnifiedView = true; filterPartView = false; filterEntryView = false
- },
- label = { Text(stringResource(R.string.unified)) },
- Modifier.padding(start = 5.dp),
- leadingIcon = if (filterUnifiedView) {
- {
- Icon(
- imageVector = Icons.Filled.Done,
- contentDescription = stringResource(id = R.string.enabled_content_desc),
- modifier = Modifier.size(FilterChipDefaults.IconSize)
- )
- }
- } else {
- null
- }
- )
- FilterChip(
- selected = filterPartView,
- onClick = {
- filterPartView = true; filterUnifiedView = false; filterEntryView = false
- },
- label = { Text(stringResource(R.string.partitions)) },
- Modifier.padding(start = 5.dp),
- leadingIcon = if (filterPartView) {
- {
- Icon(
- imageVector = Icons.Filled.Done,
- contentDescription = stringResource(R.string.enabled_content_desc),
- modifier = Modifier.size(FilterChipDefaults.IconSize)
- )
- }
- } else {
- null
- }
- )
- FilterChip(
- selected = filterEntryView,
- onClick = {
- filterPartView = false; filterUnifiedView = false; filterEntryView = true
- },
- label = { Text(stringResource(R.string.entries)) },
- Modifier.padding(start = 5.dp),
- leadingIcon = if (filterEntryView) {
- {
- Icon(
- imageVector = Icons.Filled.Done,
- contentDescription = stringResource(R.string.enabled_content_desc),
- modifier = Modifier.size(FilterChipDefaults.IconSize)
- )
- }
- } else {
- null
- }
- )
- }
-
- var parts by remember { mutableStateOf(SDUtils.generateMeta(vm.deviceInfo!!)) }
- if (parts == null) {
- Text(stringResource(R.string.part_wizard_err))
- return
- }
- val entries = remember {
- val outList = mutableMapOf()
- val list = SuFile.open(vm.logic!!.abmEntries.absolutePath).listFiles()
- for (i in list!!) {
- try {
- outList[ConfigFile.importFromFile(i)] = i
- } catch (e: ActionAbortedCleanlyError) {
- Log.e("ABM", Log.getStackTraceString(e))
- }
- }
- return@remember outList
- }
- var processing by remember { mutableStateOf(false) }
- var rename by remember { mutableStateOf(false) }
- var delete by remember { mutableStateOf(false) }
- var result: String? by remember { mutableStateOf(null) }
- var editPartID: SDUtils.Partition? by remember { mutableStateOf(null) }
- var editEntryID: ConfigFile? by remember { mutableStateOf(null) }
- Column(
- Modifier
- .verticalScroll(rememberScrollState())
- .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
- if (vm.noobMode) {
- Card(modifier = Modifier
- .fillMaxWidth()
- .padding(5.dp)) {
- Row(
- Modifier
- .fillMaxWidth()
- .padding(20.dp)
- ) {
- Icon(painterResource(id = R.drawable.ic_about), stringResource(id = R.string.icon_content_desc))
- Text(stringResource(R.string.click2inspect))
- }
- }
- }
- if (filterUnifiedView) {
- var i = 0
- while (i < parts!!.s.size) {
- var found = false
- if (parts!!.s[i].type != SDUtils.PartitionType.FREE) {
- for (e in entries.keys) {
- if (e.has("xpart") && e["xpart"] != null && e["xpart"]!!.isNotBlank()) {
- for (j in e["xpart"]!!.split(":")) {
- if ("${parts!!.s[i].id}" == j) {
- found = true
- Row(horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { editEntryID = e }) {
- Text(
- if (e.has("title")) {
- stringResource(R.string.entry_title, e["title"]!!)
- } else {
- stringResource(R.string.invalid_entry)
- }
- )
- }
- while (e["xpart"]!!.split(":").contains("${parts!!.s[i].id}")) {
- if (i + 1 == parts!!.s.size) break
- if (!e["xpart"]!!.split(":").contains("${parts!!.s[++i].id}")) {
- i--; break
- }
- }
- break
- }
- }
- }
- if (found) break
- }
- }
- if (!found) {
- val p = parts!!.s[i]
- Row(horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { editPartID = p }) {
- Text(
- if (p.type == SDUtils.PartitionType.FREE)
- stringResource(id = R.string.free_space_item, p.sizeFancy)
- else
- stringResource(id = R.string.part_item, p.id, p.name)
- )
- }
- }
- i++
- }
- }
- if (filterPartView) {
- for (p in parts!!.s) {
- Row(horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { editPartID = p }) {
- Text(
- if (p.type == SDUtils.PartitionType.FREE)
- stringResource(id = R.string.free_space_item, p.sizeFancy)
- else
- stringResource(id = R.string.part_item, p.id, p.name)
- )
- }
- }
- }
- if (filterEntryView) {
- for (e in entries.keys) {
- Row(horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { editEntryID = e }) {
- /* format:
- entry["title"] = str
- entry["linux"] = path(str)
- entry["initrd"] = path(str)
- entry["dtb"] = path(str)
- entry["options"] = str
- entry["xtype"] = str
- entry["xpart"] = array (str.split(":"))
- entry["xupdate"] = uri(str)
- */
- Text(
- if (e.has("title")) {
- stringResource(R.string.entry_title, e["title"]!!)
- } else {
- stringResource(R.string.invalid_entry)
- }
- )
- }
- }
- val e = ConfigFile()
- Row(horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { editEntryID = e }) {
- Text(stringResource(R.string.new_entry))
- }
- }
- if (editPartID != null) {
- val p = editPartID!!
- AlertDialog(
- onDismissRequest = {
- editPartID = null
- },
- title = {
- val name = if (p.type == SDUtils.PartitionType.FREE)
- stringResource(R.string.free_space)
- else if (p.name.isBlank())
- stringResource(R.string.part_title, p.id)
- else
- p.name.trim()
-
- Text(text = "\"${name}\"")
- },
- icon = {
- Icon(painterResource(id = R.drawable.ic_sd), stringResource(id = R.string.icon_content_desc))
- },
- text = {
- Column {
- val fancyType = stringResource(when (p.type) {
- SDUtils.PartitionType.RESERVED -> R.string.reserved
- SDUtils.PartitionType.ADOPTED -> R.string.adoptable_meta
- SDUtils.PartitionType.PORTABLE -> R.string.portable_part
- SDUtils.PartitionType.UNKNOWN -> R.string.unknown
- SDUtils.PartitionType.FREE -> R.string.free_space
- SDUtils.PartitionType.SYSTEM -> R.string.os_system
- SDUtils.PartitionType.DATA -> R.string.os_userdata
- })
- if (p.type != SDUtils.PartitionType.FREE && !filterUnifiedView)
- Text(stringResource(id = R.string.detail_id, p.id, p.major, p.minor))
- Text(stringResource(id = R.string.detail_type, fancyType, if (p.type != SDUtils.PartitionType.FREE && !filterUnifiedView) stringResource(id = R.string.detail_type_code, p.code) else ""))
- Text(stringResource(id = R.string.detail_size, p.sizeFancy, if (!filterUnifiedView) stringResource(id = R.string.detail_size_sectors, p.size) else ""))
- if (!filterUnifiedView)
- Text(stringResource(
- id = R.string.detail_position,
- p.startSector,
- p.endSector
- ))
- if (p.type != SDUtils.PartitionType.FREE) {
- Row {
- Button(onClick = {
- rename = true
- }, Modifier.padding(end = 5.dp)) {
- Text(stringResource(R.string.rename))
- }
- Button(onClick = {
- delete = true
- }) {
- Text(stringResource(R.string.delete))
- }
- }
- if (!filterUnifiedView) {
- Row {
- Button(onClick = {
- processing = true
- vm.logic!!.mount(p).submit {
- processing = false
- result = it.out.joinToString("\n") + it.err.joinToString("\n")
- }
- }, Modifier.padding(end = 5.dp)) {
- Text(stringResource(R.string.mount))
- }
- Button(onClick = {
- processing = true
- vm.logic!!.unmount(p).submit {
- processing = false
- result = it.out.joinToString("\n") + it.err.joinToString("\n")
- }
- }) {
- Text(stringResource(R.string.umount))
- }
- }
- }
- Button(onClick = { vm.startBackupAndRestoreFlow(p) }) {
- Text(stringResource(R.string.backupnrestore))
- }
- } else {
- Button(onClick = { vm.startCreateFlow(p as SDUtils.Partition.FreeSpace) }) {
- Text(stringResource(R.string.create))
- }
- }
- }
- },
- confirmButton = {
- Button(
- onClick = {
- editPartID = null
- }) {
- Text(stringResource(R.string.cancel))
- }
- }
- )
- if (rename) {
- var e by remember { mutableStateOf(false) }
- var t by remember { mutableStateOf(p.name) }
- AlertDialog(
- onDismissRequest = {
- rename = false
- },
- title = {
- Text(stringResource(R.string.rename))
- },
- text = {
- TextField(value = t, onValueChange = {
- t = it
- e = !t.matches(Regex("\\A\\p{ASCII}*\\z"))
- }, isError = e, label = {
- Text(stringResource(R.string.part_name))
- })
- },
- dismissButton = {
- Button(onClick = { rename = false }) {
- Text(stringResource(R.string.cancel))
- }
- },
- confirmButton = {
- Button(onClick = {
- if (!e) {
- processing = true
- rename = false
- vm.logic!!.rename(p, t).submit { r ->
- result = r.out.joinToString("\n") + r.err.joinToString("\n")
- parts = SDUtils.generateMeta(vm.deviceInfo!!)
- editPartID = parts?.s!!.findLast { it.id == p.id }
- processing = false
- }
- }
- }, enabled = !e) {
- Text(stringResource(R.string.rename))
- }
- }
- )
- } else if (delete) {
- AlertDialog(
- onDismissRequest = {
- delete = false
- },
- title = {
- Text(stringResource(R.string.delete))
- },
- text = {
- Text(stringResource(R.string.really_delete_part))
- },
- dismissButton = {
- Button(onClick = { delete = false }) {
- Text(stringResource(R.string.cancel))
- }
- },
- confirmButton = {
- Button(onClick = {
- processing = true
- delete = false
- val wasMounted = vm.logic!!.mounted
- vm.unmountBootset()
- vm.logic!!.delete(p).submit {
- vm.mountBootset()
- if (wasMounted != vm.logic!!.mounted) vm.activity!!.finish()
- else {
- processing = false
- editPartID = null
- parts = SDUtils.generateMeta(vm.deviceInfo!!)
- result = it.out.joinToString("\n") + it.err.joinToString("\n")
- }
- }
- }) {
- Text(stringResource(R.string.delete))
- }
- }
- )
- }
- }
- if (editEntryID != null && !filterUnifiedView) {
- val ctx = LocalContext.current
- val fn = Regex("[\\da-zA-Z]+\\.conf")
- val ascii = Regex("\\A\\p{ASCII}+\\z")
- val xtype = arrayOf("droid", "SFOS", "UT", "")
- val xpart = Regex("^$|^real$|^\\d(:\\d+)*$")
- val e = editEntryID!!
- var f = entries[e]
- var newFileName by remember { mutableStateOf(f?.name ?: "NewEntry.conf") }
- var newFileNameErr by remember { mutableStateOf(!newFileName.matches(fn)) }
- var titleT by remember { mutableStateOf(e["title"] ?: "") }
- var titleE by remember { mutableStateOf(!titleT.matches(ascii)) }
- var linuxT by remember { mutableStateOf(e["linux"] ?: "") }
- var linuxE by remember { mutableStateOf(!linuxT.matches(ascii)) }
- var initrdT by remember { mutableStateOf(e["initrd"] ?: "") }
- var initrdE by remember { mutableStateOf(!initrdT.matches(ascii)) }
- var dtbT by remember { mutableStateOf(e["dtb"] ?: "") }
- var dtbE by remember { mutableStateOf(!dtbT.matches(ascii)) }
- var optionsT by remember { mutableStateOf(e["options"] ?: "") }
- var optionsE by remember { mutableStateOf(!optionsT.matches(ascii)) }
- var xtypeT by remember { mutableStateOf(e["xtype"] ?: "") }
- var xtypeE by remember { mutableStateOf(!xtype.contains(xtypeT)) }
- var xpartT by remember { mutableStateOf(e["xpart"] ?: "") }
- var xpartE by remember { mutableStateOf(!xpartT.matches(xpart)) }
- var xupdateT by remember { mutableStateOf(e["xupdate"] ?: "") }
- val isOk = !(newFileNameErr || titleE || linuxE || initrdE || dtbE || optionsE || xtypeE || xpartE)
- AlertDialog(
- onDismissRequest = {
- editEntryID = null
- },
- title = {
- Text(text = if (e.has("title")) "\"${e["title"]}\"" else if (f != null) stringResource(id = R.string.invalid_entry2) else stringResource(id = R.string.new_entry2))
- },
- icon = {
- Icon(painterResource(id = R.drawable.ic_roms), stringResource(R.string.icon_content_desc))
- },
- text = {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- TextField(value = newFileName, onValueChange = {
- if (f != null) return@TextField
- newFileName = it
- newFileNameErr = !(newFileName.matches(fn))
- }, isError = newFileNameErr, enabled = f == null, label = {
- Text(stringResource(R.string.file_name))
- })
-
- TextField(value = titleT, onValueChange = {
- titleT = it
- titleE = !(titleT.matches(ascii))
- }, isError = titleE, label = {
- Text(stringResource(R.string.title))
- })
-
- TextField(value = linuxT, onValueChange = {
- linuxT = it
- linuxE = !(linuxT.matches(ascii))
- }, isError = linuxE, label = {
- Text(stringResource(R.string.linux))
- })
-
- TextField(value = initrdT, onValueChange = {
- initrdT = it
- initrdE = !(initrdT.matches(ascii))
- }, isError = initrdE, label = {
- Text(stringResource(R.string.initrd))
- })
-
- TextField(value = dtbT, onValueChange = {
- dtbT = it
- dtbE = !(dtbT.matches(ascii))
- }, isError = dtbE, label = {
- Text(stringResource(R.string.dtb))
- })
-
- TextField(value = optionsT, onValueChange = {
- optionsT = it
- optionsE = !(optionsT.matches(ascii))
- }, isError = optionsE, label = {
- Text(stringResource(R.string.options))
- })
-
- TextField(value = xtypeT, onValueChange = {
- xtypeT = it
- xtypeE = !(xtype.contains(xtypeT))
- }, isError = xtypeE, label = {
- Text(stringResource(R.string.rom_type))
- })
-
- TextField(value = xpartT, onValueChange = {
- xpartT = it
- xpartE = !(xpartT.matches(xpart))
- }, isError = xpartE, label = {
- Text(stringResource(R.string.assigned_parts))
- })
-
- TextField(value = xupdateT, onValueChange = {
- xupdateT = it
- }, label = {
- Text(stringResource(R.string.updater_url))
- })
- }
- },
- confirmButton = {
- if (f != null && e["xpart"] != "real") {
- Button(
- onClick = {
- f!!.delete()
- entries.remove(e)
- editEntryID = null
- }) {
- Text(stringResource(R.string.delete))
- }
- }
- Button(
- onClick = {
- if (!isOk) return@Button
- if (f == null) {
- f = SuFile.open(vm.logic!!.abmEntries, newFileName)
- if (f!!.exists()) {
- Toast.makeText(
- ctx,
- vm.activity!!.getString(R.string.file_already_exists),
- Toast.LENGTH_LONG
- ).show()
- f = null
- return@Button
- }
- }
- entries[e] = f!!
- e["title"] = titleT
- e["linux"] = linuxT
- e["initrd"] = initrdT
- e["dtb"] = dtbT
- e["options"] = optionsT
- e["xtype"] = xtypeT
- e["xpart"] = xpartT
- e["xupdate"] = xupdateT
- e.exportToFile(f!!)
- editEntryID = null
- }, enabled = isOk
- ) {
- Text(stringResource(if (f != null) R.string.update else R.string.create))
- }
- Button(
- onClick = {
- editEntryID = null
- }) {
- Text(stringResource(R.string.cancel))
- }
- }
- )
- } else if (editEntryID != null) {
- val e = editEntryID!!
- AlertDialog(
- onDismissRequest = {
- editEntryID = null
- },
- title = {
- Text(text = if (e.has("title")) "\"${e["title"]}\"" else stringResource(R.string.invalid_entry2))
- },
- icon = {
- Icon(painterResource(id = R.drawable.ic_roms), stringResource(id = R.string.icon_content_desc))
- },
- text = {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- Button(
- onClick = {
- if (e.has("xupdate") && !e["xupdate"].isNullOrBlank())
- vm.startUpdateFlow(entries[e]!!.absolutePath)
- }, enabled = e.has("xupdate") && !e["xupdate"].isNullOrBlank()) {
- Text(stringResource(R.string.update))
- }
- Button(
- onClick = {
- delete = true
- }) {
- Text(stringResource(R.string.delete))
- }
- }
- },
- confirmButton = {
- Button(
- onClick = {
- editEntryID = null
- }) {
- Text(stringResource(R.string.cancel))
- }
- }
- )
-
- if (delete) {
- AlertDialog(
- onDismissRequest = {
- delete = false
- },
- title = {
- Text(stringResource(R.string.delete))
- },
- text = {
- Text(stringResource(R.string.really_delete_os))
- },
- dismissButton = {
- Button(onClick = { delete = false }) {
- Text(stringResource(id = R.string.cancel))
- }
- },
- confirmButton = {
- Button(onClick = {
- processing = true
- delete = false
- CoroutineScope(Dispatchers.Default).launch {
- var tresult = ""
- if (e.has("xpart") && !e["xpart"].isNullOrBlank()) {
- val allp = e["xpart"]!!.split(":")
- .map { parts!!.dumpKernelPartition(Integer.valueOf(it)) }
- vm.unmountBootset()
- for (p in allp) { // Do not chain, but regenerate meta and unmount every time. Thanks void
- val r = vm.logic!!.delete(p).exec()
- parts = SDUtils.generateMeta(vm.deviceInfo!!)
- tresult += r.out.joinToString("\n") + r.err.joinToString("\n") + "\n"
- }
- vm.mountBootset()
- }
- val f = entries[e]!!
- val f2 = SuFile(vm.logic!!.abmBootset, f.nameWithoutExtension)
- if (!f2.deleteRecursive())
- tresult += vm.activity!!.getString(R.string.cannot_delete, f2.absolutePath)
- entries.remove(e)
- if (!f.delete())
- tresult += vm.activity!!.getString(R.string.cannot_delete, f.absolutePath)
- editEntryID = null
- processing = false
- parts = SDUtils.generateMeta(vm.deviceInfo!!)
- result = tresult
- }
- }) {
- Text(stringResource(R.string.delete))
- }
- }
- )
- }
- }
- if (processing) {
- AlertDialog(
- onDismissRequest = {},
- title = {
- Text(stringResource(R.string.please_wait))
- },
- text = {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceAround
- ) {
- CircularProgressIndicator(Modifier.padding(end = 20.dp))
- Text(stringResource(R.string.loading))
- }
- },
- confirmButton = {}
- )
- } else if (result != null) {
- AlertDialog(
- onDismissRequest = {
- result = null
- },
- title = {
- Text(stringResource(R.string.done))
- },
- text = {
- result?.let {
- Text(it)
- }
- },
- confirmButton = {
- Button(onClick = { result = null }) {
- Text(stringResource(id = R.string.ok))
- }
- }
- )
- }
- }
-}
-
@Preview(showBackground = true)
@Composable
private fun Preview() {
diff --git a/app/src/main/java/org/andbootmgr/app/Settings.kt b/app/src/main/java/org/andbootmgr/app/Settings.kt
index e53bd297..9ab6e861 100644
--- a/app/src/main/java/org/andbootmgr/app/Settings.kt
+++ b/app/src/main/java/org/andbootmgr/app/Settings.kt
@@ -101,8 +101,8 @@ fun Settings(vm: MainActivityState) {
modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp)) {
Text(stringResource(R.string.noob_mode))
Switch(checked = vm.noobMode, onCheckedChange = {
- vm.noobMode = it
ctx.getSharedPreferences("abm", 0).edit().putBoolean("noob_mode", it).apply()
+ vm.noobMode = it
})
}
}
diff --git a/app/src/main/java/org/andbootmgr/app/Start.kt b/app/src/main/java/org/andbootmgr/app/Start.kt
new file mode 100644
index 00000000..f4801169
--- /dev/null
+++ b/app/src/main/java/org/andbootmgr/app/Start.kt
@@ -0,0 +1,831 @@
+package org.andbootmgr.app
+
+import android.annotation.SuppressLint
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Done
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.FilterChip
+import androidx.compose.material3.FilterChipDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.topjohnwu.superuser.Shell
+import com.topjohnwu.superuser.io.SuFile
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.andbootmgr.app.util.ConfigFile
+import org.andbootmgr.app.util.SDUtils
+import java.io.File
+
+private val configFileNameRegex = Regex("[\\da-zA-Z]+\\.conf")
+private val asciiRegex = Regex("\\A\\p{ASCII}+\\z")
+private val xtypeValidValues = arrayOf("droid", "SFOS", "UT", "")
+private val xpartValidValues = Regex("^$|^real$|^\\d(:\\d+)*$")
+
+@Composable
+fun Start(vm: MainActivityState) {
+ val installed: Boolean
+ val booted: Boolean
+ val mounted: Boolean
+ val sdPresent: Boolean
+ val corrupt: Boolean
+ val metaOnSd: Boolean
+ if (vm.deviceInfo != null) {
+ installed = remember { vm.deviceInfo!!.isInstalled(vm.logic!!) }
+ booted = remember { vm.deviceInfo!!.isBooted(vm.logic!!) }
+ corrupt = remember { vm.deviceInfo!!.isCorrupt(vm.logic!!) }
+ mounted = vm.logic!!.mounted
+ metaOnSd = vm.deviceInfo!!.metaonsd
+ sdPresent = if (metaOnSd) remember { SuFile.open(vm.deviceInfo!!.bdev).exists() } else false
+ } else {
+ installed = false
+ booted = false
+ corrupt = true
+ mounted = false
+ sdPresent = false
+ metaOnSd = false
+ }
+ val notOkColor = CardDefaults.cardColors(
+ containerColor = Color(0xFFFF0F0F)
+ )
+ val okColor = CardDefaults.cardColors(
+ containerColor = Color(0xFF0DDF0F)
+ )
+ val notOkIcon = R.drawable.ic_baseline_error_24
+ val okIcon = R.drawable.ic_baseline_check_circle_24
+ val okText = stringResource(R.string.installed)
+ val partiallyOkText = stringResource(R.string.installed_deactivated)
+ val corruptDeactivatedText = stringResource(R.string.deactivated_corrupt)
+ val corruptText = stringResource(R.string.activated_corrupt)
+ val notOkText = stringResource(R.string.not_installed)
+ val ok = installed and booted and mounted and !corrupt
+ val usedText = if (ok) {
+ okText
+ } else if (installed and booted and (!mounted || corrupt)) {
+ corruptText
+ } else if (installed and !booted and (!mounted || corrupt)) {
+ corruptDeactivatedText
+ } else if (installed and !booted) {
+ partiallyOkText
+ } else {
+ notOkText
+ }
+ Column {
+ Card(
+ colors = if (ok) okColor else notOkColor, modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp)
+ ) {
+ Box(Modifier.padding(10.dp)) {
+ Column {
+ Row(
+ Modifier
+ .padding(5.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painterResource(if (ok) okIcon else notOkIcon),
+ "",
+ Modifier.size(48.dp)
+ )
+ Text(usedText, fontSize = 32.sp)
+ }
+ Text(stringResource(id = R.string.activated, stringResource(if (booted) R.string.yes else R.string.no)))
+ Text(stringResource(id = R.string.mounted_b, stringResource(if (mounted) R.string.yes else R.string.no)))
+ if (metaOnSd) {
+ Text(stringResource(id = R.string.sd_inserted, stringResource(if (sdPresent) R.string.yes else R.string.no)))
+ Text(stringResource(id = R.string.sd_formatted, stringResource(if (installed) R.string.yes else R.string.no)))
+ } else {
+ Text(stringResource(id = R.string.installed_status, stringResource(if (installed) R.string.yes else R.string.no)))
+ }
+ if (mounted) {
+ Text(stringResource(id = R.string.corrupt_b, stringResource(if (corrupt) R.string.yes else R.string.no)))
+ }
+ Text(stringResource(R.string.device, if (vm.deviceInfo == null) stringResource(id = R.string.unsupported) else vm.deviceInfo!!.codename))
+ }
+ }
+ }
+ if (Shell.isAppGrantedRoot() != true) {
+ Text(
+ stringResource(R.string.need_root),
+ textAlign = TextAlign.Center
+ )
+ } else if (metaOnSd && !sdPresent) {
+ Text(stringResource(R.string.need_sd), textAlign = TextAlign.Center)
+ } else if (!installed && !mounted) {
+ Button(onClick = { vm.startFlow("droidboot") }) {
+ Text(stringResource(if (metaOnSd) R.string.setup_sd else R.string.install))
+ }
+ } else if (!booted && mounted) {
+ Text(stringResource(R.string.installed_not_booted), textAlign = TextAlign.Center)
+ Button(onClick = {
+ vm.startFlow("fix_droidboot")
+ }) {
+ Text(stringResource(R.string.repair_droidboot))
+ }
+ } else if (!mounted) {
+ Text(stringResource(R.string.cannot_mount), textAlign = TextAlign.Center)
+ } else if (vm.isOk) {
+ PartTool(vm)
+ } else {
+ Text(stringResource(R.string.invalid), textAlign = TextAlign.Center)
+ }
+ }
+}
+
+@Composable
+private fun PartTool(vm: MainActivityState) {
+ var filterUnifiedView by remember { mutableStateOf(true) }
+ var filterPartView by remember { mutableStateOf(false) }
+ var filterEntryView by remember { mutableStateOf(false) }
+ if (!vm.noobMode)
+ Row {
+ FilterChip(
+ selected = filterUnifiedView,
+ onClick = {
+ filterUnifiedView = true; filterPartView = false; filterEntryView = false
+ },
+ label = { Text(stringResource(R.string.unified)) },
+ Modifier.padding(start = 5.dp),
+ leadingIcon = if (filterUnifiedView) {
+ {
+ Icon(
+ imageVector = Icons.Filled.Done,
+ contentDescription = stringResource(id = R.string.enabled_content_desc),
+ modifier = Modifier.size(FilterChipDefaults.IconSize)
+ )
+ }
+ } else {
+ null
+ }
+ )
+ FilterChip(
+ selected = filterPartView,
+ onClick = {
+ filterPartView = true; filterUnifiedView = false; filterEntryView = false
+ },
+ label = { Text(stringResource(R.string.partitions)) },
+ Modifier.padding(start = 5.dp),
+ leadingIcon = if (filterPartView) {
+ {
+ Icon(
+ imageVector = Icons.Filled.Done,
+ contentDescription = stringResource(R.string.enabled_content_desc),
+ modifier = Modifier.size(FilterChipDefaults.IconSize)
+ )
+ }
+ } else {
+ null
+ }
+ )
+ FilterChip(
+ selected = filterEntryView,
+ onClick = {
+ filterPartView = false; filterUnifiedView = false; filterEntryView = true
+ },
+ label = { Text(stringResource(R.string.entries)) },
+ Modifier.padding(start = 5.dp),
+ leadingIcon = if (filterEntryView) {
+ {
+ Icon(
+ imageVector = Icons.Filled.Done,
+ contentDescription = stringResource(R.string.enabled_content_desc),
+ modifier = Modifier.size(FilterChipDefaults.IconSize)
+ )
+ }
+ } else {
+ null
+ }
+ )
+ }
+
+ var parts by remember { mutableStateOf(SDUtils.generateMeta(vm.deviceInfo!!)) }
+ if (parts == null) {
+ Text(stringResource(R.string.part_wizard_err))
+ return
+ }
+ @SuppressLint("MutableCollectionMutableState") // lol
+ var entries by remember { mutableStateOf?>(null) }
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ val outList = mutableStateMapOf()
+ val list = SuFile.open(vm.logic!!.abmEntries.absolutePath).listFiles()
+ for (i in list!!) {
+ try {
+ outList[ConfigFile.importFromFile(i)] = i
+ } catch (e: ActionAbortedCleanlyError) {
+ Log.e("ABM", Log.getStackTraceString(e))
+ }
+ }
+ entries = outList
+ }
+ }
+ var processing by remember { mutableStateOf(false) }
+ var rename by remember { mutableStateOf(false) }
+ var delete by remember { mutableStateOf(false) }
+ var result by remember { mutableStateOf(null) }
+ var editPartID by remember { mutableStateOf(null) }
+ var editEntryID by remember { mutableStateOf(null) }
+ Column(
+ Modifier
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
+ if (vm.noobMode) {
+ Card(modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp)) {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .padding(20.dp)
+ ) {
+ Icon(painterResource(id = R.drawable.ic_about), stringResource(id = R.string.icon_content_desc))
+ Text(stringResource(R.string.click2inspect))
+ }
+ }
+ }
+ if (filterUnifiedView && entries != null) {
+ var i = 0
+ while (i < parts!!.s.size) {
+ var found = false
+ if (parts!!.s[i].type != SDUtils.PartitionType.FREE) {
+ for (e in entries!!.keys) {
+ if (e.has("xpart") && e["xpart"] != null && e["xpart"]!!.isNotBlank()) {
+ for (j in e["xpart"]!!.split(":")) {
+ if ("${parts!!.s[i].id}" == j) {
+ found = true
+ Row(horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { editEntryID = e }) {
+ Text(
+ if (e.has("title")) {
+ stringResource(R.string.entry_title, e["title"]!!)
+ } else {
+ stringResource(R.string.invalid_entry)
+ }
+ )
+ }
+ while (e["xpart"]!!.split(":").contains("${parts!!.s[i].id}")) {
+ if (i + 1 == parts!!.s.size) break
+ if (!e["xpart"]!!.split(":").contains("${parts!!.s[++i].id}")) {
+ i--; break
+ }
+ }
+ break
+ }
+ }
+ }
+ if (found) break
+ }
+ }
+ if (!found) {
+ val p = parts!!.s[i]
+ Row(horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { editPartID = p }) {
+ Text(
+ if (p.type == SDUtils.PartitionType.FREE)
+ stringResource(id = R.string.free_space_item, p.sizeFancy)
+ else
+ stringResource(id = R.string.part_item, p.id, p.name)
+ )
+ }
+ }
+ i++
+ }
+ } else if (filterPartView) {
+ for (p in parts!!.s) {
+ Row(horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { editPartID = p }) {
+ Text(
+ if (p.type == SDUtils.PartitionType.FREE)
+ stringResource(id = R.string.free_space_item, p.sizeFancy)
+ else
+ stringResource(id = R.string.part_item, p.id, p.name)
+ )
+ }
+ }
+ } else if (filterEntryView && entries != null) {
+ for (e in entries!!.keys) {
+ Row(horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { editEntryID = e }) {
+ /* format:
+ entry["title"] = str
+ entry["linux"] = path(str)
+ entry["initrd"] = path(str)
+ entry["dtb"] = path(str)
+ entry["options"] = str
+ entry["xtype"] = str
+ entry["xpart"] = array (str.split(":"))
+ entry["xupdate"] = uri(str)
+ */
+ Text(
+ if (e.has("title")) {
+ stringResource(R.string.entry_title, e["title"]!!)
+ } else {
+ stringResource(R.string.invalid_entry)
+ }
+ )
+ }
+ }
+ val e = ConfigFile()
+ Row(horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { editEntryID = e }) {
+ Text(stringResource(R.string.new_entry))
+ }
+ }
+ if (editPartID != null) {
+ val p = editPartID!!
+ AlertDialog(
+ onDismissRequest = {
+ editPartID = null
+ },
+ title = {
+ val name = if (p.type == SDUtils.PartitionType.FREE)
+ stringResource(R.string.free_space)
+ else if (p.name.isBlank())
+ stringResource(R.string.part_title, p.id)
+ else
+ p.name.trim()
+
+ Text(text = "\"${name}\"")
+ },
+ icon = {
+ Icon(painterResource(id = R.drawable.ic_sd), stringResource(id = R.string.icon_content_desc))
+ },
+ text = {
+ Column {
+ val fancyType = stringResource(when (p.type) {
+ SDUtils.PartitionType.RESERVED -> R.string.reserved
+ SDUtils.PartitionType.ADOPTED -> R.string.adoptable_meta
+ SDUtils.PartitionType.PORTABLE -> R.string.portable_part
+ SDUtils.PartitionType.UNKNOWN -> R.string.unknown
+ SDUtils.PartitionType.FREE -> R.string.free_space
+ SDUtils.PartitionType.SYSTEM -> R.string.os_system
+ SDUtils.PartitionType.DATA -> R.string.os_userdata
+ })
+ if (p.type != SDUtils.PartitionType.FREE && !filterUnifiedView)
+ Text(stringResource(id = R.string.detail_id, p.id, p.major, p.minor))
+ Text(stringResource(id = R.string.detail_type, fancyType, if (p.type != SDUtils.PartitionType.FREE && !filterUnifiedView) stringResource(id = R.string.detail_type_code, p.code) else ""))
+ Text(stringResource(id = R.string.detail_size, p.sizeFancy, if (!filterUnifiedView) stringResource(id = R.string.detail_size_sectors, p.size) else ""))
+ if (!filterUnifiedView)
+ Text(
+ stringResource(
+ id = R.string.detail_position,
+ p.startSector,
+ p.endSector
+ )
+ )
+ if (p.type != SDUtils.PartitionType.FREE) {
+ Row {
+ Button(onClick = {
+ rename = true
+ }, Modifier.padding(end = 5.dp)) {
+ Text(stringResource(R.string.rename))
+ }
+ Button(onClick = {
+ delete = true
+ }) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ if (!filterUnifiedView) {
+ Row {
+ Button(onClick = {
+ processing = true
+ vm.logic!!.mount(p).submit {
+ processing = false
+ result = it.out.joinToString("\n") + it.err.joinToString("\n")
+ }
+ }, Modifier.padding(end = 5.dp)) {
+ Text(stringResource(R.string.mount))
+ }
+ Button(onClick = {
+ processing = true
+ vm.logic!!.unmount(p).submit {
+ processing = false
+ result = it.out.joinToString("\n") + it.err.joinToString("\n")
+ }
+ }) {
+ Text(stringResource(R.string.umount))
+ }
+ }
+ }
+ Button(onClick = { vm.startBackupAndRestoreFlow(p) }) {
+ Text(stringResource(R.string.backupnrestore))
+ }
+ } else {
+ Button(onClick = { vm.startCreateFlow(p as SDUtils.Partition.FreeSpace) }) {
+ Text(stringResource(R.string.create))
+ }
+ }
+ }
+ },
+ confirmButton = {
+ Button(
+ onClick = {
+ editPartID = null
+ }) {
+ Text(stringResource(R.string.cancel))
+ }
+ }
+ )
+ if (rename) {
+ var e by remember { mutableStateOf(false) }
+ var t by remember { mutableStateOf(p.name) }
+ AlertDialog(
+ onDismissRequest = {
+ rename = false
+ },
+ title = {
+ Text(stringResource(R.string.rename))
+ },
+ text = {
+ TextField(value = t, onValueChange = {
+ t = it
+ e = !t.matches(Regex("\\A\\p{ASCII}*\\z"))
+ }, isError = e, label = {
+ Text(stringResource(R.string.part_name))
+ })
+ },
+ dismissButton = {
+ Button(onClick = { rename = false }) {
+ Text(stringResource(R.string.cancel))
+ }
+ },
+ confirmButton = {
+ Button(onClick = {
+ if (!e) {
+ processing = true
+ rename = false
+ vm.logic!!.rename(p, t).submit { r ->
+ result = r.out.joinToString("\n") + r.err.joinToString("\n")
+ parts = SDUtils.generateMeta(vm.deviceInfo!!)
+ editPartID = parts?.s!!.findLast { it.id == p.id }
+ processing = false
+ }
+ }
+ }, enabled = !e) {
+ Text(stringResource(R.string.rename))
+ }
+ }
+ )
+ } else if (delete) {
+ AlertDialog(
+ onDismissRequest = {
+ delete = false
+ },
+ title = {
+ Text(stringResource(R.string.delete))
+ },
+ text = {
+ Text(stringResource(R.string.really_delete_part))
+ },
+ dismissButton = {
+ Button(onClick = { delete = false }) {
+ Text(stringResource(R.string.cancel))
+ }
+ },
+ confirmButton = {
+ Button(onClick = {
+ processing = true
+ delete = false
+ val wasMounted = vm.logic!!.mounted
+ vm.unmountBootset()
+ vm.logic!!.delete(p).submit {
+ vm.mountBootset()
+ if (wasMounted != vm.logic!!.mounted) vm.activity!!.finish()
+ else {
+ processing = false
+ editPartID = null
+ parts = SDUtils.generateMeta(vm.deviceInfo!!)
+ result = it.out.joinToString("\n") + it.err.joinToString("\n")
+ }
+ }
+ }) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ )
+ }
+ }
+ if (editEntryID != null && !filterUnifiedView) {
+ val ctx = LocalContext.current
+ val e = editEntryID!!
+ var f = entries!![e]
+ var newFileName by remember { mutableStateOf(f?.name ?: "NewEntry.conf") }
+ val newFileNameErr by remember(newFileName) { derivedStateOf { !newFileName.matches(configFileNameRegex) } }
+ var titleT by remember { mutableStateOf(e["title"] ?: "") }
+ val titleE by remember { derivedStateOf { !titleT.matches(asciiRegex) } }
+ var linuxT by remember { mutableStateOf(e["linux"] ?: "") }
+ val linuxE by remember { derivedStateOf { !linuxT.matches(asciiRegex) } }
+ var initrdT by remember { mutableStateOf(e["initrd"] ?: "") }
+ val initrdE by remember { derivedStateOf { !initrdT.matches(asciiRegex) } }
+ var dtbT by remember { mutableStateOf(e["dtb"] ?: "") }
+ val dtbE by remember { derivedStateOf { dtbT.matches(asciiRegex) } }
+ var optionsT by remember { mutableStateOf(e["options"] ?: "") }
+ val optionsE by remember { derivedStateOf { !optionsT.matches(asciiRegex) } }
+ var xtypeT by remember { mutableStateOf(e["xtype"] ?: "") }
+ val xtypeE by remember { derivedStateOf { !xtypeValidValues.contains(xtypeT) } }
+ var xpartT by remember { mutableStateOf(e["xpart"] ?: "") }
+ val xpartE by remember { derivedStateOf { !xpartT.matches(xpartValidValues) } }
+ var xupdateT by remember { mutableStateOf(e["xupdate"] ?: "") }
+ val isOk = !(newFileNameErr || titleE || linuxE || initrdE || dtbE || optionsE || xtypeE || xpartE)
+ AlertDialog(
+ onDismissRequest = {
+ editEntryID = null
+ },
+ title = {
+ Text(text = if (e.has("title")) "\"${e["title"]}\"" else if (f != null) stringResource(id = R.string.invalid_entry2) else stringResource(id = R.string.new_entry2))
+ },
+ icon = {
+ Icon(painterResource(id = R.drawable.ic_roms), stringResource(R.string.icon_content_desc))
+ },
+ text = {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ TextField(value = newFileName, onValueChange = {
+ if (f != null) return@TextField
+ newFileName = it
+ }, isError = newFileNameErr, enabled = f == null, label = {
+ Text(stringResource(R.string.file_name))
+ })
+
+ TextField(value = titleT, onValueChange = {
+ titleT = it
+ }, isError = titleE, label = {
+ Text(stringResource(R.string.title))
+ })
+
+ TextField(value = linuxT, onValueChange = {
+ linuxT = it
+ }, isError = linuxE, label = {
+ Text(stringResource(R.string.linux))
+ })
+
+ TextField(value = initrdT, onValueChange = {
+ initrdT = it
+ }, isError = initrdE, label = {
+ Text(stringResource(R.string.initrd))
+ })
+
+ TextField(value = dtbT, onValueChange = {
+ dtbT = it
+ }, isError = dtbE, label = {
+ Text(stringResource(R.string.dtb))
+ })
+
+ TextField(value = optionsT, onValueChange = {
+ optionsT = it
+ }, isError = optionsE, label = {
+ Text(stringResource(R.string.options))
+ })
+
+ TextField(value = xtypeT, onValueChange = {
+ xtypeT = it
+ }, isError = xtypeE, label = {
+ Text(stringResource(R.string.rom_type))
+ })
+
+ TextField(value = xpartT, onValueChange = {
+ xpartT = it
+ }, isError = xpartE, label = {
+ Text(stringResource(R.string.assigned_parts))
+ })
+
+ TextField(value = xupdateT, onValueChange = {
+ xupdateT = it
+ }, label = {
+ Text(stringResource(R.string.updater_url))
+ })
+ }
+ },
+ confirmButton = {
+ if (f != null && e["xpart"] != "real") {
+ Button(
+ onClick = {
+ f!!.delete()
+ entries!!.remove(e)
+ editEntryID = null
+ }) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ Button(
+ onClick = {
+ if (!isOk) return@Button
+ if (f == null) {
+ f = SuFile.open(vm.logic!!.abmEntries, newFileName)
+ if (f!!.exists()) {
+ Toast.makeText(
+ ctx,
+ vm.activity!!.getString(R.string.file_already_exists),
+ Toast.LENGTH_LONG
+ ).show()
+ f = null
+ return@Button
+ }
+ }
+ entries!![e] = f!!
+ e["title"] = titleT
+ e["linux"] = linuxT
+ e["initrd"] = initrdT
+ e["dtb"] = dtbT
+ e["options"] = optionsT
+ e["xtype"] = xtypeT
+ e["xpart"] = xpartT
+ e["xupdate"] = xupdateT
+ e.exportToFile(f!!)
+ editEntryID = null
+ }, enabled = isOk
+ ) {
+ Text(stringResource(if (f != null) R.string.update else R.string.create))
+ }
+ Button(
+ onClick = {
+ editEntryID = null
+ }) {
+ Text(stringResource(R.string.cancel))
+ }
+ }
+ )
+ } else if (editEntryID != null) {
+ val e = editEntryID!!
+ AlertDialog(
+ onDismissRequest = {
+ editEntryID = null
+ },
+ title = {
+ Text(text = if (e.has("title")) "\"${e["title"]}\"" else stringResource(R.string.invalid_entry2))
+ },
+ icon = {
+ Icon(painterResource(id = R.drawable.ic_roms), stringResource(id = R.string.icon_content_desc))
+ },
+ text = {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Button(
+ onClick = {
+ if (e.has("xupdate") && !e["xupdate"].isNullOrBlank())
+ vm.startUpdateFlow(entries!![e]!!.absolutePath)
+ }, enabled = e.has("xupdate") && !e["xupdate"].isNullOrBlank()) {
+ Text(stringResource(R.string.update))
+ }
+ Button(
+ onClick = {
+ delete = true
+ }) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ },
+ confirmButton = {
+ Button(
+ onClick = {
+ editEntryID = null
+ }) {
+ Text(stringResource(R.string.cancel))
+ }
+ }
+ )
+
+ if (delete) {
+ AlertDialog(
+ onDismissRequest = {
+ delete = false
+ },
+ title = {
+ Text(stringResource(R.string.delete))
+ },
+ text = {
+ Text(stringResource(R.string.really_delete_os))
+ },
+ dismissButton = {
+ Button(onClick = { delete = false }) {
+ Text(stringResource(id = R.string.cancel))
+ }
+ },
+ confirmButton = {
+ Button(onClick = {
+ processing = true
+ delete = false
+ CoroutineScope(Dispatchers.Default).launch {
+ var tresult = ""
+ if (e.has("xpart") && !e["xpart"].isNullOrBlank()) {
+ val allp = e["xpart"]!!.split(":")
+ .map { parts!!.dumpKernelPartition(Integer.valueOf(it)) }
+ vm.unmountBootset()
+ for (p in allp) { // Do not chain, but regenerate meta and unmount every time. Thanks vold
+ val r = vm.logic!!.delete(p).exec()
+ parts = SDUtils.generateMeta(vm.deviceInfo!!)
+ tresult += r.out.joinToString("\n") + r.err.joinToString("\n") + "\n"
+ }
+ vm.mountBootset()
+ }
+ val f = entries!![e]!!
+ val f2 = SuFile(vm.logic!!.abmBootset, f.nameWithoutExtension)
+ if (!f2.deleteRecursive())
+ tresult += vm.activity!!.getString(R.string.cannot_delete, f2.absolutePath)
+ entries!!.remove(e)
+ if (!f.delete())
+ tresult += vm.activity!!.getString(R.string.cannot_delete, f.absolutePath)
+ editEntryID = null
+ processing = false
+ parts = SDUtils.generateMeta(vm.deviceInfo!!)
+ result = tresult
+ }
+ }) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ )
+ }
+ }
+ if (processing) {
+ AlertDialog(
+ onDismissRequest = {},
+ title = {
+ Text(stringResource(R.string.please_wait))
+ },
+ text = {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceAround
+ ) {
+ CircularProgressIndicator(Modifier.padding(end = 20.dp))
+ Text(stringResource(R.string.loading))
+ }
+ },
+ confirmButton = {}
+ )
+ } else if (result != null) {
+ AlertDialog(
+ onDismissRequest = {
+ result = null
+ },
+ title = {
+ Text(stringResource(R.string.done))
+ },
+ text = {
+ result?.let {
+ Text(it)
+ }
+ },
+ confirmButton = {
+ Button(onClick = { result = null }) {
+ Text(stringResource(id = R.string.ok))
+ }
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt
index 041f8688..2c800495 100644
--- a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt
@@ -3,17 +3,15 @@ package org.andbootmgr.app
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.*
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import org.andbootmgr.app.util.AbmTheme
import org.andbootmgr.app.util.Terminal
import java.io.File
import java.io.IOException
@@ -101,18 +99,4 @@ private fun Flash(vm: WizardActivityState) {
}
}
}
-}
-
-@Composable
-@Preview
-private fun Preview() {
- val vm = WizardActivityState("null")
- AbmTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- SelectDroidBoot(vm)
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt
index 0eaa9412..dc3a8e3d 100644
--- a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt
+++ b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt
@@ -1,6 +1,5 @@
package org.andbootmgr.app
-import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
@@ -8,7 +7,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Button
import androidx.compose.material3.Text
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
@@ -36,17 +40,13 @@ class UpdateFlowWizardPageFactory(private val vm: WizardActivityState) {
val noobMode = c.vm.activity.getSharedPreferences("abm", 0).getBoolean("noob_mode", BuildConfig.DEFAULT_NOOB_MODE)
return listOf(WizardPage("start",
NavButton(vm.activity.getString(R.string.cancel)) {
- it.startActivity(Intent(it, MainActivity::class.java))
it.finish()
},
if (noobMode) NavButton("") {} else NavButton(vm.activity.getString(R.string.local_update)) { vm.navigate("local") }
) {
Start(c)
}, WizardPage("local",
- NavButton(vm.activity.getString(R.string.cancel)) {
- it.startActivity(Intent(it, MainActivity::class.java))
- it.finish()
- },
+ NavButton(vm.activity.getString(R.string.cancel)) { it.finish() },
NavButton(vm.activity.getString(R.string.online_update)) { vm.navigate("start") }
) {
Local(c)
@@ -372,7 +372,6 @@ private fun Flash(u: UpdateFlowDataHolder) {
}
u.vm.nextText.value = u.vm.activity.getString(R.string.finish)
u.vm.onNext.value = {
- it.startActivity(Intent(it, MainActivity::class.java))
it.finish()
}
}
diff --git a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt
index b455833f..23a8d6c8 100644
--- a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt
+++ b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt
@@ -1,26 +1,19 @@
package org.andbootmgr.app
-import android.annotation.SuppressLint
import android.net.Uri
-import android.os.Bundle
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.layout.Arrangement
+import android.view.WindowManager
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.core.net.toFile
import androidx.navigation.NavHostController
@@ -28,10 +21,6 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.topjohnwu.superuser.io.SuFileOutputStream
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import org.andbootmgr.app.util.AbmTheme
import java.io.File
import java.io.FileInputStream
import java.io.IOException
@@ -57,137 +46,38 @@ class WizardPageFactory(private val vm: WizardActivityState) {
}
}
-class WizardActivity : ComponentActivity() {
- private lateinit var vm: WizardActivityState
- private lateinit var newFile: ActivityResultLauncher
- private var onFileCreated: ((Uri) -> Unit)? = null
- private lateinit var chooseFile: ActivityResultLauncher
- private var onFileChosen: ((Uri) -> Unit)? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- vm = WizardActivityState(intent.getStringExtra("codename")!!)
- vm.activity = this
- chooseFile =
- registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
- if (uri == null) {
- Toast.makeText(
- this,
- getString(R.string.file_unavailable),
- Toast.LENGTH_LONG
- ).show()
- onFileChosen = null
- return@registerForActivityResult
- }
- if (onFileChosen != null) {
- onFileChosen!!(uri)
- onFileChosen = null
- } else {
- Toast.makeText(
- this@WizardActivity,
- getString(R.string.internal_file_error1),
- Toast.LENGTH_LONG
- ).show()
- }
- }
- newFile =
- registerForActivityResult(ActivityResultContracts.CreateDocument("application/octet-stream")) { uri: Uri? ->
- if (uri == null) {
- Toast.makeText(
- this,
- getString(R.string.file_unavailable),
- Toast.LENGTH_LONG
- ).show()
- onFileCreated = null
- return@registerForActivityResult
- }
- if (onFileCreated != null) {
- onFileCreated!!(uri)
- onFileCreated = null
- } else {
- Toast.makeText(
- this@WizardActivity,
- getString(R.string.internal_file_error1),
- Toast.LENGTH_LONG
- ).show()
- }
- }
- CoroutineScope(Dispatchers.Main).launch {
- vm.deviceInfo = JsonDeviceInfoFactory(vm.activity).get(vm.codename)!!
- vm.logic = DeviceLogic(vm.activity)
- val wizardPages = WizardPageFactory(vm).get(intent.getStringExtra("flow")!!)
- setContent {
- vm.navController = rememberNavController()
- AbmTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Column(
- verticalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth()
- ) {
- NavHost(
- navController = vm.navController,
- startDestination = "start",
- modifier = Modifier
- .fillMaxWidth()
- .weight(1.0f)
- ) {
- for (i in wizardPages) {
- composable(i.name) {
- if (!vm.btnsOverride) {
- vm.prevText.value = i.prev.text
- vm.nextText.value = i.next.text
- vm.onPrev.value = i.prev.onClick
- vm.onNext.value = i.next.onClick
- }
- i.run()
- }
- }
- }
- Box(Modifier.fillMaxWidth()) {
- BtnsRow(vm)
- }
- }
+@Composable
+fun WizardCompat(mvm: MainActivityState, flow: String) {
+ DisposableEffect(Unit) {
+ mvm.activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ onDispose { mvm.activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) }
+ }
+ val vm = remember { WizardActivityState(mvm) }
+ vm.navController = rememberNavController()
+ val wizardPages = remember(flow) { WizardPageFactory(vm).get(flow) }
+ Column(modifier = Modifier.fillMaxSize()) {
+ NavHost(
+ navController = vm.navController,
+ startDestination = "start",
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1.0f)
+ ) {
+ for (i in wizardPages) {
+ composable(i.name) {
+ if (!vm.btnsOverride) {
+ vm.prevText.value = i.prev.text
+ vm.nextText.value = i.next.text
+ vm.onPrev.value = i.prev.onClick
+ vm.onNext.value = i.next.onClick
}
+ i.run()
}
}
}
- }
-
- @SuppressLint("MissingSuperCall")
- @Deprecated("Deprecated in Java")
- override fun onBackPressed() {
- vm.onPrev.value(this)
- }
-
- fun navigate(next: String) {
- vm.navigate(next)
- }
- fun chooseFile(mime: String, callback: (Uri) -> Unit) {
- if (onFileChosen != null) {
- Toast.makeText(
- this,
- getString(R.string.internal_file_error2),
- Toast.LENGTH_LONG
- ).show()
- return
- }
- onFileChosen = callback
- chooseFile.launch(mime)
- }
- fun createFile(name: String, callback: (Uri) -> Unit) {
- if (onFileCreated != null) {
- Toast.makeText(
- this,
- getString(R.string.internal_file_error2),
- Toast.LENGTH_LONG
- ).show()
- return
+ Box(Modifier.fillMaxWidth()) {
+ BtnsRow(vm)
}
- onFileCreated = callback
- newFile.launch(name)
}
}
@@ -204,25 +94,32 @@ private class ExpectedDigestInputStream(stream: InputStream?,
}
class HashMismatchException(message: String) : Exception(message)
-class WizardActivityState(val codename: String) {
+class WizardActivityState(val mvm: MainActivityState) {
+ val codename = mvm.deviceInfo!!.codename
var btnsOverride = false
+ val activity = mvm.activity!!
lateinit var navController: NavHostController
- lateinit var activity: WizardActivity
- lateinit var logic: DeviceLogic
- lateinit var deviceInfo: DeviceInfo
- private var current = mutableStateOf("start")
+ val logic = mvm.logic!!
+ val deviceInfo = mvm.deviceInfo!!
var prevText = mutableStateOf("")
var nextText = mutableStateOf("")
- var onPrev: MutableState<(WizardActivity) -> Unit> = mutableStateOf({})
- var onNext: MutableState<(WizardActivity) -> Unit> = mutableStateOf({})
+ var onPrev: MutableState<(WizardActivityState) -> Unit> = mutableStateOf({})
+ var onNext: MutableState<(WizardActivityState) -> Unit> = mutableStateOf({})
var flashes: HashMap> = HashMap()
var texts: HashMap = HashMap()
fun navigate(next: String) {
btnsOverride = false
- current.value = next
- navController.navigate(current.value)
+ navController.navigate(next) {
+ launchSingleTop = true
+ }
+ }
+ fun finish() {
+ mvm.wizardCompat = null
+ mvm.wizardCompatE = null
+ mvm.wizardCompatPid = null
+ mvm.wizardCompatSid = null
}
fun copy(inputStream: InputStream, outputStream: OutputStream): Long {
@@ -270,7 +167,7 @@ class WizardActivityState(val codename: String) {
}
}
-class NavButton(val text: String, val onClick: (WizardActivity) -> Unit)
+class NavButton(val text: String, val onClick: (WizardActivityState) -> Unit)
class WizardPage(override val name: String, override val prev: NavButton,
override val next: NavButton, override val run: @Composable () -> Unit
) : IWizardPage
@@ -286,12 +183,12 @@ interface IWizardPage {
private fun BtnsRow(vm: WizardActivityState) {
Row {
TextButton(onClick = {
- vm.onPrev.value(vm.activity)
+ vm.onPrev.value(vm)
}, modifier = Modifier.weight(1f, true)) {
Text(vm.prevText.value)
}
TextButton(onClick = {
- vm.onNext.value(vm.activity)
+ vm.onNext.value(vm)
}, modifier = Modifier.weight(1f, true)) {
Text(vm.nextText.value)
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 34cdccd9..62faca79 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -74,8 +74,6 @@
Please choose an installer shell script, or use the download button to automatically download the latest version from the internet (recommended for most users).
This will reinstall DroidBoot
File not available, please try again later!
- Internal error - no file handler added!! Please cancel install
- Internal error - double file choose!! Please cancel install
Local update
Online update
Failed to check for updates! Please try again later.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 1a67fae5..39ac7b54 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -19,6 +19,9 @@
- true
- @color/red
+
#FE9200
#FE9200
diff --git a/build.gradle.kts b/build.gradle.kts
index b9d4444e..10a0edae 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,6 +4,5 @@ plugins {
val kotlinVersion = "2.0.0"
id("org.jetbrains.kotlin.android") version kotlinVersion apply false
id("org.jetbrains.kotlin.plugin.compose") version kotlinVersion apply false
- id("org.jetbrains.kotlinx.atomicfu") version "0.25.0" apply false
id("com.mikepenz.aboutlibraries.plugin") version "11.2.1" apply false
}
\ No newline at end of file