diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7559476..af1d93d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,6 +42,11 @@
android:name=".notification.UnmountReceiver"
android:exported="false" />
+
+
+
diff --git a/app/src/main/java/sh/sar/isodroid/isodrive/CreateImgEventBus.kt b/app/src/main/java/sh/sar/isodroid/isodrive/CreateImgEventBus.kt
new file mode 100644
index 0000000..75408cb
--- /dev/null
+++ b/app/src/main/java/sh/sar/isodroid/isodrive/CreateImgEventBus.kt
@@ -0,0 +1,37 @@
+package sh.sar.isodroid.isodrive
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+sealed class CreateImgEvent {
+ data class Progress(val bytesWritten: Long, val totalBytes: Long) : CreateImgEvent()
+ data class Complete(val success: Boolean, val filePath: String?) : CreateImgEvent()
+ object Cancelled : CreateImgEvent()
+}
+
+object CreateImgEventBus {
+ private val _events = MutableSharedFlow()
+ val events = _events.asSharedFlow()
+
+ private var cancelRequested = false
+
+ suspend fun emitProgress(bytesWritten: Long, totalBytes: Long) {
+ _events.emit(CreateImgEvent.Progress(bytesWritten, totalBytes))
+ }
+
+ suspend fun emitComplete(success: Boolean, filePath: String?) {
+ cancelRequested = false
+ _events.emit(CreateImgEvent.Complete(success, filePath))
+ }
+
+ suspend fun cancel() {
+ cancelRequested = true
+ _events.emit(CreateImgEvent.Cancelled)
+ }
+
+ fun isCancelRequested(): Boolean = cancelRequested
+
+ fun resetCancel() {
+ cancelRequested = false
+ }
+}
diff --git a/app/src/main/java/sh/sar/isodroid/notification/CreateImgReceiver.kt b/app/src/main/java/sh/sar/isodroid/notification/CreateImgReceiver.kt
new file mode 100644
index 0000000..fd6ed16
--- /dev/null
+++ b/app/src/main/java/sh/sar/isodroid/notification/CreateImgReceiver.kt
@@ -0,0 +1,19 @@
+package sh.sar.isodroid.notification
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import sh.sar.isodroid.isodrive.CreateImgEventBus
+
+class CreateImgReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == NotificationHelper.ACTION_CANCEL_CREATE) {
+ CoroutineScope(Dispatchers.Main).launch {
+ CreateImgEventBus.cancel()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/sh/sar/isodroid/notification/NotificationHelper.kt b/app/src/main/java/sh/sar/isodroid/notification/NotificationHelper.kt
index cb440e5..4be852c 100644
--- a/app/src/main/java/sh/sar/isodroid/notification/NotificationHelper.kt
+++ b/app/src/main/java/sh/sar/isodroid/notification/NotificationHelper.kt
@@ -16,8 +16,11 @@ class NotificationHelper(private val context: Context) {
companion object {
const val CHANNEL_ID = "iso_drive_mount_status"
+ const val CHANNEL_ID_PROGRESS = "iso_drive_progress"
const val NOTIFICATION_ID = 1001
+ const val NOTIFICATION_ID_PROGRESS = 1002
const val ACTION_UNMOUNT = "sh.sar.isodroid.ACTION_UNMOUNT"
+ const val ACTION_CANCEL_CREATE = "sh.sar.isodroid.ACTION_CANCEL_CREATE"
@Volatile
private var instance: NotificationHelper? = null
@@ -32,11 +35,11 @@ class NotificationHelper(private val context: Context) {
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
- createNotificationChannel()
+ createNotificationChannels()
}
- private fun createNotificationChannel() {
- val channel = NotificationChannel(
+ private fun createNotificationChannels() {
+ val mountChannel = NotificationChannel(
CHANNEL_ID,
"Mount Status",
NotificationManager.IMPORTANCE_LOW
@@ -44,7 +47,18 @@ class NotificationHelper(private val context: Context) {
description = "Shows when an ISO/IMG file is mounted"
setShowBadge(false)
}
- notificationManager.createNotificationChannel(channel)
+
+ val progressChannel = NotificationChannel(
+ CHANNEL_ID_PROGRESS,
+ "Progress",
+ NotificationManager.IMPORTANCE_LOW
+ ).apply {
+ description = "Shows progress for file operations"
+ setShowBadge(false)
+ }
+
+ notificationManager.createNotificationChannel(mountChannel)
+ notificationManager.createNotificationChannel(progressChannel)
}
fun showMountedNotification(mountStatus: MountStatus) {
@@ -106,4 +120,68 @@ class NotificationHelper(private val context: Context) {
fun hideNotification() {
notificationManager.cancel(NOTIFICATION_ID)
}
+
+ fun showCreateProgressNotification(fileName: String, progress: Int, bytesWritten: Long, totalBytes: Long) {
+ val writtenMB = bytesWritten / (1024 * 1024)
+ val totalMB = totalBytes / (1024 * 1024)
+
+ // Intent to open the app
+ val openIntent = Intent(context, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+ val openPendingIntent = PendingIntent.getActivity(
+ context,
+ 2,
+ openIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // Cancel action intent
+ val cancelIntent = Intent(context, CreateImgReceiver::class.java).apply {
+ action = ACTION_CANCEL_CREATE
+ }
+ val cancelPendingIntent = PendingIntent.getBroadcast(
+ context,
+ 3,
+ cancelIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val notification = NotificationCompat.Builder(context, CHANNEL_ID_PROGRESS)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle("Creating $fileName")
+ .setContentText("$writtenMB MB / $totalMB MB")
+ .setProgress(100, progress, false)
+ .setOngoing(true)
+ .setShowWhen(false)
+ .setContentIntent(openPendingIntent)
+ .addAction(
+ R.drawable.ic_eject,
+ "Cancel",
+ cancelPendingIntent
+ )
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+ .build()
+
+ notificationManager.notify(NOTIFICATION_ID_PROGRESS, notification)
+ }
+
+ fun showCreateCompleteNotification(fileName: String, success: Boolean) {
+ hideProgressNotification()
+
+ val notification = NotificationCompat.Builder(context, CHANNEL_ID_PROGRESS)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle(if (success) "Image Created" else "Creation Failed")
+ .setContentText(fileName)
+ .setAutoCancel(true)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .build()
+
+ notificationManager.notify(NOTIFICATION_ID_PROGRESS, notification)
+ }
+
+ fun hideProgressNotification() {
+ notificationManager.cancel(NOTIFICATION_ID_PROGRESS)
+ }
}
diff --git a/app/src/main/java/sh/sar/isodroid/ui/components/CreateImgDialog.kt b/app/src/main/java/sh/sar/isodroid/ui/components/CreateImgDialog.kt
new file mode 100644
index 0000000..4f7159f
--- /dev/null
+++ b/app/src/main/java/sh/sar/isodroid/ui/components/CreateImgDialog.kt
@@ -0,0 +1,234 @@
+package sh.sar.isodroid.ui.components
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+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.semantics.Role
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+
+data class CreateImgOptions(
+ val fileName: String,
+ val sizeValue: Long,
+ val sizeUnit: SizeUnit
+) {
+ val totalBytes: Long
+ get() = sizeValue * sizeUnit.bytes
+}
+
+enum class SizeUnit(val label: String, val bytes: Long) {
+ MB("MB", 1024L * 1024L),
+ GB("GB", 1024L * 1024L * 1024L)
+}
+
+data class CreateImgProgress(
+ val isCreating: Boolean = false,
+ val fileName: String = "",
+ val progress: Float = 0f,
+ val bytesWritten: Long = 0L,
+ val totalBytes: Long = 0L
+) {
+ val progressText: String
+ get() {
+ val writtenMB = bytesWritten / (1024 * 1024)
+ val totalMB = totalBytes / (1024 * 1024)
+ return "$writtenMB MB / $totalMB MB"
+ }
+}
+
+@Composable
+fun CreateImgDialog(
+ progress: CreateImgProgress,
+ onDismiss: () -> Unit,
+ onConfirm: (CreateImgOptions) -> Unit,
+ onCancel: () -> Unit
+) {
+ var fileName by remember { mutableStateOf("") }
+ var sizeValue by remember { mutableStateOf("") }
+ var sizeUnit by remember { mutableStateOf(SizeUnit.GB) }
+
+ val isValidInput = fileName.isNotBlank() &&
+ !fileName.contains("/") &&
+ sizeValue.toLongOrNull()?.let { it > 0 } == true
+
+ AlertDialog(
+ onDismissRequest = {
+ if (!progress.isCreating) onDismiss()
+ },
+ title = {
+ Text(
+ text = if (progress.isCreating) "Creating Image" else "Create IMG File",
+ style = MaterialTheme.typography.headlineSmall
+ )
+ },
+ text = {
+ Column {
+ if (progress.isCreating) {
+ // Show progress
+ Text(
+ text = progress.fileName,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ LinearProgressIndicator(
+ progress = { progress.progress },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = progress.progressText,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+
+ Text(
+ text = "${(progress.progress * 100).toInt()}%",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.primary
+ )
+ } else {
+ // Input form
+ Text(
+ text = "Create a blank IMG file that can be mounted as a writable USB drive.",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // File name input
+ OutlinedTextField(
+ value = fileName,
+ onValueChange = { fileName = it.replace("/", "") },
+ label = { Text("File Name") },
+ suffix = { Text(".img") },
+ singleLine = true,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Size input
+ Text(
+ text = "Size",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ OutlinedTextField(
+ value = sizeValue,
+ onValueChange = { newValue ->
+ if (newValue.isEmpty() || newValue.toLongOrNull() != null) {
+ sizeValue = newValue
+ }
+ },
+ label = { Text("Size") },
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+ singleLine = true,
+ modifier = Modifier.weight(1f)
+ )
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Column(Modifier.selectableGroup()) {
+ SizeUnit.entries.forEach { unit ->
+ Row(
+ Modifier
+ .selectable(
+ selected = sizeUnit == unit,
+ onClick = { sizeUnit = unit },
+ role = Role.RadioButton
+ )
+ .padding(vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
+ selected = sizeUnit == unit,
+ onClick = null
+ )
+ Text(
+ text = unit.label,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+ }
+ }
+ }
+
+ // Size warning for large files
+ val totalBytes = (sizeValue.toLongOrNull() ?: 0L) * sizeUnit.bytes
+ if (totalBytes > 4L * 1024 * 1024 * 1024) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "Large files may take a while to create.",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.error
+ )
+ }
+ }
+ }
+ },
+ confirmButton = {
+ if (progress.isCreating) {
+ TextButton(onClick = onCancel) {
+ Text("Cancel")
+ }
+ } else {
+ TextButton(
+ onClick = {
+ onConfirm(
+ CreateImgOptions(
+ fileName = fileName,
+ sizeValue = sizeValue.toLongOrNull() ?: 0L,
+ sizeUnit = sizeUnit
+ )
+ )
+ },
+ enabled = isValidInput
+ ) {
+ Text("Create")
+ }
+ }
+ },
+ dismissButton = {
+ if (!progress.isCreating) {
+ TextButton(onClick = onDismiss) {
+ Text("Cancel")
+ }
+ }
+ }
+ )
+}
diff --git a/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt b/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
index 82b141c..ba92760 100644
--- a/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
+++ b/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
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.Download
import androidx.compose.material.icons.filled.Eject
import androidx.compose.material.icons.filled.Refresh
@@ -37,6 +38,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import sh.sar.isodroid.data.IsoFile
import sh.sar.isodroid.data.MountOptions
+import sh.sar.isodroid.ui.components.CreateImgDialog
import sh.sar.isodroid.ui.components.FileBrowser
import sh.sar.isodroid.ui.components.MountDialog
import sh.sar.isodroid.ui.components.StatusCard
@@ -55,10 +57,13 @@ fun MainScreen(
var selectedFile by remember { mutableStateOf(null) }
var showMountDialog by remember { mutableStateOf(false) }
+ var showCreateImgDialog by remember { mutableStateOf(false) }
// Show error messages
LaunchedEffect(uiState.errorMessage) {
uiState.errorMessage?.let { message ->
+ // Close create dialog if open
+ showCreateImgDialog = false
snackbarHostState.showSnackbar(message)
viewModel.clearError()
}
@@ -67,6 +72,8 @@ fun MainScreen(
// Show success messages
LaunchedEffect(uiState.successMessage) {
uiState.successMessage?.let { message ->
+ // Close create dialog if open
+ showCreateImgDialog = false
snackbarHostState.showSnackbar(message)
viewModel.clearSuccess()
}
@@ -81,6 +88,15 @@ fun MainScreen(
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
actions = {
+ IconButton(
+ onClick = { showCreateImgDialog = true },
+ enabled = uiState.hasRoot && uiState.isSupported
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "Create IMG"
+ )
+ }
IconButton(onClick = { viewModel.refresh() }) {
Icon(
imageVector = Icons.Default.Refresh,
@@ -181,4 +197,21 @@ fun MainScreen(
)
}
}
+
+ // Create IMG dialog
+ if (showCreateImgDialog || uiState.createImgProgress.isCreating) {
+ CreateImgDialog(
+ progress = uiState.createImgProgress,
+ onDismiss = {
+ showCreateImgDialog = false
+ },
+ onConfirm = { options ->
+ viewModel.createImg(options)
+ },
+ onCancel = {
+ viewModel.cancelCreateImg()
+ showCreateImgDialog = false
+ }
+ )
+ }
}
diff --git a/app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt b/app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt
index dab7067..123302f 100644
--- a/app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt
+++ b/app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt
@@ -19,12 +19,16 @@ import kotlinx.coroutines.launch
import sh.sar.isodroid.data.IsoFile
import sh.sar.isodroid.data.MountOptions
import sh.sar.isodroid.data.MountStatus
+import sh.sar.isodroid.isodrive.CreateImgEvent
+import sh.sar.isodroid.isodrive.CreateImgEventBus
import sh.sar.isodroid.isodrive.IsoDriveManager
import sh.sar.isodroid.isodrive.MountEvent
import sh.sar.isodroid.isodrive.MountEventBus
import sh.sar.isodroid.isodrive.SupportStatus
import sh.sar.isodroid.notification.NotificationHelper
import sh.sar.isodroid.root.RootManager
+import sh.sar.isodroid.ui.components.CreateImgOptions
+import sh.sar.isodroid.ui.components.CreateImgProgress
import java.io.File
private val Application.dataStore: DataStore by preferencesDataStore(name = "settings")
@@ -39,6 +43,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val uiState: StateFlow = _uiState.asStateFlow()
private var navigationStack = mutableListOf()
+ private var currentCreateImgFileName = ""
companion object {
private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
@@ -62,6 +67,58 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
}
+
+ // Observe create img events
+ viewModelScope.launch {
+ CreateImgEventBus.events.collect { event ->
+ when (event) {
+ is CreateImgEvent.Progress -> {
+ val progress = event.bytesWritten.toFloat() / event.totalBytes.toFloat()
+ _uiState.update {
+ it.copy(
+ createImgProgress = CreateImgProgress(
+ isCreating = true,
+ fileName = currentCreateImgFileName,
+ progress = progress,
+ bytesWritten = event.bytesWritten,
+ totalBytes = event.totalBytes
+ )
+ )
+ }
+ notificationHelper.showCreateProgressNotification(
+ currentCreateImgFileName,
+ (progress * 100).toInt(),
+ event.bytesWritten,
+ event.totalBytes
+ )
+ }
+ is CreateImgEvent.Complete -> {
+ _uiState.update {
+ it.copy(
+ createImgProgress = CreateImgProgress(),
+ successMessage = if (event.success) "Image created successfully" else "Failed to create image"
+ )
+ }
+ notificationHelper.showCreateCompleteNotification(
+ event.filePath?.substringAfterLast("/") ?: "image.img",
+ event.success
+ )
+ if (event.success) {
+ loadFiles()
+ }
+ }
+ is CreateImgEvent.Cancelled -> {
+ _uiState.update {
+ it.copy(
+ createImgProgress = CreateImgProgress(),
+ successMessage = "Image creation cancelled"
+ )
+ }
+ notificationHelper.hideProgressNotification()
+ }
+ }
+ }
+ }
}
fun initialize() {
@@ -351,6 +408,99 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
+ fun createImg(options: CreateImgOptions) {
+ viewModelScope.launch {
+ val fileName = "${options.fileName}.img"
+ currentCreateImgFileName = fileName
+ val filePath = "${_uiState.value.currentPath}/$fileName"
+ val totalBytes = options.totalBytes
+
+ // Check if file already exists
+ val checkResult = RootManager.executeCommand("test -f \"$filePath\" && echo exists")
+ if (checkResult.output.trim() == "exists") {
+ _uiState.update { it.copy(errorMessage = "File already exists: $fileName") }
+ return@launch
+ }
+
+ // Start creating
+ _uiState.update {
+ it.copy(
+ createImgProgress = CreateImgProgress(
+ isCreating = true,
+ fileName = fileName,
+ progress = 0f,
+ bytesWritten = 0,
+ totalBytes = totalBytes
+ )
+ )
+ }
+
+ CreateImgEventBus.resetCancel()
+
+ // Use dd with progress reporting
+ // We'll create in chunks and report progress
+ val blockSize = 1024 * 1024L // 1MB blocks
+ val totalBlocks = totalBytes / blockSize
+ var writtenBlocks = 0L
+
+ // Create file with dd in background, checking for cancellation
+ val result = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
+ try {
+ // First, create the file with truncate to reserve space indication
+ val createResult = RootManager.executeCommand(
+ "dd if=/dev/zero of=\"$filePath\" bs=1M count=0 seek=$totalBlocks 2>/dev/null"
+ )
+
+ if (!createResult.success) {
+ return@withContext false
+ }
+
+ // Now fill with zeros in chunks for progress reporting
+ while (writtenBlocks < totalBlocks) {
+ if (CreateImgEventBus.isCancelRequested()) {
+ // Clean up partial file
+ RootManager.executeCommand("rm -f \"$filePath\"")
+ return@withContext false
+ }
+
+ // Write a chunk (up to 64MB at a time for efficiency)
+ val chunksToWrite = minOf(64, totalBlocks - writtenBlocks)
+ val chunkResult = RootManager.executeCommand(
+ "dd if=/dev/zero of=\"$filePath\" bs=1M count=$chunksToWrite seek=$writtenBlocks conv=notrunc 2>/dev/null"
+ )
+
+ if (!chunkResult.success) {
+ RootManager.executeCommand("rm -f \"$filePath\"")
+ return@withContext false
+ }
+
+ writtenBlocks += chunksToWrite
+ val bytesWritten = writtenBlocks * blockSize
+
+ CreateImgEventBus.emitProgress(bytesWritten, totalBytes)
+ }
+
+ true
+ } catch (e: Exception) {
+ RootManager.executeCommand("rm -f \"$filePath\"")
+ false
+ }
+ }
+
+ if (CreateImgEventBus.isCancelRequested()) {
+ CreateImgEventBus.emitComplete(false, null)
+ } else {
+ CreateImgEventBus.emitComplete(result, if (result) filePath else null)
+ }
+ }
+ }
+
+ fun cancelCreateImg() {
+ viewModelScope.launch {
+ CreateImgEventBus.cancel()
+ }
+ }
+
fun clearError() {
_uiState.update { it.copy(errorMessage = null) }
}
@@ -370,5 +520,6 @@ data class MainUiState(
val currentPath: String = "",
val isoDirectory: String = "",
val errorMessage: String? = null,
- val successMessage: String? = null
+ val successMessage: String? = null,
+ val createImgProgress: CreateImgProgress = CreateImgProgress()
)