Skip to content

Commit 033b3a7

Browse files
committed
Mod delete dialog now uses checkboxes.
1 parent 0550187 commit 033b3a7

File tree

1 file changed

+153
-113
lines changed

1 file changed

+153
-113
lines changed

lib/utils/dialogs.dart

Lines changed: 153 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import 'package:open_filex/open_filex.dart';
88
import 'package:path/path.dart' as p;
99
import 'package:trios/about/about_page.dart';
1010
import 'package:trios/models/mod_variant.dart';
11-
import 'package:trios/thirdparty/dartx/iterable.dart';
1211
import 'package:trios/thirdparty/faded_scrollable/faded_scrollable.dart';
1312
import 'package:trios/trios/app_state.dart';
1413
import 'package:trios/trios/constants.dart';
1514
import 'package:trios/utils/platform_specific.dart';
16-
import 'package:trios/widgets/checkbox_with_label.dart';
15+
import 'package:trios/widgets/disable.dart';
1716
import 'package:trios/widgets/trios_app_icon.dart';
1817

1918
import 'logging.dart';
@@ -131,16 +130,77 @@ Future<void> showDeleteModFoldersConfirmationDialog(
131130
ref.read(AppState.modVariants.notifier).reloadModVariants();
132131
}
133132

133+
Row _buildVariantToDeleteRow(
134+
ModVariant variant,
135+
bool isEnabled,
136+
ThemeData theme, {
137+
required bool checked,
138+
required ValueChanged<bool?> onChanged,
139+
}) {
140+
return Row(
141+
children: [
142+
Checkbox(value: checked, onChanged: onChanged),
143+
InkWell(
144+
onTap: () => onChanged(!checked),
145+
splashColor: Colors.transparent,
146+
highlightColor: Colors.transparent,
147+
hoverColor: Colors.transparent,
148+
child: Padding(
149+
padding: const .only(left: 4),
150+
child: Column(
151+
mainAxisSize: MainAxisSize.min,
152+
crossAxisAlignment: CrossAxisAlignment.start,
153+
children: [
154+
Row(
155+
children: [
156+
Text(
157+
"${variant.modInfo.nameOrId} v${variant.modInfo.version}",
158+
style: theme.textTheme.labelLarge,
159+
),
160+
if (isEnabled)
161+
Text(
162+
" (enabled)",
163+
style: theme.textTheme.labelLarge?.copyWith(
164+
fontWeight: FontWeight.bold,
165+
),
166+
),
167+
],
168+
),
169+
Text(
170+
"${variant.modFolder.path}",
171+
style: theme.textTheme.labelMedium?.copyWith(
172+
color: theme.textTheme.bodyMedium?.color?.withAlpha(200),
173+
),
174+
),
175+
],
176+
),
177+
),
178+
),
179+
],
180+
);
181+
}
182+
134183
if (variantsToDelete.isEmpty) {
135184
return;
136185
}
137186

138187
runZonedGuarded(
139188
() async {
140-
bool allowDeletingEnabledMods =
141-
allowDeletingEnabledModsDefaultState ?? false;
142-
List<ModVariant> filteredVariantsToDelete = variantsToDelete;
143-
List<ModVariant> enabledVariantsThatWillNotBeDeleted = [];
189+
// Build enabled/disabled maps and selection state
190+
final allMods = ref.read(AppState.mods);
191+
final isEnabledBySmolId = <String, bool>{
192+
for (final v in variantsToDelete) v.smolId: v.isEnabled(allMods),
193+
};
194+
final enabledSmolIds = isEnabledBySmolId.entries
195+
.where((e) => e.value)
196+
.map((e) => e.key)
197+
.toSet();
198+
final disabledSmolIds = isEnabledBySmolId.entries
199+
.where((e) => !e.value)
200+
.map((e) => e.key)
201+
.toSet();
202+
final selectedSmolIds = <String>{};
203+
bool initializedSelection = false;
144204

145205
final shouldDelete = await showDialog<bool>(
146206
context: context,
@@ -149,27 +209,26 @@ Future<void> showDeleteModFoldersConfirmationDialog(
149209

150210
return StatefulBuilder(
151211
builder: (context, setState) {
152-
final allMods = ref.read(AppState.mods);
153-
154-
final isOnlyEnabledMods = variantsToDelete.all(
155-
(variant) => variant.isEnabled(allMods),
156-
);
157-
158-
enabledVariantsThatWillNotBeDeleted =
159-
(isOnlyEnabledMods || allowDeletingEnabledMods)
160-
? []
161-
: variantsToDelete
162-
.where((variant) => !variant.isEnabled(allMods))
163-
.toList();
164-
165-
filteredVariantsToDelete =
166-
variantsToDelete - enabledVariantsThatWillNotBeDeleted;
212+
// Initialize the default selection once per dialog open
213+
if (!initializedSelection) {
214+
final hasBoth =
215+
enabledSmolIds.isNotEmpty && disabledSmolIds.isNotEmpty;
216+
if (hasBoth) {
217+
selectedSmolIds
218+
..clear()
219+
..addAll(disabledSmolIds);
220+
} else {
221+
selectedSmolIds
222+
..clear()
223+
..addAll(isEnabledBySmolId.keys);
224+
}
225+
initializedSelection = true;
226+
}
167227

168-
final enabledVariants = variantsToDelete
169-
.where((variant) => variant.isEnabled(allMods))
170-
.toList();
171-
172-
final s = filteredVariantsToDelete.length == 1 ? "s" : "";
228+
final hasBothEnabledAndDisabled =
229+
enabledSmolIds.isNotEmpty && disabledSmolIds.isNotEmpty;
230+
final selectedCount = selectedSmolIds.length;
231+
final s = selectedCount == 1 ? "" : "s";
173232

174233
return AlertDialog(
175234
title: Text('Delete Mod$s'),
@@ -180,9 +239,7 @@ Future<void> showDeleteModFoldersConfirmationDialog(
180239
crossAxisAlignment: CrossAxisAlignment.start,
181240
spacing: 8,
182241
children: [
183-
if (filteredVariantsToDelete.isNotEmpty ||
184-
isOnlyEnabledMods)
185-
const Text('Are you sure you want to delete:'),
242+
const Text('Select the mod folders to delete:'),
186243
Flexible(
187244
child: FadedScrollable(
188245
child: Scrollbar(
@@ -196,67 +253,73 @@ Future<void> showDeleteModFoldersConfirmationDialog(
196253
crossAxisAlignment: CrossAxisAlignment.start,
197254
spacing: 4,
198255
children: [
199-
for (final variant
200-
in filteredVariantsToDelete)
201-
_buildVariantToDeleteRow(variant, theme),
202-
if (enabledVariantsThatWillNotBeDeleted
203-
.isNotEmpty)
204-
Padding(
205-
padding: const .only(top: 16),
206-
child: Text.rich(
207-
TextSpan(
208-
style: theme.textTheme.bodyLarge
209-
?.copyWith(fontSize: 20),
210-
children: [
211-
TextSpan(
212-
text:
213-
"The following mods will ",
214-
),
215-
TextSpan(
216-
text: "not",
217-
style: theme
218-
.textTheme
219-
.bodyMedium
220-
?.copyWith(
221-
fontSize: 20,
222-
fontWeight: .bold,
223-
),
224-
),
225-
TextSpan(
226-
text:
227-
" be deleted because they are enabled:",
228-
),
229-
],
230-
),
256+
for (final variant in variantsToDelete)
257+
_buildVariantToDeleteRow(
258+
variant,
259+
variant.isEnabled(allMods),
260+
theme,
261+
checked: selectedSmolIds.contains(
262+
variant.smolId,
231263
),
264+
onChanged: (newValue) {
265+
setState(() {
266+
if (newValue == true) {
267+
selectedSmolIds.add(
268+
variant.smolId,
269+
);
270+
} else {
271+
selectedSmolIds.remove(
272+
variant.smolId,
273+
);
274+
}
275+
});
276+
},
232277
),
233-
for (final variant
234-
in enabledVariantsThatWillNotBeDeleted)
235-
_buildVariantToDeleteRow(variant, theme),
236278
],
237279
),
238280
),
239281
),
240282
),
241283
),
242284
),
243-
if (!isOnlyEnabledMods && enabledVariants.isNotEmpty)
244-
CheckboxWithLabel(
245-
label: "Allow deleting of currently-enabled mods",
246-
value: allowDeletingEnabledMods,
247-
onChanged: (newValue) {
248-
setState(() {
249-
allowDeletingEnabledMods =
250-
newValue ??
251-
allowDeletingEnabledModsDefaultState ??
252-
false;
253-
});
254-
},
285+
if (variantsToDelete.length > 5)
286+
Row(
287+
children: [
288+
Align(
289+
alignment: Alignment.centerLeft,
290+
child: TextButton.icon(
291+
onPressed: () {
292+
setState(() {
293+
selectedSmolIds.addAll(
294+
variantsToDelete.map((v) => v.smolId),
295+
);
296+
});
297+
},
298+
icon: const Icon(Icons.select_all),
299+
label: const Text('Select all'),
300+
),
301+
),
302+
Align(
303+
alignment: Alignment.centerLeft,
304+
child: TextButton.icon(
305+
onPressed: () {
306+
setState(() {
307+
selectedSmolIds.clear();
308+
});
309+
},
310+
icon: const Icon(Icons.deselect),
311+
label: const Text('Deselect all'),
312+
),
313+
),
314+
],
255315
),
256-
Text(
257-
"This will delete the mod folder$s on disk. This action cannot be undone.",
258-
style: theme.textTheme.bodyMedium?.copyWith(
259-
fontWeight: FontWeight.bold,
316+
Padding(
317+
padding: const .only(top: 16),
318+
child: Text(
319+
"This will delete the mod folder$s on disk. This action cannot be undone.",
320+
style: theme.textTheme.bodyMedium?.copyWith(
321+
fontWeight: FontWeight.bold,
322+
),
260323
),
261324
),
262325
],
@@ -267,12 +330,13 @@ Future<void> showDeleteModFoldersConfirmationDialog(
267330
onPressed: () => Navigator.of(context).pop(false),
268331
child: const Text('Cancel'),
269332
),
270-
TextButton.icon(
271-
onPressed: () => Navigator.of(context).pop(true),
272-
label: Text(
273-
'Delete ${filteredVariantsToDelete.length} Mod$s',
333+
Disable(
334+
isEnabled: selectedCount > 0,
335+
child: TextButton.icon(
336+
onPressed: () => Navigator.of(context).pop(true),
337+
label: Text('Delete $selectedCount Mod$s'),
338+
icon: const Icon(Icons.delete),
274339
),
275-
icon: const Icon(Icons.delete),
276340
),
277341
],
278342
);
@@ -282,7 +346,10 @@ Future<void> showDeleteModFoldersConfirmationDialog(
282346
);
283347

284348
if (shouldDelete == true) {
285-
for (var variant in filteredVariantsToDelete) {
349+
final toDelete = variantsToDelete
350+
.where((v) => selectedSmolIds.contains(v.smolId))
351+
.toList();
352+
for (var variant in toDelete) {
286353
deleteFolder(variant.modFolder.absolute.path);
287354
}
288355
}
@@ -297,30 +364,3 @@ Future<void> showDeleteModFoldersConfirmationDialog(
297364
},
298365
);
299366
}
300-
301-
Row _buildVariantToDeleteRow(ModVariant variant, ThemeData theme) {
302-
return Row(
303-
children: [
304-
Checkbox(value: true, onChanged: (newValue) {}),
305-
Padding(
306-
padding: const .only(left: 4),
307-
child: Column(
308-
mainAxisSize: MainAxisSize.min,
309-
crossAxisAlignment: CrossAxisAlignment.start,
310-
children: [
311-
Text(
312-
"${variant.modInfo.nameOrId} v${variant.modInfo.version}",
313-
style: theme.textTheme.labelLarge,
314-
),
315-
Text(
316-
variant.modFolder.path,
317-
style: theme.textTheme.labelMedium?.copyWith(
318-
color: theme.textTheme.bodyMedium?.color?.withAlpha(200),
319-
),
320-
),
321-
],
322-
),
323-
),
324-
],
325-
);
326-
}

0 commit comments

Comments
 (0)