From c4d38589a74322f8ae7ee171a6b7329742cf28c9 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 25 Aug 2023 17:46:51 +0200 Subject: [PATCH] feat(neon_files): enable hiding files starting with a '.' Signed-off-by: Nikolas Rimikis --- packages/neon/neon_files/lib/l10n/en.arb | 1 + .../neon_files/lib/l10n/localizations.dart | 6 + .../neon_files/lib/l10n/localizations_en.dart | 3 + packages/neon/neon_files/lib/options.dart | 10 + .../neon_files/lib/widgets/browser_view.dart | 196 +++++++++--------- packages/nextcloud/lib/src/webdav/file.dart | 11 +- packages/nextcloud/pubspec.yaml | 1 + 7 files changed, 127 insertions(+), 101 deletions(-) diff --git a/packages/neon/neon_files/lib/l10n/en.arb b/packages/neon/neon_files/lib/l10n/en.arb index e53da81a..07661702 100644 --- a/packages/neon/neon_files/lib/l10n/en.arb +++ b/packages/neon/neon_files/lib/l10n/en.arb @@ -77,6 +77,7 @@ "optionsFilesSortPropertyModifiedDate": "Last modified", "optionsFilesSortPropertySize": "Size", "optionsFilesSortOrder": "Sort order of files", + "optionsShowHiddenFiles": "Show hidden files", "optionsShowPreviews": "Show previews for files", "optionsUploadQueueParallelism": "Upload queue parallelism", "optionsDownloadQueueParallelism": "Download queue parallelism", diff --git a/packages/neon/neon_files/lib/l10n/localizations.dart b/packages/neon/neon_files/lib/l10n/localizations.dart index afd2ae2a..f0372a5e 100644 --- a/packages/neon/neon_files/lib/l10n/localizations.dart +++ b/packages/neon/neon_files/lib/l10n/localizations.dart @@ -305,6 +305,12 @@ abstract class AppLocalizations { /// **'Sort order of files'** String get optionsFilesSortOrder; + /// No description provided for @optionsShowHiddenFiles. + /// + /// In en, this message translates to: + /// **'Show hidden files'** + String get optionsShowHiddenFiles; + /// No description provided for @optionsShowPreviews. /// /// In en, this message translates to: diff --git a/packages/neon/neon_files/lib/l10n/localizations_en.dart b/packages/neon/neon_files/lib/l10n/localizations_en.dart index a0a8d7f3..622b0ae2 100644 --- a/packages/neon/neon_files/lib/l10n/localizations_en.dart +++ b/packages/neon/neon_files/lib/l10n/localizations_en.dart @@ -122,6 +122,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get optionsFilesSortOrder => 'Sort order of files'; + @override + String get optionsShowHiddenFiles => 'Show hidden files'; + @override String get optionsShowPreviews => 'Show previews for files'; diff --git a/packages/neon/neon_files/lib/options.dart b/packages/neon/neon_files/lib/options.dart index ebbe7803..29e264ea 100644 --- a/packages/neon/neon_files/lib/options.dart +++ b/packages/neon/neon_files/lib/options.dart @@ -8,6 +8,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions { super.options = [ filesSortPropertyOption, filesSortBoxOrderOption, + showHiddenFilesOption, showPreviewsOption, uploadQueueParallelism, downloadQueueParallelism, @@ -43,6 +44,14 @@ class FilesAppSpecificOptions extends NextcloudAppOptions { values: sortBoxOrderOptionValues, ); + late final showHiddenFilesOption = ToggleOption( + storage: super.storage, + category: generalCategory, + key: 'show-hidden-files', + label: (final context) => AppLocalizations.of(context).optionsShowHiddenFiles, + defaultValue: false, + ); + late final showPreviewsOption = ToggleOption( storage: super.storage, category: generalCategory, @@ -117,4 +126,5 @@ enum FilesSortProperty { name, modifiedDate, size, + isFolder, } diff --git a/packages/neon/neon_files/lib/widgets/browser_view.dart b/packages/neon/neon_files/lib/widgets/browser_view.dart index e05298a7..c0daa200 100644 --- a/packages/neon/neon_files/lib/widgets/browser_view.dart +++ b/packages/neon/neon_files/lib/widgets/browser_view.dart @@ -66,112 +66,116 @@ class _FilesBrowserViewState extends State { (FilesSortProperty.isFolder, SortBoxOrder.ascending), }, input: files.data, - builder: (final context, final sorted) => NeonListView( - scrollKey: 'files-${pathSnapshot.requireData.join('/')}', - withFloatingActionButton: true, - items: [ - for (final uploadTask in tasksSnapshot.requireData.whereType().where( - (final task) => - sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty, - )) ...[ - FileListTile( - bloc: widget.filesBloc, - browserBloc: widget.bloc, - details: FileDetails.fromUploadTask( - task: uploadTask, + builder: (final context, final sorted) => ValueListenableBuilder( + valueListenable: widget.bloc.options.showHiddenFilesOption, + builder: (final context, final showHiddenFiles, final _) => NeonListView( + scrollKey: 'files-${pathSnapshot.requireData.join('/')}', + withFloatingActionButton: true, + items: [ + for (final uploadTask in tasksSnapshot.requireData.whereType().where( + (final task) => + sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty, + )) ...[ + FileListTile( + bloc: widget.filesBloc, + browserBloc: widget.bloc, + details: FileDetails.fromUploadTask( + task: uploadTask, + ), + mode: widget.mode, ), - mode: widget.mode, - ), - ], - for (final file in sorted) ...[ - if (widget.mode != FilesBrowserMode.selectDirectory || file.isDirectory) ...[ - Builder( - builder: (final context) { - final matchingTask = tasksSnapshot.requireData - .firstWhereOrNull((final task) => _pathMatchesFile(task.path, file.name)); + ], + for (final file in sorted) ...[ + if ((widget.mode != FilesBrowserMode.selectDirectory || file.isDirectory) && + (!file.isHidden || showHiddenFiles)) ...[ + Builder( + builder: (final context) { + final matchingTask = tasksSnapshot.requireData + .firstWhereOrNull((final task) => _pathMatchesFile(task.path, file.name)); - final details = matchingTask != null - ? FileDetails.fromTask( - task: matchingTask, - file: file, - ) - : FileDetails.fromWebDav( - file: file, - path: widget.bloc.path.value, - ); + final details = matchingTask != null + ? FileDetails.fromTask( + task: matchingTask, + file: file, + ) + : FileDetails.fromWebDav( + file: file, + path: widget.bloc.path.value, + ); - return FileListTile( - bloc: widget.filesBloc, - browserBloc: widget.bloc, - details: details, - mode: widget.mode, - ); - }, - ), + return FileListTile( + bloc: widget.filesBloc, + browserBloc: widget.bloc, + details: details, + mode: widget.mode, + ); + }, + ), + ], ], ], - ], - isLoading: files.isLoading, - 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: [ - IconButton( - padding: EdgeInsets.zero, - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity, - vertical: VisualDensity.minimumDensity, - ), - tooltip: AppLocalizations.of(context).goToPath(''), - icon: const Icon( - Icons.house, - size: 30, - ), - onPressed: () { - widget.bloc.setPath([]); - }, - ), - for (var i = 0; i < pathSnapshot.requireData.length; i++) ...[ - Builder( - builder: (final context) { - final path = pathSnapshot.requireData.sublist(0, i + 1); - return Tooltip( - message: AppLocalizations.of(context).goToPath(path.join('/')), - excludeFromSemantics: true, - child: TextButton( - onPressed: () { - widget.bloc.setPath(path); - }, - child: Text( - pathSnapshot.requireData[i], - semanticsLabel: AppLocalizations.of(context).goToPath(path.join('/')), - ), - ), - ); + isLoading: files.isLoading, + 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: [ + IconButton( + padding: EdgeInsets.zero, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + tooltip: AppLocalizations.of(context).goToPath(''), + icon: const Icon( + Icons.house, + size: 30, + ), + onPressed: () { + widget.bloc.setPath([]); }, ), - ], - ] - .intersperse( - const Icon( - Icons.keyboard_arrow_right, - size: 30, + for (var i = 0; i < pathSnapshot.requireData.length; i++) ...[ + Builder( + builder: (final context) { + final path = pathSnapshot.requireData.sublist(0, i + 1); + return Tooltip( + message: AppLocalizations.of(context).goToPath(path.join('/')), + excludeFromSemantics: true, + child: TextButton( + onPressed: () { + widget.bloc.setPath(path); + }, + child: Text( + pathSnapshot.requireData[i], + semanticsLabel: AppLocalizations.of(context).goToPath(path.join('/')), + ), + ), + ); + }, ), - ) - .toList(), + ], + ] + .intersperse( + const Icon( + Icons.keyboard_arrow_right, + size: 30, + ), + ) + .toList(), + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/packages/nextcloud/lib/src/webdav/file.dart b/packages/nextcloud/lib/src/webdav/file.dart index a9cff423..a796270c 100644 --- a/packages/nextcloud/lib/src/webdav/file.dart +++ b/packages/nextcloud/lib/src/webdav/file.dart @@ -83,12 +83,13 @@ class WebDavFile { // normalised path (remove trailing slash) final end = path.endsWith('/') ? path.length - 1 : path.length; final segments = Uri.parse(path, 0, end).pathSegments; - if (segments.isNotEmpty) { - return segments.last; - } - return ''; + + return segments.lastOrNull ?? ''; }(); - /// Returns if the file is a directory + /// Whether the file is hidden. + late final bool isHidden = name.startsWith('.'); + + /// Whether the file is a directory late final bool isDirectory = (isCollection ?? false) || path.endsWith('/'); } diff --git a/packages/nextcloud/pubspec.yaml b/packages/nextcloud/pubspec.yaml index e5267070..fb3d93e9 100644 --- a/packages/nextcloud/pubspec.yaml +++ b/packages/nextcloud/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: built_collection: ^5.1.1 built_value: ^8.6.2 + collection: ^1.17.2 crypto: ^3.0.3 crypton: ^2.2.0 dynamite_runtime: