From 29dfce6599a4c80b706d973b525476736c65306e Mon Sep 17 00:00:00 2001 From: jld3103 Date: Thu, 20 Apr 2023 16:41:22 +0200 Subject: [PATCH] neon_files: Sort files --- packages/neon/neon/lib/l10n/en.arb | 4 + .../neon/neon/lib/l10n/localizations.dart | 24 ++ .../neon/neon/lib/l10n/localizations_en.dart | 12 + packages/neon/neon_files/lib/neon_files.dart | 2 + packages/neon/neon_files/lib/options.dart | 31 +++ packages/neon/neon_files/lib/sort/files.dart | 13 + .../neon_files/lib/widgets/browser_view.dart | 225 +++++++++--------- packages/neon/neon_files/pubspec.yaml | 2 + 8 files changed, 203 insertions(+), 110 deletions(-) create mode 100644 packages/neon/neon_files/lib/sort/files.dart diff --git a/packages/neon/neon/lib/l10n/en.arb b/packages/neon/neon/lib/l10n/en.arb index d15d03e6..f3893eb9 100644 --- a/packages/neon/neon/lib/l10n/en.arb +++ b/packages/neon/neon/lib/l10n/en.arb @@ -194,6 +194,10 @@ } } }, + "filesOptionsFilesSortPropertyName": "Name", + "filesOptionsFilesSortPropertyModifiedDate": "Last modified", + "filesOptionsFilesSortPropertySize": "Size", + "filesOptionsFilesSortOrder": "Sort order of files", "filesOptionsShowPreviews": "Show previews for files", "filesOptionsUploadQueueParallelism": "Upload queue parallelism", "filesOptionsDownloadQueueParallelism": "Download queue parallelism", diff --git a/packages/neon/neon/lib/l10n/localizations.dart b/packages/neon/neon/lib/l10n/localizations.dart index 71ceaab7..ee67d1dd 100644 --- a/packages/neon/neon/lib/l10n/localizations.dart +++ b/packages/neon/neon/lib/l10n/localizations.dart @@ -731,6 +731,30 @@ abstract class AppLocalizations { /// **'Are you sure you want to download a file that is bigger than {warningSize} ({actualSize})?'** String filesConfirmDownloadSizeWarning(String warningSize, String actualSize); + /// No description provided for @filesOptionsFilesSortPropertyName. + /// + /// In en, this message translates to: + /// **'Name'** + String get filesOptionsFilesSortPropertyName; + + /// No description provided for @filesOptionsFilesSortPropertyModifiedDate. + /// + /// In en, this message translates to: + /// **'Last modified'** + String get filesOptionsFilesSortPropertyModifiedDate; + + /// No description provided for @filesOptionsFilesSortPropertySize. + /// + /// In en, this message translates to: + /// **'Size'** + String get filesOptionsFilesSortPropertySize; + + /// No description provided for @filesOptionsFilesSortOrder. + /// + /// In en, this message translates to: + /// **'Sort order of files'** + String get filesOptionsFilesSortOrder; + /// No description provided for @filesOptionsShowPreviews. /// /// In en, this message translates to: diff --git a/packages/neon/neon/lib/l10n/localizations_en.dart b/packages/neon/neon/lib/l10n/localizations_en.dart index d120be57..141c7a38 100644 --- a/packages/neon/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/neon/lib/l10n/localizations_en.dart @@ -366,6 +366,18 @@ class AppLocalizationsEn extends AppLocalizations { return 'Are you sure you want to download a file that is bigger than $warningSize ($actualSize)?'; } + @override + String get filesOptionsFilesSortPropertyName => 'Name'; + + @override + String get filesOptionsFilesSortPropertyModifiedDate => 'Last modified'; + + @override + String get filesOptionsFilesSortPropertySize => 'Size'; + + @override + String get filesOptionsFilesSortOrder => 'Sort order of files'; + @override String get filesOptionsShowPreviews => 'Show previews for files'; diff --git a/packages/neon/neon_files/lib/neon_files.dart b/packages/neon/neon_files/lib/neon_files.dart index f7f66d44..238da87b 100644 --- a/packages/neon/neon_files/lib/neon_files.dart +++ b/packages/neon/neon_files/lib/neon_files.dart @@ -21,6 +21,7 @@ import 'package:provider/provider.dart'; import 'package:queue/queue.dart'; import 'package:rxdart/rxdart.dart'; import 'package:settings/settings.dart'; +import 'package:sort_box/sort_box.dart'; part 'blocs/browser.dart'; part 'blocs/files.dart'; @@ -31,6 +32,7 @@ part 'models/file_details.dart'; part 'options.dart'; part 'pages/details.dart'; part 'pages/main.dart'; +part 'sort/files.dart'; part 'utils/download_task.dart'; part 'utils/upload_task.dart'; part 'widgets/browser_view.dart'; diff --git a/packages/neon/neon_files/lib/options.dart b/packages/neon/neon_files/lib/options.dart index aa442e4a..15ab449f 100644 --- a/packages/neon/neon_files/lib/options.dart +++ b/packages/neon/neon_files/lib/options.dart @@ -6,6 +6,8 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions { generalCategory, ]; super.options = [ + filesSortPropertyOption, + filesSortBoxOrderOption, showPreviewsOption, uploadQueueParallelism, downloadQueueParallelism, @@ -18,6 +20,29 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions { name: (final context) => AppLocalizations.of(context).optionsCategoryGeneral, ); + late final filesSortPropertyOption = SelectOption( + storage: super.storage, + category: generalCategory, + key: 'files-sort-property', + label: (final context) => AppLocalizations.of(context).newsOptionsArticlesSortProperty, + defaultValue: BehaviorSubject.seeded(FilesSortProperty.name), + values: BehaviorSubject.seeded({ + FilesSortProperty.name: (final context) => AppLocalizations.of(context).filesOptionsFilesSortPropertyName, + FilesSortProperty.modifiedDate: (final context) => + AppLocalizations.of(context).filesOptionsFilesSortPropertyModifiedDate, + FilesSortProperty.size: (final context) => AppLocalizations.of(context).filesOptionsFilesSortPropertySize, + }), + ); + + late final filesSortBoxOrderOption = SelectOption( + storage: super.storage, + category: generalCategory, + key: 'files-sort-box-order', + label: (final context) => AppLocalizations.of(context).filesOptionsFilesSortOrder, + defaultValue: BehaviorSubject.seeded(SortBoxOrder.ascending), + values: BehaviorSubject.seeded(sortBoxOrderOptionValues), + ); + late final showPreviewsOption = ToggleOption( storage: super.storage, category: generalCategory, @@ -87,3 +112,9 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions { values: BehaviorSubject.seeded(_sizeWarningValues), ); } + +enum FilesSortProperty { + name, + modifiedDate, + size, +} diff --git a/packages/neon/neon_files/lib/sort/files.dart b/packages/neon/neon_files/lib/sort/files.dart new file mode 100644 index 00000000..1f65ac7c --- /dev/null +++ b/packages/neon/neon_files/lib/sort/files.dart @@ -0,0 +1,13 @@ +part of '../neon_files.dart'; + +final filesSortBox = SortBox( + { + FilesSortProperty.name: (final file) => file.name.toLowerCase(), + FilesSortProperty.modifiedDate: (final file) => file.lastModified?.millisecondsSinceEpoch ?? 0, + FilesSortProperty.size: (final file) => file.size ?? 0, + }, + { + FilesSortProperty.modifiedDate: Box(FilesSortProperty.name, SortBoxOrder.ascending), + FilesSortProperty.size: Box(FilesSortProperty.name, SortBoxOrder.ascending), + }, +); diff --git a/packages/neon/neon_files/lib/widgets/browser_view.dart b/packages/neon/neon_files/lib/widgets/browser_view.dart index 70421090..5c8a3f8d 100644 --- a/packages/neon/neon_files/lib/widgets/browser_view.dart +++ b/packages/neon/neon_files/lib/widgets/browser_view.dart @@ -71,128 +71,133 @@ class _FilesBrowserViewState extends State { child: const Icon(Icons.add), ) : null, - body: NeonListView( - scrollKey: 'files-${pathSnapshot.data!.join('/')}', - withFloatingActionButton: true, - items: [ - for (final uploadTask in files.data == null - ? [] - : uploadTasksSnapshot.data!.where( - (final task) => files.data! - .where((final file) => _pathMatchesFile(task.path, file.name)) - .isEmpty, - )) ...[ - StreamBuilder( - stream: uploadTask.progress, - builder: (final context, final uploadTaskProgressSnapshot) => - !uploadTaskProgressSnapshot.hasData - ? Container() - : _buildFile( - context: context, - details: FileDetails( - path: uploadTask.path, - isDirectory: false, - size: uploadTask.size, - etag: null, - mimeType: null, - lastModified: uploadTask.lastModified, - hasPreview: null, - isFavorite: null, + body: SortBoxBuilder( + sortBox: filesSortBox, + sortPropertyOption: widget.bloc.options.filesSortPropertyOption, + sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption, + input: files.data, + builder: (final context, final sorted) => NeonListView( + scrollKey: 'files-${pathSnapshot.data!.join('/')}', + withFloatingActionButton: true, + items: [ + for (final uploadTask in sorted == null + ? [] + : uploadTasksSnapshot.data!.where( + (final task) => + sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty, + )) ...[ + StreamBuilder( + stream: uploadTask.progress, + builder: (final context, final uploadTaskProgressSnapshot) => + !uploadTaskProgressSnapshot.hasData + ? Container() + : _buildFile( + context: context, + details: FileDetails( + path: uploadTask.path, + isDirectory: false, + size: uploadTask.size, + etag: null, + mimeType: null, + lastModified: uploadTask.lastModified, + hasPreview: null, + isFavorite: null, + ), + uploadProgress: uploadTaskProgressSnapshot.data, + downloadProgress: null, ), - uploadProgress: uploadTaskProgressSnapshot.data, - downloadProgress: null, - ), - ), - ], - if (files.data != null) ...[ - for (final file in files.data!) ...[ - if (!widget.onlyShowDirectories || file.isDirectory) ...[ - Builder( - builder: (final context) { - final matchingUploadTasks = uploadTasksSnapshot.data! - .where((final task) => _pathMatchesFile(task.path, file.name)); - final matchingDownloadTasks = downloadTasksSnapshot.data! - .where((final task) => _pathMatchesFile(task.path, file.name)); + ), + ], + if (sorted != null) ...[ + for (final file in sorted) ...[ + if (!widget.onlyShowDirectories || file.isDirectory) ...[ + Builder( + builder: (final context) { + final matchingUploadTasks = uploadTasksSnapshot.data! + .where((final task) => _pathMatchesFile(task.path, file.name)); + final matchingDownloadTasks = downloadTasksSnapshot.data! + .where((final task) => _pathMatchesFile(task.path, file.name)); - return StreamBuilder( - stream: matchingUploadTasks.isNotEmpty - ? matchingUploadTasks.first.progress - : Stream.value(null), - builder: (final context, final uploadTaskProgressSnapshot) => - StreamBuilder( - stream: matchingDownloadTasks.isNotEmpty - ? matchingDownloadTasks.first.progress + return StreamBuilder( + stream: matchingUploadTasks.isNotEmpty + ? matchingUploadTasks.first.progress : Stream.value(null), - builder: (final context, final downloadTaskProgressSnapshot) => _buildFile( - context: context, - details: FileDetails( - path: [...widget.bloc.path.value, file.name], - isDirectory: matchingUploadTasks.isEmpty && file.isDirectory, - size: matchingUploadTasks.isNotEmpty - ? matchingUploadTasks.first.size - : file.size!, - etag: matchingUploadTasks.isNotEmpty ? null : file.etag, - mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType, - lastModified: matchingUploadTasks.isNotEmpty - ? matchingUploadTasks.first.lastModified - : file.lastModified!, - hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview, - isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite, + builder: (final context, final uploadTaskProgressSnapshot) => + StreamBuilder( + stream: matchingDownloadTasks.isNotEmpty + ? matchingDownloadTasks.first.progress + : Stream.value(null), + builder: (final context, final downloadTaskProgressSnapshot) => _buildFile( + context: context, + details: FileDetails( + path: [...widget.bloc.path.value, file.name], + isDirectory: matchingUploadTasks.isEmpty && file.isDirectory, + size: matchingUploadTasks.isNotEmpty + ? matchingUploadTasks.first.size + : file.size!, + etag: matchingUploadTasks.isNotEmpty ? null : file.etag, + mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType, + lastModified: matchingUploadTasks.isNotEmpty + ? matchingUploadTasks.first.lastModified + : file.lastModified!, + hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview, + isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite, + ), + uploadProgress: uploadTaskProgressSnapshot.data, + downloadProgress: downloadTaskProgressSnapshot.data, ), - uploadProgress: uploadTaskProgressSnapshot.data, - downloadProgress: downloadTaskProgressSnapshot.data, ), - ), - ); - }, - ), + ); + }, + ), + ], ], ], ], - ], - isLoading: files.loading, - error: files.error, - onRefresh: widget.bloc.refresh, - builder: (final context, final widget) => widget, - topScrollingChildren: [ - Align( - alignment: Alignment.topLeft, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 10, - ), - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - SizedBox( - height: 40, - child: InkWell( - onTap: () { - widget.bloc.setPath([]); - }, - child: const Icon(Icons.house), - ), - ), - for (var i = 0; i < pathSnapshot.data!.length; i++) ...[ - InkWell( - onTap: () { - widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1)); - }, - child: Text(pathSnapshot.data![i]), + isLoading: files.loading, + error: files.error, + onRefresh: widget.bloc.refresh, + builder: (final context, final widget) => widget, + topScrollingChildren: [ + Align( + alignment: Alignment.topLeft, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, + ), + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + SizedBox( + height: 40, + child: InkWell( + onTap: () { + widget.bloc.setPath([]); + }, + child: const Icon(Icons.house), + ), ), - ], - ] - .intersperse( - const Icon( - Icons.keyboard_arrow_right, - size: 40, + for (var i = 0; i < pathSnapshot.data!.length; i++) ...[ + InkWell( + onTap: () { + widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1)); + }, + child: Text(pathSnapshot.data![i]), ), - ) - .toList(), + ], + ] + .intersperse( + const Icon( + Icons.keyboard_arrow_right, + size: 40, + ), + ) + .toList(), + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/packages/neon/neon_files/pubspec.yaml b/packages/neon/neon_files/pubspec.yaml index c28306f7..af544b7d 100644 --- a/packages/neon/neon_files/pubspec.yaml +++ b/packages/neon/neon_files/pubspec.yaml @@ -28,6 +28,8 @@ dependencies: rxdart: ^0.27.7 settings: path: ../../settings + sort_box: + path: ../../sort_box dev_dependencies: nit_picking: