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:
@@ -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),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user