Skip to content

Commit 111e880

Browse files
authored
feat(Compose): Add confirmation dialog on multiple operations (#2529)
1 parent a30ff45 commit 111e880

File tree

7 files changed

+145
-40
lines changed

7 files changed

+145
-40
lines changed

app/src/main/java/app/revanced/manager/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ private fun ReVancedManager(vm: MainViewModel) {
164164
}
165165
}
166166
},
167-
vm = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
167+
viewModel = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
168168
)
169169
}
170170

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package app.revanced.manager.ui.component
2+
3+
import androidx.compose.material3.AlertDialog
4+
import androidx.compose.material3.Icon
5+
import androidx.compose.material3.Text
6+
import androidx.compose.material3.TextButton
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.graphics.vector.ImageVector
9+
import androidx.compose.ui.res.stringResource
10+
import app.revanced.manager.R
11+
12+
@Composable
13+
fun ConfirmDialog(
14+
onDismiss: () -> Unit,
15+
onConfirm: () -> Unit,
16+
title: String,
17+
description: String,
18+
icon: ImageVector
19+
) {
20+
AlertDialog(
21+
onDismissRequest = onDismiss,
22+
dismissButton = {
23+
TextButton(onDismiss) {
24+
Text(stringResource(R.string.cancel))
25+
}
26+
},
27+
confirmButton = {
28+
TextButton(
29+
onClick = {
30+
onConfirm()
31+
onDismiss()
32+
}
33+
) {
34+
Text(stringResource(R.string.confirm))
35+
}
36+
},
37+
title = { Text(title) },
38+
icon = { Icon(icon, null) },
39+
text = { Text(description) }
40+
)
41+
}

app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
77
import androidx.compose.foundation.layout.height
88
import androidx.compose.foundation.layout.size
99
import androidx.compose.material.icons.Icons
10+
import androidx.compose.material.icons.outlined.Delete
1011
import androidx.compose.material.icons.outlined.ErrorOutline
1112
import androidx.compose.material.icons.outlined.Warning
1213
import androidx.compose.material3.Icon
@@ -26,8 +27,9 @@ import androidx.compose.ui.unit.dp
2627
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2728
import app.revanced.manager.R
2829
import app.revanced.manager.domain.bundles.PatchBundleSource
29-
import app.revanced.manager.ui.component.haptics.HapticCheckbox
3030
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
31+
import app.revanced.manager.ui.component.ConfirmDialog
32+
import app.revanced.manager.ui.component.haptics.HapticCheckbox
3133
import kotlinx.coroutines.flow.map
3234

3335
@OptIn(ExperimentalFoundationApi::class)
@@ -42,6 +44,7 @@ fun BundleItem(
4244
toggleSelection: (Boolean) -> Unit,
4345
) {
4446
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
47+
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
4548
val state by bundle.state.collectAsStateWithLifecycle()
4649

4750
val version by remember(bundle) {
@@ -52,15 +55,25 @@ fun BundleItem(
5255
if (viewBundleDialogPage) {
5356
BundleInformationDialog(
5457
onDismissRequest = { viewBundleDialogPage = false },
55-
onDeleteRequest = {
56-
viewBundleDialogPage = false
57-
onDelete()
58-
},
58+
onDeleteRequest = { showDeleteConfirmationDialog = true },
5959
bundle = bundle,
6060
onUpdate = onUpdate,
6161
)
6262
}
6363

64+
if (showDeleteConfirmationDialog) {
65+
ConfirmDialog(
66+
onDismiss = { showDeleteConfirmationDialog = false },
67+
onConfirm = {
68+
onDelete()
69+
viewBundleDialogPage = false
70+
},
71+
title = stringResource(R.string.bundle_delete_single_dialog_title),
72+
description = stringResource(R.string.bundle_delete_single_dialog_description, name),
73+
icon = Icons.Outlined.Delete
74+
)
75+
}
76+
6477
ListItem(
6578
modifier = Modifier
6679
.height(64.dp)

app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.filled.BatteryAlert
2121
import androidx.compose.material.icons.filled.Close
2222
import androidx.compose.material.icons.outlined.Apps
2323
import androidx.compose.material.icons.outlined.BugReport
24+
import androidx.compose.material.icons.outlined.Delete
2425
import androidx.compose.material.icons.outlined.DeleteOutline
2526
import androidx.compose.material.icons.outlined.Download
2627
import androidx.compose.material.icons.outlined.Refresh
@@ -62,6 +63,7 @@ import app.revanced.manager.ui.component.AppTopBar
6263
import app.revanced.manager.ui.component.AutoUpdatesDialog
6364
import app.revanced.manager.ui.component.AvailableUpdateDialog
6465
import app.revanced.manager.ui.component.NotificationCard
66+
import app.revanced.manager.ui.component.ConfirmDialog
6567
import app.revanced.manager.ui.component.bundle.BundleTopBar
6668
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
6769
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
@@ -154,6 +156,20 @@ fun DashboardScreen(
154156
}
155157
)
156158

159+
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
160+
if (showDeleteConfirmationDialog) {
161+
ConfirmDialog(
162+
onDismiss = { showDeleteConfirmationDialog = false },
163+
onConfirm = {
164+
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
165+
vm.cancelSourceSelection()
166+
},
167+
title = stringResource(R.string.bundle_delete_multiple_dialog_title),
168+
description = stringResource(R.string.bundle_delete_multiple_dialog_description),
169+
icon = Icons.Outlined.Delete
170+
)
171+
}
172+
157173
Scaffold(
158174
topBar = {
159175
if (bundlesSelectable) {
@@ -169,8 +185,7 @@ fun DashboardScreen(
169185
actions = {
170186
IconButton(
171187
onClick = {
172-
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
173-
vm.cancelSourceSelection()
188+
showDeleteConfirmationDialog = true
174189
}
175190
) {
176191
Icon(

app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.LazyColumn
1717
import androidx.compose.foundation.lazy.items
1818
import androidx.compose.material.icons.Icons
1919
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
20+
import androidx.compose.material.icons.outlined.Cancel
2021
import androidx.compose.material.icons.outlined.FileDownload
2122
import androidx.compose.material.icons.outlined.PostAdd
2223
import androidx.compose.material.icons.outlined.Save
@@ -45,6 +46,7 @@ import app.revanced.manager.R
4546
import app.revanced.manager.data.room.apps.installed.InstallType
4647
import app.revanced.manager.ui.component.AppScaffold
4748
import app.revanced.manager.ui.component.AppTopBar
49+
import app.revanced.manager.ui.component.ConfirmDialog
4850
import app.revanced.manager.ui.component.InstallerStatusDialog
4951
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
5052
import app.revanced.manager.ui.component.patcher.InstallPickerDialog
@@ -58,25 +60,23 @@ import app.revanced.manager.util.EventEffect
5860
@Composable
5961
fun PatcherScreen(
6062
onBackClick: () -> Unit,
61-
vm: PatcherViewModel
63+
viewModel: PatcherViewModel
6264
) {
63-
fun leaveScreen() {
64-
vm.onBack()
65-
onBackClick()
66-
}
67-
BackHandler(onBack = ::leaveScreen)
6865

6966
val context = LocalContext.current
7067
val exportApkLauncher =
71-
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), vm::export)
68+
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), viewModel::export)
7269

73-
val patcherSucceeded by vm.patcherSucceeded.observeAsState(null)
74-
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (vm.installedPackageName != null || !vm.isInstalling) } }
70+
val patcherSucceeded by viewModel.patcherSucceeded.observeAsState(null)
71+
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (viewModel.installedPackageName != null || !viewModel.isInstalling) } }
7572
var showInstallPicker by rememberSaveable { mutableStateOf(false) }
73+
var showDismissConfirmationDialog by rememberSaveable { mutableStateOf(false) }
74+
75+
BackHandler(onBack = { showDismissConfirmationDialog = true })
7676

7777
val steps by remember {
7878
derivedStateOf {
79-
vm.steps.groupBy { it.category }
79+
viewModel.steps.groupBy { it.category }
8080
}
8181
}
8282

@@ -93,34 +93,47 @@ fun PatcherScreen(
9393
if (showInstallPicker)
9494
InstallPickerDialog(
9595
onDismiss = { showInstallPicker = false },
96-
onConfirm = vm::install
96+
onConfirm = viewModel::install
97+
)
98+
99+
if (showDismissConfirmationDialog) {
100+
ConfirmDialog(
101+
onDismiss = { showDismissConfirmationDialog = false },
102+
onConfirm = {
103+
viewModel.onBack()
104+
onBackClick()
105+
},
106+
title = stringResource(R.string.patcher_stop_confirm_title),
107+
description = stringResource(R.string.patcher_stop_confirm_description),
108+
icon = Icons.Outlined.Cancel
97109
)
110+
}
98111

99-
vm.packageInstallerStatus?.let {
100-
InstallerStatusDialog(it, vm, vm::dismissPackageInstallerDialog)
112+
viewModel.packageInstallerStatus?.let {
113+
InstallerStatusDialog(it, viewModel, viewModel::dismissPackageInstallerDialog)
101114
}
102115

103116
val activityLauncher = rememberLauncherForActivityResult(
104117
contract = ActivityResultContracts.StartActivityForResult(),
105-
onResult = vm::handleActivityResult
118+
onResult = viewModel::handleActivityResult
106119
)
107-
EventEffect(flow = vm.launchActivityFlow) { intent ->
120+
EventEffect(flow = viewModel.launchActivityFlow) { intent ->
108121
activityLauncher.launch(intent)
109122
}
110123

111-
vm.activityPromptDialog?.let { title ->
124+
viewModel.activityPromptDialog?.let { title ->
112125
AlertDialog(
113-
onDismissRequest = vm::rejectInteraction,
126+
onDismissRequest = viewModel::rejectInteraction,
114127
confirmButton = {
115128
TextButton(
116-
onClick = vm::allowInteraction
129+
onClick = viewModel::allowInteraction
117130
) {
118131
Text(stringResource(R.string.continue_))
119132
}
120133
},
121134
dismissButton = {
122135
TextButton(
123-
onClick = vm::rejectInteraction
136+
onClick = viewModel::rejectInteraction
124137
) {
125138
Text(stringResource(R.string.cancel))
126139
}
@@ -137,20 +150,20 @@ fun PatcherScreen(
137150
AppTopBar(
138151
title = stringResource(R.string.patcher),
139152
scrollBehavior = scrollBehavior,
140-
onBackClick = ::leaveScreen
153+
onBackClick = { showDismissConfirmationDialog = true }
141154
)
142155
},
143156
bottomBar = {
144157
BottomAppBar(
145158
actions = {
146159
IconButton(
147-
onClick = { exportApkLauncher.launch("${vm.packageName}_${vm.version}_revanced_patched.apk") },
160+
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") },
148161
enabled = patcherSucceeded == true
149162
) {
150163
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk))
151164
}
152165
IconButton(
153-
onClick = { vm.exportLogs(context) },
166+
onClick = { viewModel.exportLogs(context) },
154167
enabled = patcherSucceeded != null
155168
) {
156169
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs))
@@ -161,11 +174,11 @@ fun PatcherScreen(
161174
HapticExtendedFloatingActionButton(
162175
text = {
163176
Text(
164-
stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app)
177+
stringResource(if (viewModel.installedPackageName == null) R.string.install_app else R.string.open_app)
165178
)
166179
},
167180
icon = {
168-
vm.installedPackageName?.let {
181+
viewModel.installedPackageName?.let {
169182
Icon(
170183
Icons.AutoMirrored.Outlined.OpenInNew,
171184
stringResource(R.string.open_app)
@@ -176,10 +189,10 @@ fun PatcherScreen(
176189
)
177190
},
178191
onClick = {
179-
if (vm.installedPackageName == null)
180-
if (vm.isDeviceRooted()) showInstallPicker = true
181-
else vm.install(InstallType.DEFAULT)
182-
else vm.open()
192+
if (viewModel.installedPackageName == null)
193+
if (viewModel.isDeviceRooted()) showInstallPicker = true
194+
else viewModel.install(InstallType.DEFAULT)
195+
else viewModel.open()
183196
}
184197
)
185198
}
@@ -193,7 +206,7 @@ fun PatcherScreen(
193206
.fillMaxSize()
194207
) {
195208
LinearProgressIndicator(
196-
progress = { vm.progress },
209+
progress = { viewModel.progress },
197210
modifier = Modifier.fillMaxWidth()
198211
)
199212

@@ -209,8 +222,8 @@ fun PatcherScreen(
209222
Steps(
210223
category = category,
211224
steps = steps,
212-
stepCount = if (category == StepCategory.PATCHING) vm.patchesProgress else null,
213-
stepProgressProvider = vm
225+
stepCount = if (category == StepCategory.PATCHING) viewModel.patchesProgress else null,
226+
stepProgressProvider = viewModel
214227
)
215228
}
216229
}

app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding
99
import androidx.compose.foundation.lazy.items
1010
import androidx.compose.material.icons.Icons
1111
import androidx.compose.material.icons.filled.Delete
12+
import androidx.compose.material.icons.outlined.Delete
1213
import androidx.compose.material3.AlertDialog
1314
import androidx.compose.material3.ExperimentalMaterial3Api
1415
import androidx.compose.material3.Icon
@@ -43,6 +44,7 @@ import app.revanced.manager.ui.component.AppTopBar
4344
import app.revanced.manager.ui.component.ExceptionViewerDialog
4445
import app.revanced.manager.ui.component.GroupHeader
4546
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
47+
import app.revanced.manager.ui.component.ConfirmDialog
4648
import app.revanced.manager.ui.component.haptics.HapticCheckbox
4749
import app.revanced.manager.ui.component.settings.SettingsListItem
4850
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
@@ -59,6 +61,17 @@ fun DownloadsSettingsScreen(
5961
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
6062
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
6163
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
64+
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
65+
66+
if (showDeleteConfirmationDialog) {
67+
ConfirmDialog(
68+
onDismiss = { showDeleteConfirmationDialog = false },
69+
onConfirm = { viewModel.deleteApps() },
70+
title = stringResource(R.string.downloader_plugin_delete_apps_title),
71+
description = stringResource(R.string.downloader_plugin_delete_apps_description),
72+
icon = Icons.Outlined.Delete
73+
)
74+
}
6275

6376
Scaffold(
6477
topBar = {
@@ -68,7 +81,7 @@ fun DownloadsSettingsScreen(
6881
onBackClick = onBackClick,
6982
actions = {
7083
if (viewModel.appSelection.isNotEmpty()) {
71-
IconButton(onClick = { viewModel.deleteApps() }) {
84+
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
7285
Icon(Icons.Default.Delete, stringResource(R.string.delete))
7386
}
7487
}

0 commit comments

Comments
 (0)