diff --git a/app/src/main/java/sh/sar/isodroid/ui/components/UsbWarningDialog.kt b/app/src/main/java/sh/sar/isodroid/ui/components/UsbWarningDialog.kt new file mode 100644 index 0000000..d06ffc8 --- /dev/null +++ b/app/src/main/java/sh/sar/isodroid/ui/components/UsbWarningDialog.kt @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +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.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +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.unit.dp + +@Composable +fun UsbWarningDialog( + isUnmount: Boolean, + onDismiss: () -> Unit, + onConfirm: (dontShowAgain: Boolean) -> Unit +) { + var dontShowAgain by remember { mutableStateOf(false) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text( + text = "USB Services Will Restart", + style = MaterialTheme.typography.headlineSmall + ) + }, + text = { + Column { + Text( + text = if (isUnmount) { + "Unmounting will restart USB services on your device, including:" + } else { + "Mounting will restart USB services on your device, including:" + }, + style = MaterialTheme.typography.bodyMedium + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = "• MTP file transfer\n• USB ADB\n• USB tethering\n• Other USB functions", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = "Active file transfers may be interrupted and could result in data corruption. Make sure no transfers are in progress.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = dontShowAgain, + onCheckedChange = { dontShowAgain = it } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "Don't show this again", + style = MaterialTheme.typography.bodyMedium + ) + } + } + }, + confirmButton = { + TextButton( + onClick = { onConfirm(dontShowAgain) } + ) { + Text(if (isUnmount) "Unmount" else "Mount") + } + }, + dismissButton = { + 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 b6c1bcf..cd1072f 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 @@ -49,14 +49,17 @@ import androidx.compose.runtime.rememberCoroutineScope 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.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.FileContextMenu import sh.sar.isodroid.ui.components.FileItemCard import sh.sar.isodroid.ui.components.MountDialog import sh.sar.isodroid.ui.components.StatusCard +import sh.sar.isodroid.ui.components.UsbWarningDialog import sh.sar.isodroid.viewmodel.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -69,12 +72,38 @@ fun MainScreen( val uiState by viewModel.uiState.collectAsState() val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() + val context = LocalContext.current var selectedFile by remember { mutableStateOf(null) } var showMountDialog by remember { mutableStateOf(false) } var showCreateImgDialog by remember { mutableStateOf(false) } var contextMenuFile by remember { mutableStateOf(null) } + // USB warning dialog state + var showUsbWarning by remember { mutableStateOf(false) } + var pendingMountPath by remember { mutableStateOf(null) } + var pendingMountOptions by remember { mutableStateOf(null) } + var isUnmountWarning by remember { mutableStateOf(false) } + + val prefs = remember { context.getSharedPreferences("iso_drive_prefs", android.content.Context.MODE_PRIVATE) } + val skipUsbWarning = remember { mutableStateOf(prefs.getBoolean("skip_usb_warning", false)) } + + fun showUsbWarningOrProceed( + isUnmount: Boolean, + mountPath: String? = null, + mountOptions: MountOptions? = null, + onProceed: () -> Unit + ) { + if (skipUsbWarning.value) { + onProceed() + } else { + isUnmountWarning = isUnmount + pendingMountPath = mountPath + pendingMountOptions = mountOptions + showUsbWarning = true + } + } + val pullToRefreshState = rememberPullToRefreshState() // Handle pull-to-refresh @@ -150,8 +179,10 @@ fun MainScreen( if (uiState.mountStatus.mounted) { ExtendedFloatingActionButton( onClick = { - scope.launch { - viewModel.unmount() + showUsbWarningOrProceed(isUnmount = true) { + scope.launch { + viewModel.unmount() + } } }, icon = { @@ -295,8 +326,14 @@ fun MainScreen( val filePath = file.path // Capture path before clearing state showMountDialog = false selectedFile = null - scope.launch { - viewModel.mount(filePath, options) + showUsbWarningOrProceed( + isUnmount = false, + mountPath = filePath, + mountOptions = options + ) { + scope.launch { + viewModel.mount(filePath, options) + } } } ) @@ -337,4 +374,39 @@ fun MainScreen( } ) } + + // USB warning dialog + if (showUsbWarning) { + UsbWarningDialog( + isUnmount = isUnmountWarning, + onDismiss = { + showUsbWarning = false + pendingMountPath = null + pendingMountOptions = null + }, + onConfirm = { dontShowAgain -> + if (dontShowAgain) { + prefs.edit().putBoolean("skip_usb_warning", true).apply() + skipUsbWarning.value = true + } + + // Capture values before clearing state + val isUnmount = isUnmountWarning + val path = pendingMountPath + val options = pendingMountOptions + + showUsbWarning = false + pendingMountPath = null + pendingMountOptions = null + + scope.launch { + if (isUnmount) { + viewModel.unmount() + } else if (path != null && options != null) { + viewModel.mount(path, options) + } + } + } + ) + } }