Add a Settings option to reorder the selection action bar's actions

NooSelectionBar's fixed inline slots (3 mobile / 4 desktop, the rest
behind "More") previously just took the first N actions in whatever
order each screen happened to build them in. Give bulk actions a
stable SelectionActionKind identity (independent of their dynamic
icon/label, e.g. favorite vs. unfavorite) so Settings can offer a
drag-to-reorder list - mirroring the existing Tabs section, minus the
pin/hide concept, which doesn't apply here - and each screen applies
that saved priority order via orderSelectionActions before handing
its action list to the bar.
This commit is contained in:
2026-09-28 12:21:36 -04:00
parent 800e8ebf44
commit 254db255a7
10 changed files with 347 additions and 27 deletions
+23 -6
View File
@@ -5,6 +5,7 @@ import '../widgets/noo/nav/noo_toolbar.dart';
import '../widgets/noo/noo_layout.dart';
import '../widgets/settings/settings_account_card.dart';
import '../widgets/settings/settings_accounts.dart';
import '../widgets/settings/settings_action_bar.dart';
import '../widgets/settings/settings_appearance.dart';
import '../widgets/settings/settings_file_sync.dart';
import '../widgets/settings/settings_files_cache.dart';
@@ -12,9 +13,11 @@ import '../widgets/settings/settings_security.dart';
import '../widgets/settings/settings_swipe.dart';
import '../widgets/settings/settings_tabs.dart';
/// Settings, pushed on top of the shell (DESIGN_SYSTEM.md 4's 8-section
/// Settings, pushed on top of the shell (DESIGN_SYSTEM.md 4's 9-section
/// order: account card, accounts, security, file sync, files cache,
/// appearance, tabs, swipe on a file). One column of [SettingsSection]s on
/// appearance, tabs, action bar, swipe on a file). One column of
/// [SettingsSection]s (and [SettingsActionBarSection], which has no option
/// rows of its own to put in one - just the reorder list) on
/// mobile; a 2-column grid of cards on desktop - see each
/// `widgets/settings/*.dart` file for a section's own content and any
/// setting that had to be slotted in or grouped under "Advanced appearance".
@@ -29,6 +32,7 @@ class AccountView extends StatelessWidget {
SettingsFilesCacheSection(),
SettingsAppearanceSection(),
SettingsTabsSection(),
SettingsActionBarSection(),
SettingsSwipeSection(),
];
@@ -48,7 +52,9 @@ class AccountView extends StatelessWidget {
),
body: SafeArea(
top: false,
child: desktop ? _DesktopGrid(sections: _sections) : _MobileList(sections: _sections),
child: desktop
? _DesktopGrid(sections: _sections)
: _MobileList(sections: _sections),
),
);
}
@@ -61,10 +67,18 @@ class _MobileList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.fromLTRB(NooSpace.sm, NooSpace.sm, NooSpace.sm, NooSpace.xxl),
padding: const EdgeInsets.fromLTRB(
NooSpace.sm,
NooSpace.sm,
NooSpace.sm,
NooSpace.xxl,
),
physics: const BouncingScrollPhysics(),
children: [
for (final section in sections) ...[section, const SizedBox(height: NooSpace.xl)],
for (final section in sections) ...[
section,
const SizedBox(height: NooSpace.xl),
],
],
);
}
@@ -112,7 +126,10 @@ class _Column extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final child in children) ...[child, const SizedBox(height: NooSpace.xl)],
for (final child in children) ...[
child,
const SizedBox(height: NooSpace.xl),
],
],
);
}
+12 -1
View File
@@ -96,13 +96,15 @@ class _FavoritesViewState extends State<FavoritesView> {
/// The bulk actions shown in the sticky selection toolbar for the
/// currently-selected items - same set Files/Photos offer.
List<SelectionAction> _buildSelectionActions(List<NextcloudItem> selected) {
return [
final actions = [
SelectionAction(
kind: SelectionActionKind.favorite,
icon: LucideIcons.starOff,
label: 'Remove from favorites',
onTap: () => _unfavoriteSelected(selected),
),
SelectionAction(
kind: SelectionActionKind.share,
icon: LucideIcons.share2,
label: 'Share',
onTap: () => selected.length == 1
@@ -110,32 +112,41 @@ class _FavoritesViewState extends State<FavoritesView> {
: _shareSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.download,
icon: LucideIcons.download,
label: 'Download',
onTap: () => _downloadSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.delete,
icon: LucideIcons.trash2,
label: 'Delete',
onTap: () => _confirmDeleteSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.copy,
icon: LucideIcons.copy,
label: 'Copy',
onTap: () => _moveOrCopySelected(selected, copy: true),
),
SelectionAction(
kind: SelectionActionKind.move,
icon: LucideIcons.folderInput,
label: 'Move',
onTap: () => _moveOrCopySelected(selected, copy: false),
),
if (selected.length == 1)
SelectionAction(
kind: SelectionActionKind.details,
icon: LucideIcons.info,
label: 'Details',
onTap: () => DetailsSheet.show(context, selected.single),
),
];
return orderSelectionActions(
actions,
context.read<SettingsController>().selectionActionOrder,
);
}
Future<void> _unfavoriteSelected(List<NextcloudItem> items) async {
+15 -1
View File
@@ -419,6 +419,7 @@ class _FilesViewState extends State<FilesView>
if (pick.isPicking) {
return [
SelectionAction(
kind: SelectionActionKind.favorite,
icon: LucideIcons.check,
label: 'Use ${selected.length} item(s)',
onTap: () => pick.confirmPick(selected),
@@ -427,13 +428,15 @@ class _FilesViewState extends State<FilesView>
}
final allFavorited = selected.every((i) => i.isFavorite);
final allSynced = selected.every((i) => sync.isPathSynced(i.path));
return [
final actions = [
SelectionAction(
kind: SelectionActionKind.favorite,
icon: allFavorited ? LucideIcons.starOff : LucideIcons.star,
label: allFavorited ? 'Remove from favorites' : 'Favorite',
onTap: () => _favoriteSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.share,
icon: LucideIcons.share2,
label: 'Share',
onTap: () => selected.length == 1
@@ -441,43 +444,54 @@ class _FilesViewState extends State<FilesView>
: _shareSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.download,
icon: LucideIcons.download,
label: 'Download',
onTap: () => _downloadSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.delete,
icon: LucideIcons.trash2,
label: 'Delete',
onTap: () => _confirmDeleteSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.copy,
icon: LucideIcons.copy,
label: 'Copy',
onTap: () => _moveOrCopySelected(context, selected, copy: true),
),
SelectionAction(
kind: SelectionActionKind.move,
icon: LucideIcons.folderInput,
label: 'Move',
onTap: () => _moveOrCopySelected(context, selected, copy: false),
),
if (selected.length == 1)
SelectionAction(
kind: SelectionActionKind.rename,
icon: LucideIcons.filePen,
label: 'Rename',
onTap: () => _renameItem(selected.single),
),
SelectionAction(
kind: SelectionActionKind.sync,
icon: LucideIcons.hardDriveDownload,
label: allSynced ? 'Stop syncing to device' : 'Sync to device',
onTap: () => _toggleSyncSelected(context, sync, selected),
),
if (selected.length == 1)
SelectionAction(
kind: SelectionActionKind.details,
icon: LucideIcons.info,
label: 'Details',
onTap: () => DetailsSheet.show(context, selected.single),
),
];
return orderSelectionActions(
actions,
context.read<SettingsController>().selectionActionOrder,
);
}
Future<void> _renameItem(NextcloudItem item) async {
+14 -1
View File
@@ -11,6 +11,7 @@ import '../providers/item_operations.dart';
import '../providers/photos_controller.dart';
import '../providers/pick_controller.dart';
import '../providers/session_controller.dart';
import '../providers/settings_controller.dart';
import '../services/download_service.dart';
import '../theme/design_tokens.dart';
import '../widgets/details/details_sheet.dart';
@@ -113,6 +114,7 @@ class _PhotosViewState extends State<PhotosView> {
if (pick.isPicking) {
return [
SelectionAction(
kind: SelectionActionKind.favorite,
icon: LucideIcons.check,
label: 'Use ${selected.length} item(s)',
onTap: () => pick.confirmPick(selected),
@@ -123,13 +125,15 @@ class _PhotosViewState extends State<PhotosView> {
// uses for favorite/unfavorite (see `files_view.dart`) rather than a
// filled heart.
final allFavorited = selected.every((i) => i.isFavorite);
return [
final actions = [
SelectionAction(
kind: SelectionActionKind.favorite,
icon: allFavorited ? LucideIcons.starOff : LucideIcons.star,
label: allFavorited ? 'Remove from favorites' : 'Favorite',
onTap: () => _favoriteSelected(selected),
),
SelectionAction(
kind: SelectionActionKind.share,
icon: LucideIcons.share2,
label: 'Share',
onTap: () => selected.length == 1
@@ -137,32 +141,41 @@ class _PhotosViewState extends State<PhotosView> {
: _shareSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.download,
icon: LucideIcons.download,
label: 'Download',
onTap: () => _downloadSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.delete,
icon: LucideIcons.trash2,
label: 'Delete',
onTap: () => _confirmDeleteSelected(context, selected),
),
SelectionAction(
kind: SelectionActionKind.copy,
icon: LucideIcons.copy,
label: 'Copy',
onTap: () => _moveOrCopySelected(selected, copy: true),
),
SelectionAction(
kind: SelectionActionKind.move,
icon: LucideIcons.folderInput,
label: 'Move',
onTap: () => _moveOrCopySelected(selected, copy: false),
),
if (selected.length == 1)
SelectionAction(
kind: SelectionActionKind.details,
icon: LucideIcons.info,
label: 'Details',
onTap: () => DetailsSheet.show(context, selected.single),
),
];
return orderSelectionActions(
actions,
context.read<SettingsController>().selectionActionOrder,
);
}
/// Splits an already-filtered/sorted list into contiguous month runs