add setup wizard, remove stoage api permission since root is used for that
This commit is contained in:
@@ -2,16 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<!-- Storage permissions -->
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="32" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="29"
|
|
||||||
tools:ignore="ScopedStorage" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
||||||
tools:ignore="ScopedStorage" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
||||||
|
|
||||||
<!-- Notification permission for Android 13+ -->
|
<!-- Notification permission for Android 13+ -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
package sh.sar.isodroid
|
package sh.sar.isodroid
|
||||||
|
|
||||||
import android.Manifest
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -20,7 +13,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
@@ -28,39 +20,14 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import sh.sar.isodroid.ui.screens.DownloadsScreen
|
import sh.sar.isodroid.ui.screens.DownloadsScreen
|
||||||
import sh.sar.isodroid.ui.screens.MainScreen
|
import sh.sar.isodroid.ui.screens.MainScreen
|
||||||
import sh.sar.isodroid.ui.screens.SettingsScreen
|
import sh.sar.isodroid.ui.screens.SettingsScreen
|
||||||
|
import sh.sar.isodroid.ui.screens.SetupWizardScreen
|
||||||
import sh.sar.isodroid.ui.theme.ISODroidTheme
|
import sh.sar.isodroid.ui.theme.ISODroidTheme
|
||||||
import sh.sar.isodroid.viewmodel.MainViewModel
|
import sh.sar.isodroid.viewmodel.MainViewModel
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private lateinit var viewModel: MainViewModel
|
private lateinit var viewModel: MainViewModel
|
||||||
private var hasStoragePermission by mutableStateOf(false)
|
private var setupComplete by mutableStateOf(false)
|
||||||
|
|
||||||
private val requestPermissionLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
|
||||||
) { permissions ->
|
|
||||||
hasStoragePermission = permissions.values.all { it }
|
|
||||||
if (hasStoragePermission) {
|
|
||||||
viewModel.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val notificationPermissionLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.RequestPermission()
|
|
||||||
) { /* Notification permission result - we continue regardless */ }
|
|
||||||
|
|
||||||
private val manageStorageLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) {
|
|
||||||
hasStoragePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
Environment.isExternalStorageManager()
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
if (hasStoragePermission) {
|
|
||||||
viewModel.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -68,7 +35,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
|
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
|
||||||
|
|
||||||
checkAndRequestPermissions()
|
// Check if setup was completed previously
|
||||||
|
setupComplete = isSetupComplete()
|
||||||
|
|
||||||
|
// Initialize ViewModel if setup is already complete
|
||||||
|
if (setupComplete) {
|
||||||
|
viewModel.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
ISODroidTheme {
|
ISODroidTheme {
|
||||||
@@ -76,50 +49,30 @@ class MainActivity : ComponentActivity() {
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
ISODroidNavHost(viewModel = viewModel)
|
if (setupComplete) {
|
||||||
|
ISODroidNavHost(viewModel = viewModel)
|
||||||
|
} else {
|
||||||
|
SetupWizardScreen(
|
||||||
|
onSetupComplete = {
|
||||||
|
markSetupComplete()
|
||||||
|
setupComplete = true
|
||||||
|
viewModel.initialize()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAndRequestPermissions() {
|
private fun isSetupComplete(): Boolean {
|
||||||
// Request notification permission on Android 13+
|
val prefs = getSharedPreferences("iso_drive_prefs", Context.MODE_PRIVATE)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
return prefs.getBoolean("setup_complete", false)
|
||||||
if (ContextCompat.checkSelfPermission(
|
}
|
||||||
this,
|
|
||||||
Manifest.permission.POST_NOTIFICATIONS
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request storage permission
|
private fun markSetupComplete() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
val prefs = getSharedPreferences("iso_drive_prefs", Context.MODE_PRIVATE)
|
||||||
if (!Environment.isExternalStorageManager()) {
|
prefs.edit().putBoolean("setup_complete", true).apply()
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
|
||||||
data = Uri.parse("package:$packageName")
|
|
||||||
}
|
|
||||||
manageStorageLauncher.launch(intent)
|
|
||||||
} else {
|
|
||||||
hasStoragePermission = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val permissions = arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
val needsPermission = permissions.any {
|
|
||||||
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsPermission) {
|
|
||||||
requestPermissionLauncher.launch(permissions)
|
|
||||||
} else {
|
|
||||||
hasStoragePermission = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package sh.sar.isodroid.ui.screens
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Album
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Notifications
|
||||||
|
import androidx.compose.material.icons.filled.Security
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sh.sar.isodroid.root.RootManager
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SetupWizardScreen(
|
||||||
|
onSetupComplete: () -> Unit
|
||||||
|
) {
|
||||||
|
var currentStep by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
when (currentStep) {
|
||||||
|
0 -> WelcomeStep(
|
||||||
|
onNext = { currentStep = 1 }
|
||||||
|
)
|
||||||
|
1 -> RootAccessStep(
|
||||||
|
onNext = { currentStep = 2 },
|
||||||
|
onSkip = { currentStep = 2 }
|
||||||
|
)
|
||||||
|
2 -> NotificationStep(
|
||||||
|
onNext = { currentStep = 3 },
|
||||||
|
onSkip = { currentStep = 3 }
|
||||||
|
)
|
||||||
|
3 -> CompleteStep(
|
||||||
|
onFinish = onSetupComplete
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WelcomeStep(
|
||||||
|
onNext: () -> Unit
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Album,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(80.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Welcome to ISO Drive",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Mount ISO and IMG files as USB drives directly from your Android device.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Let's set up a few things to get started.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onNext,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Get Started")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RootAccessStep(
|
||||||
|
onNext: () -> Unit,
|
||||||
|
onSkip: () -> Unit
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var hasRoot by remember { mutableStateOf<Boolean?>(null) }
|
||||||
|
var isChecking by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
PermissionCard(
|
||||||
|
icon = Icons.Default.Security,
|
||||||
|
title = "Root Access",
|
||||||
|
description = "ISO Drive needs superuser (root) access to mount ISO files as USB devices. This is required because mounting USB gadgets is a system-level operation.",
|
||||||
|
granted = hasRoot
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
if (hasRoot == true) {
|
||||||
|
Button(
|
||||||
|
onClick = onNext,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Continue")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
isChecking = true
|
||||||
|
scope.launch {
|
||||||
|
hasRoot = RootManager.requestRoot()
|
||||||
|
isChecking = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = !isChecking
|
||||||
|
) {
|
||||||
|
Text(if (isChecking) "Requesting..." else "Grant Root Access")
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onSkip,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Skip for now")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NotificationStep(
|
||||||
|
onNext: () -> Unit,
|
||||||
|
onSkip: () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// Check if we need to show this step at all (only Android 13+)
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
// Auto-advance on older Android versions
|
||||||
|
onNext()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPermission by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val permissionLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
hasPermission = granted
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionCard(
|
||||||
|
icon = Icons.Default.Notifications,
|
||||||
|
title = "Notifications",
|
||||||
|
description = "ISO Drive shows a notification when an ISO is mounted, with a quick unmount button. This helps you keep track of the mount status.",
|
||||||
|
granted = hasPermission
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
if (hasPermission) {
|
||||||
|
Button(
|
||||||
|
onClick = onNext,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Continue")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Enable Notifications")
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onSkip,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Skip for now")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CompleteStep(
|
||||||
|
onFinish: () -> Unit
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(80.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "You're all set!",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "ISO Drive is ready to use. Place your ISO or IMG files in the isodrive folder and start mounting.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Default directory: /sdcard/isodrive/",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onFinish,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Start Using ISO Drive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PermissionCard(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
granted: Boolean?
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(20.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
if (granted != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (granted) Icons.Default.Check else Icons.Default.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (granted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,11 +45,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private val DEFAULT_ISO_DIRECTORY = "${Environment.getExternalStorageDirectory().absolutePath}/isodrive"
|
private val DEFAULT_ISO_DIRECTORY = "${Environment.getExternalStorageDirectory().absolutePath}/isodrive"
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
private var initialized = false
|
||||||
viewModelScope.launch {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
init {
|
||||||
// Observe mount events from notification actions
|
// Observe mount events from notification actions
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
MountEventBus.events.collect { event ->
|
MountEventBus.events.collect { event ->
|
||||||
@@ -66,7 +64,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initialize() {
|
fun initialize() {
|
||||||
|
if (initialized) return
|
||||||
|
initialized = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
doInitialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doInitialize() {
|
||||||
_uiState.update { it.copy(isLoading = true) }
|
_uiState.update { it.copy(isLoading = true) }
|
||||||
|
|
||||||
// Load saved ISO directory
|
// Load saved ISO directory
|
||||||
@@ -77,8 +83,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
_uiState.update { it.copy(isoDirectory = savedDirectory, currentPath = savedDirectory) }
|
_uiState.update { it.copy(isoDirectory = savedDirectory, currentPath = savedDirectory) }
|
||||||
navigationStack.add(savedDirectory)
|
navigationStack.add(savedDirectory)
|
||||||
|
|
||||||
// Check root access
|
// Check root access (don't request, just check - wizard handles requesting)
|
||||||
val hasRoot = RootManager.requestRoot()
|
val hasRoot = RootManager.hasRoot()
|
||||||
_uiState.update { it.copy(hasRoot = hasRoot) }
|
_uiState.update { it.copy(hasRoot = hasRoot) }
|
||||||
|
|
||||||
if (hasRoot) {
|
if (hasRoot) {
|
||||||
|
|||||||
Reference in New Issue
Block a user