Browse Source

Merge pull request #604 from nextcloud/feat/neon_files/sort_folders

Feat/neon files/sort folders
pull/688/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
d37dc793f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      packages/neon/neon_files/lib/dialogs/choose_folder.dart
  2. 1
      packages/neon/neon_files/lib/l10n/en.arb
  3. 6
      packages/neon/neon_files/lib/l10n/localizations.dart
  4. 3
      packages/neon/neon_files/lib/l10n/localizations_en.dart
  5. 10
      packages/neon/neon_files/lib/options.dart
  6. 15
      packages/neon/neon_files/lib/pages/main.dart
  7. 1
      packages/neon/neon_files/lib/sort/files.dart
  8. 46
      packages/neon/neon_files/lib/widgets/browser_view.dart
  9. 40
      packages/neon/neon_files/lib/widgets/file_list_tile.dart
  10. 11
      packages/nextcloud/lib/src/webdav/file.dart
  11. 1
      packages/nextcloud/pubspec.yaml

3
packages/neon/neon_files/lib/dialogs/choose_folder.dart

@ -25,8 +25,7 @@ class FilesChooseFolderDialog extends StatelessWidget {
child: FilesBrowserView( child: FilesBrowserView(
bloc: bloc, bloc: bloc,
filesBloc: filesBloc, filesBloc: filesBloc,
enableFileActions: false, mode: FilesBrowserMode.selectDirectory,
onlyShowDirectories: true,
), ),
), ),
StreamBuilder<List<String>>( StreamBuilder<List<String>>(

1
packages/neon/neon_files/lib/l10n/en.arb

@ -77,6 +77,7 @@
"optionsFilesSortPropertyModifiedDate": "Last modified", "optionsFilesSortPropertyModifiedDate": "Last modified",
"optionsFilesSortPropertySize": "Size", "optionsFilesSortPropertySize": "Size",
"optionsFilesSortOrder": "Sort order of files", "optionsFilesSortOrder": "Sort order of files",
"optionsShowHiddenFiles": "Show hidden files",
"optionsShowPreviews": "Show previews for files", "optionsShowPreviews": "Show previews for files",
"optionsUploadQueueParallelism": "Upload queue parallelism", "optionsUploadQueueParallelism": "Upload queue parallelism",
"optionsDownloadQueueParallelism": "Download queue parallelism", "optionsDownloadQueueParallelism": "Download queue parallelism",

6
packages/neon/neon_files/lib/l10n/localizations.dart

@ -305,6 +305,12 @@ abstract class AppLocalizations {
/// **'Sort order of files'** /// **'Sort order of files'**
String get optionsFilesSortOrder; 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. /// No description provided for @optionsShowPreviews.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

3
packages/neon/neon_files/lib/l10n/localizations_en.dart

@ -122,6 +122,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get optionsFilesSortOrder => 'Sort order of files'; String get optionsFilesSortOrder => 'Sort order of files';
@override
String get optionsShowHiddenFiles => 'Show hidden files';
@override @override
String get optionsShowPreviews => 'Show previews for files'; String get optionsShowPreviews => 'Show previews for files';

10
packages/neon/neon_files/lib/options.dart

@ -8,6 +8,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
super.options = [ super.options = [
filesSortPropertyOption, filesSortPropertyOption,
filesSortBoxOrderOption, filesSortBoxOrderOption,
showHiddenFilesOption,
showPreviewsOption, showPreviewsOption,
uploadQueueParallelism, uploadQueueParallelism,
downloadQueueParallelism, downloadQueueParallelism,
@ -43,6 +44,14 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
values: sortBoxOrderOptionValues, 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( late final showPreviewsOption = ToggleOption(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
@ -132,4 +141,5 @@ enum FilesSortProperty {
name, name,
modifiedDate, modifiedDate,
size, size,
isFolder,
} }

15
packages/neon/neon_files/lib/pages/main.dart

@ -27,21 +27,6 @@ class _FilesMainPageState extends State<FilesMainPage> {
body: FilesBrowserView( body: FilesBrowserView(
bloc: bloc.browser, bloc: bloc.browser,
filesBloc: bloc, filesBloc: bloc,
onPickFile: (final details) async {
final sizeWarning = bloc.options.downloadSizeWarning.value;
if (sizeWarning != null && details.size != null && details.size! > sizeWarning) {
if (!(await showConfirmationDialog(
context,
AppLocalizations.of(context).downloadConfirmSizeWarning(
filesize(sizeWarning),
filesize(details.size),
),
))) {
return;
}
}
bloc.openFile(details.path, details.etag!, details.mimeType);
},
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () async {

1
packages/neon/neon_files/lib/sort/files.dart

@ -5,6 +5,7 @@ final filesSortBox = SortBox<FilesSortProperty, WebDavFile>(
FilesSortProperty.name: (final file) => file.name.toLowerCase(), FilesSortProperty.name: (final file) => file.name.toLowerCase(),
FilesSortProperty.modifiedDate: (final file) => file.lastModified?.millisecondsSinceEpoch ?? 0, FilesSortProperty.modifiedDate: (final file) => file.lastModified?.millisecondsSinceEpoch ?? 0,
FilesSortProperty.size: (final file) => file.size ?? 0, FilesSortProperty.size: (final file) => file.size ?? 0,
FilesSortProperty.isFolder: (final file) => file.isDirectory ? 0 : 1,
}, },
{ {
FilesSortProperty.modifiedDate: { FilesSortProperty.modifiedDate: {

46
packages/neon/neon_files/lib/widgets/browser_view.dart

@ -1,21 +1,30 @@
part of '../neon_files.dart'; part of '../neon_files.dart';
/// Mode to operate the [FilesBrowserView] in.
enum FilesBrowserMode {
/// Default file browser mode.
///
/// When a file is selecteed it will be opened or downloaded.
browser,
/// Select directory.
selectDirectory,
/// Don't show file actions.
noActions,
}
class FilesBrowserView extends StatefulWidget { class FilesBrowserView extends StatefulWidget {
const FilesBrowserView({ const FilesBrowserView({
required this.bloc, required this.bloc,
required this.filesBloc, required this.filesBloc,
this.onPickFile, this.mode = FilesBrowserMode.browser,
this.enableFileActions = true,
this.onlyShowDirectories = false,
super.key, super.key,
// ignore: prefer_asserts_with_message });
}) : assert((onPickFile == null) == onlyShowDirectories);
final FilesBrowserBloc bloc; final FilesBrowserBloc bloc;
final FilesBloc filesBloc; final FilesBloc filesBloc;
final Function(FileDetails)? onPickFile; final FilesBrowserMode mode;
final bool enableFileActions;
final bool onlyShowDirectories;
@override @override
State<FilesBrowserView> createState() => _FilesBrowserViewState(); State<FilesBrowserView> createState() => _FilesBrowserViewState();
@ -24,11 +33,11 @@ class FilesBrowserView extends StatefulWidget {
class _FilesBrowserViewState extends State<FilesBrowserView> { class _FilesBrowserViewState extends State<FilesBrowserView> {
@override @override
void initState() { void initState() {
super.initState();
widget.bloc.errors.listen((final error) { widget.bloc.errors.listen((final error) {
NeonException.showSnackbar(context, error); NeonException.showSnackbar(context, error);
}); });
super.initState();
} }
@override @override
@ -53,8 +62,13 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
sortBox: filesSortBox, sortBox: filesSortBox,
sortPropertyOption: widget.bloc.options.filesSortPropertyOption, sortPropertyOption: widget.bloc.options.filesSortPropertyOption,
sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption, sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption,
presort: const {
(FilesSortProperty.isFolder, SortBoxOrder.ascending),
},
input: files.data, input: files.data,
builder: (final context, final sorted) => NeonListView<Widget>( builder: (final context, final sorted) => ValueListenableBuilder(
valueListenable: widget.bloc.options.showHiddenFilesOption,
builder: (final context, final showHiddenFiles, final _) => NeonListView<Widget>(
scrollKey: 'files-${pathSnapshot.requireData.join('/')}', scrollKey: 'files-${pathSnapshot.requireData.join('/')}',
withFloatingActionButton: true, withFloatingActionButton: true,
items: [ items: [
@ -68,12 +82,12 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
details: FileDetails.fromUploadTask( details: FileDetails.fromUploadTask(
task: uploadTask, task: uploadTask,
), ),
enableFileActions: widget.enableFileActions, mode: widget.mode,
onPickFile: widget.onPickFile,
), ),
], ],
for (final file in sorted) ...[ for (final file in sorted) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[ if ((widget.mode != FilesBrowserMode.selectDirectory || file.isDirectory) &&
(!file.isHidden || showHiddenFiles)) ...[
Builder( Builder(
builder: (final context) { builder: (final context) {
final matchingTask = tasksSnapshot.requireData final matchingTask = tasksSnapshot.requireData
@ -93,8 +107,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
bloc: widget.filesBloc, bloc: widget.filesBloc,
browserBloc: widget.bloc, browserBloc: widget.bloc,
details: details, details: details,
enableFileActions: widget.enableFileActions, mode: widget.mode,
onPickFile: widget.onPickFile,
); );
}, },
), ),
@ -167,6 +180,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
), ),
), ),
), ),
),
); );
bool _pathMatchesFile(final List<String> path, final String name) => const ListEquality().equals( bool _pathMatchesFile(final List<String> path, final String name) => const ListEquality().equals(

40
packages/neon/neon_files/lib/widgets/file_list_tile.dart

@ -1,7 +1,9 @@
import 'package:filesize/filesize.dart'; import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart'; import 'package:neon/widgets.dart';
import 'package:neon_files/l10n/localizations.dart';
import 'package:neon_files/neon_files.dart'; import 'package:neon_files/neon_files.dart';
import 'package:neon_files/widgets/actions.dart'; import 'package:neon_files/widgets/actions.dart';
@ -10,32 +12,39 @@ class FileListTile extends StatelessWidget {
required this.bloc, required this.bloc,
required this.browserBloc, required this.browserBloc,
required this.details, required this.details,
this.enableFileActions = true, this.mode = FilesBrowserMode.browser,
this.onPickFile,
super.key, super.key,
}); });
final FilesBloc bloc; final FilesBloc bloc;
final FilesBrowserBloc browserBloc; final FilesBrowserBloc browserBloc;
final FileDetails details; final FileDetails details;
final bool enableFileActions; final FilesBrowserMode mode;
final Function(FileDetails)? onPickFile;
@override Future<void> _onTap(final BuildContext context, final FileDetails details) async {
Widget build(final BuildContext context) {
// When the ETag is null it means we are uploading this file right now
final onTap = details.isDirectory || details.etag != null
? () {
if (details.isDirectory) { if (details.isDirectory) {
browserBloc.setPath(details.path); browserBloc.setPath(details.path);
} else { } else if (mode == FilesBrowserMode.browser) {
onPickFile?.call(details); final sizeWarning = bloc.options.downloadSizeWarning.value;
if (sizeWarning != null && details.size != null && details.size! > sizeWarning) {
if (!(await showConfirmationDialog(
context,
AppLocalizations.of(context).downloadConfirmSizeWarning(
filesize(sizeWarning),
filesize(details.size),
),
))) {
return;
}
}
bloc.openFile(details.path, details.etag!, details.mimeType);
} }
} }
: null;
return ListTile( @override
onTap: onTap, Widget build(final BuildContext context) => ListTile(
// When the ETag is null it means we are uploading this file right now
onTap: details.isDirectory || details.etag != null ? () async => _onTap(context, details) : null,
title: Text( title: Text(
details.name, details.name,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -63,14 +72,13 @@ class FileListTile extends StatelessWidget {
details: details, details: details,
bloc: bloc, bloc: bloc,
), ),
trailing: !details.hasTask && enableFileActions trailing: !details.hasTask && mode != FilesBrowserMode.noActions
? FileActions(details: details) ? FileActions(details: details)
: const SizedBox.square( : const SizedBox.square(
dimension: 48, dimension: 48,
), ),
); );
} }
}
class _FileIcon extends StatelessWidget { class _FileIcon extends StatelessWidget {
const _FileIcon({ const _FileIcon({

11
packages/nextcloud/lib/src/webdav/file.dart

@ -83,12 +83,13 @@ class WebDavFile {
// normalised path (remove trailing slash) // normalised path (remove trailing slash)
final end = path.endsWith('/') ? path.length - 1 : path.length; final end = path.endsWith('/') ? path.length - 1 : path.length;
final segments = Uri.parse(path, 0, end).pathSegments; final segments = Uri.parse(path, 0, end).pathSegments;
if (segments.isNotEmpty) {
return segments.last; return segments.lastOrNull ?? '';
}
return '';
}(); }();
/// 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('/'); late final bool isDirectory = (isCollection ?? false) || path.endsWith('/');
} }

1
packages/nextcloud/pubspec.yaml

@ -8,6 +8,7 @@ environment:
dependencies: dependencies:
built_collection: ^5.1.1 built_collection: ^5.1.1
built_value: ^8.6.2 built_value: ^8.6.2
collection: ^1.17.2
crypto: ^3.0.3 crypto: ^3.0.3
crypton: ^2.2.0 crypton: ^2.2.0
dynamite_runtime: dynamite_runtime:

Loading…
Cancel
Save