import 'package:flutter/material.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:provider/provider.dart'; import '../providers/files_controller.dart'; import '../theme/design_tokens.dart'; import 'noo/core/noo_chip.dart'; import 'noo/core/noo_segmented_control.dart'; import 'filter_mode_rows.dart'; import 'noo/lists/noo_grouped_list.dart'; import 'noo/lists/noo_settings_row.dart'; import 'noo/overlays/noo_sheet.dart'; import 'sort_menu_button.dart' show sortFieldLabel; String _typeFilterLabel(FilesTypeFilter filter) { switch (filter) { case FilesTypeFilter.all: return 'Filters'; case FilesTypeFilter.filesOnly: return 'Files only'; case FilesTypeFilter.foldersOnly: return 'Folders only'; } } /// The Files tab's sort/filter/view-mode row (Noo Design System project's /// Files screen: a sort chip, a filter chip, a spacer, then the list/grid /// segmented toggle) - shared with the Offline tab, which reads and writes /// the exact same [FilesController] prefs. [folderPath] picks which /// folder's sort applies. [showStorageScope] is off for Offline - a local /// mirror has no cloud/external distinction. /// /// Hidden-files and storage-scope, which have no home in the mockup's /// simple two-chip row, live inside the filter chip's sheet instead of as /// their own row controls. /// /// A plain `Row`, not a horizontally-scrolling one - it used to wrap in a /// `SingleChildScrollView`, which gives its child unbounded width, silently /// breaking the trailing `Spacer` (a flex child needs a bounded width to /// size against) and leaving the List/Grid toggle sitting right after the /// filter chip instead of pinned to the row's right edge. The two chips /// plus an icon-only toggle never come close to needing to scroll anyway. class FilesControlsRow extends StatelessWidget { final String folderPath; final bool showStorageScope; const FilesControlsRow({ super.key, required this.folderPath, this.showStorageScope = true, }); @override Widget build(BuildContext context) { final files = context.watch(); final filtersActive = files.filesTypeFilter != FilesTypeFilter.all || files.hiddenFilter != HiddenFilesFilter.hide || (showStorageScope && files.storageScope != StorageScope.cloud); return SizedBox( height: 44, child: Row( children: [ NooChip( icon: files.sortAscendingFor(folderPath) ? LucideIcons.arrowUp : LucideIcons.arrowDown, onTap: () => _showSortSheet(context, files), child: Text(sortFieldLabel(files.sortFieldFor(folderPath))), ), const SizedBox(width: 8), NooChip( icon: LucideIcons.filter, trailing: NooChipTrailing.menu, selected: filtersActive, onTap: () => _showFilterSheet(context, files), child: Text(_typeFilterLabel(files.filesTypeFilter)), ), const Spacer(), NooSegmentedControl( iconOnly: true, value: files.isGridView, onChanged: files.setGridView, options: const [ NooSegmentOption(value: false, icon: LucideIcons.list), NooSegmentOption(value: true, icon: LucideIcons.grid), ], ), ], ), ); } void _showSortSheet(BuildContext context, FilesController files) { showNooSheet( context, children: [ // `showNooSheet`'s `children` are built once, up front - a bare // checkmark/value here would freeze at whatever it was when the // sheet opened, since tapping a row calls `files.set...` and // notifies the controller, not this already-built widget tree. // `ListenableBuilder` re-runs its `builder` on every // `FilesController.notifyListeners()` instead, so the selection // updates live - see the identical fix in `_showFilterSheet`. ListenableBuilder( listenable: files, builder: (context, _) => Column( mainAxisSize: MainAxisSize.min, children: [ NooSegmentedControl( fill: true, onSurface: true, value: files.sortAscendingFor(folderPath), onChanged: (asc) { if (asc != files.sortAscendingFor(folderPath)) { files.toggleSortOrderFor(folderPath); } }, options: const [ NooSegmentOption( value: true, icon: LucideIcons.arrowUp, label: 'Ascending', ), NooSegmentOption( value: false, icon: LucideIcons.arrowDown, label: 'Descending', ), ], ), const SizedBox(height: 22), NooGroupedList( children: [ for (final field in FileSortField.values) NooSettingsRow( label: Text(sortFieldLabel(field)), trailing: field == files.sortFieldFor(folderPath) ? Icon( LucideIcons.check, size: 18, color: context.nooColors.accentText, ) : null, onTap: () { files.setSortFieldFor(folderPath, field); Navigator.pop(context); }, ), ], ), ], ), ), ], ); } void _showFilterSheet(BuildContext context, FilesController files) { showNooSheet( context, children: [ // See `_showSortSheet`'s comment: without this, neither the type // filter's selected segment nor the toggles below would move until // the sheet is closed and reopened. ListenableBuilder( listenable: files, builder: (context, _) => Column( mainAxisSize: MainAxisSize.min, children: [ // Same "icon always, label only when selected" pill as // Photos' own type filter below - not a checkmark list, so // the two screens' filter sheets look and behave the same. NooSegmentedControl( fill: true, onSurface: true, labelOnlySelected: true, value: files.filesTypeFilter, onChanged: files.setFilesTypeFilter, options: const [ NooSegmentOption( value: FilesTypeFilter.all, icon: LucideIcons.layoutGrid, label: 'All', ), NooSegmentOption( value: FilesTypeFilter.filesOnly, icon: LucideIcons.file, label: 'Files', ), NooSegmentOption( value: FilesTypeFilter.foldersOnly, icon: LucideIcons.folder, label: 'Folders', ), ], ), const SizedBox(height: 22), HiddenFilesFilterRow( value: files.hiddenFilter, onChanged: files.setHiddenFilter, ), if (showStorageScope) ...[ const SizedBox(height: 22), StorageScopeRow( value: files.storageScope, onChanged: files.setStorageScope, ), ], ], ), ), ], ); } }