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
+22 -15
View File
@@ -11,6 +11,11 @@ class NooTabOrderRow extends StatelessWidget {
final bool pinned;
final VoidCallback? onTogglePin;
/// False hides the trailing pin toggle entirely - for reorder-only lists
/// (Settings' Action bar section) that have no "pinned" concept, just an
/// order.
final bool showPin;
/// This row's index inside its `ReorderableListView` - when set, only the
/// grip icon starts a drag (`ReorderableDragStartListener` wraps just the
/// icon, not the whole row), so a vertical drag/scroll starting anywhere
@@ -25,6 +30,7 @@ class NooTabOrderRow extends StatelessWidget {
required this.label,
this.pinned = false,
this.onTogglePin,
this.showPin = true,
this.dragIndex,
});
@@ -50,23 +56,24 @@ class NooTabOrderRow extends StatelessWidget {
style: NooText.bodyL.copyWith(height: 1, color: colors.fg1),
),
),
GestureDetector(
onTap: onTogglePin,
child: Container(
width: 36,
height: 36,
alignment: Alignment.center,
decoration: BoxDecoration(
color: pinned ? colors.accentSoft : colors.surface2,
shape: BoxShape.circle,
),
child: Icon(
pinned ? LucideIcons.pin : LucideIcons.pinOff,
size: 16,
color: pinned ? colors.accentText : colors.fg3,
if (showPin)
GestureDetector(
onTap: onTogglePin,
child: Container(
width: 36,
height: 36,
alignment: Alignment.center,
decoration: BoxDecoration(
color: pinned ? colors.accentSoft : colors.surface2,
shape: BoxShape.circle,
),
child: Icon(
pinned ? LucideIcons.pin : LucideIcons.pinOff,
size: 16,
color: pinned ? colors.accentText : colors.fg3,
),
),
),
),
],
),
);
@@ -0,0 +1,132 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/selection_action.dart';
import '../../providers/settings_controller.dart';
import '../../theme/design_tokens.dart';
import '../noo/lists/noo_tab_order_row.dart';
import '../noo/noo_layout.dart';
/// Settings section: the priority order bulk actions (favorite, share,
/// download, ...) show in on the Files/Photos/Favorites multi-select
/// action bar. Every action stays available everywhere - this only decides
/// which ones land in `NooSelectionBar`'s fixed inline slots (3 mobile / 4
/// desktop, see its own doc comment) vs. behind its trailing "More" -
/// there's no pin/hide concept here the way Tabs has, so this is just a
/// reorder list, not a whole section of its own option rows.
class SettingsActionBarSection extends StatelessWidget {
const SettingsActionBarSection({super.key});
static const _subtitle =
'Drag to reorder - the first ones shown appear directly in the bar, the rest sit behind "More"';
void _handleReorder(SettingsController settings, int oldIndex, int newIndex) {
final order = List<SelectionActionKind>.from(settings.selectionActionOrder);
final kind = order.removeAt(oldIndex);
order.insert(newIndex, kind);
settings.setSelectionActionOrder(order);
}
@override
Widget build(BuildContext context) {
final settings = context.watch<SettingsController>();
final desktop = NooLayout.isDesktop(context);
final orderList = _ActionOrderList(
order: settings.selectionActionOrder,
onReorder: (oldIndex, newIndex) =>
_handleReorder(settings, oldIndex, newIndex),
);
final colors = context.nooColors;
if (!desktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
'Action bar',
style: NooText.label.copyWith(color: colors.fg2),
),
),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
_subtitle,
style: NooText.meta.copyWith(color: colors.fg3),
),
),
const SizedBox(height: 8),
orderList,
],
);
}
return Container(
decoration: BoxDecoration(
color: colors.surface,
border: Border.all(color: colors.line),
borderRadius: BorderRadius.circular(NooRadii.card),
),
padding: const EdgeInsets.all(NooSpace.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Action bar',
style: NooText.cardTitle.copyWith(color: colors.fg1),
),
const SizedBox(height: 4),
Text(_subtitle, style: NooText.meta.copyWith(color: colors.fg3)),
const SizedBox(height: NooSpace.md),
orderList,
],
),
);
}
}
/// The reorderable [NooTabOrderRow] stack for [SelectionActionKind]s - same
/// radius-20, line-divided card `settings_tabs.dart`'s `_TabOrderList`
/// draws, minus the pin toggle (`showPin: false`): there's nothing to
/// show/hide here, only an order.
class _ActionOrderList extends StatelessWidget {
final List<SelectionActionKind> order;
final void Function(int oldIndex, int newIndex) onReorder;
const _ActionOrderList({required this.order, required this.onReorder});
@override
Widget build(BuildContext context) {
final colors = context.nooColors;
final list = ReorderableListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
buildDefaultDragHandles: false,
itemCount: order.length,
onReorderItem: onReorder,
itemBuilder: (context, index) {
final kind = order[index];
return Padding(
key: ValueKey(kind),
padding: EdgeInsets.only(top: index > 0 ? 1 : 0),
child: NooTabOrderRow(
icon: kind.icon,
label: kind.label,
showPin: false,
dragIndex: index,
),
);
},
);
return ClipRRect(
borderRadius: BorderRadius.circular(NooRadii.card),
child: DecoratedBox(
decoration: BoxDecoration(color: colors.line),
child: list,
),
);
}
}