Browse Source

Merge pull request #266 from provokateurin/feature/sort-files

Sort files
pull/268/head
Kate 2 years ago committed by GitHub
parent
commit
f2287a07ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/neon/neon/lib/l10n/en.arb
  2. 24
      packages/neon/neon/lib/l10n/localizations.dart
  3. 12
      packages/neon/neon/lib/l10n/localizations_en.dart
  4. 2
      packages/neon/neon_files/lib/neon_files.dart
  5. 31
      packages/neon/neon_files/lib/options.dart
  6. 13
      packages/neon/neon_files/lib/sort/files.dart
  7. 225
      packages/neon/neon_files/lib/widgets/browser_view.dart
  8. 2
      packages/neon/neon_files/pubspec.yaml

4
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", "filesOptionsShowPreviews": "Show previews for files",
"filesOptionsUploadQueueParallelism": "Upload queue parallelism", "filesOptionsUploadQueueParallelism": "Upload queue parallelism",
"filesOptionsDownloadQueueParallelism": "Download queue parallelism", "filesOptionsDownloadQueueParallelism": "Download queue parallelism",

24
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})?'** /// **'Are you sure you want to download a file that is bigger than {warningSize} ({actualSize})?'**
String filesConfirmDownloadSizeWarning(String warningSize, String 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. /// No description provided for @filesOptionsShowPreviews.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

12
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)?'; 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 @override
String get filesOptionsShowPreviews => 'Show previews for files'; String get filesOptionsShowPreviews => 'Show previews for files';

2
packages/neon/neon_files/lib/neon_files.dart

@ -21,6 +21,7 @@ import 'package:provider/provider.dart';
import 'package:queue/queue.dart'; import 'package:queue/queue.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:settings/settings.dart'; import 'package:settings/settings.dart';
import 'package:sort_box/sort_box.dart';
part 'blocs/browser.dart'; part 'blocs/browser.dart';
part 'blocs/files.dart'; part 'blocs/files.dart';
@ -31,6 +32,7 @@ part 'models/file_details.dart';
part 'options.dart'; part 'options.dart';
part 'pages/details.dart'; part 'pages/details.dart';
part 'pages/main.dart'; part 'pages/main.dart';
part 'sort/files.dart';
part 'utils/download_task.dart'; part 'utils/download_task.dart';
part 'utils/upload_task.dart'; part 'utils/upload_task.dart';
part 'widgets/browser_view.dart'; part 'widgets/browser_view.dart';

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

@ -6,6 +6,8 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions {
generalCategory, generalCategory,
]; ];
super.options = [ super.options = [
filesSortPropertyOption,
filesSortBoxOrderOption,
showPreviewsOption, showPreviewsOption,
uploadQueueParallelism, uploadQueueParallelism,
downloadQueueParallelism, downloadQueueParallelism,
@ -18,6 +20,29 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions {
name: (final context) => AppLocalizations.of(context).optionsCategoryGeneral, name: (final context) => AppLocalizations.of(context).optionsCategoryGeneral,
); );
late final filesSortPropertyOption = SelectOption<FilesSortProperty>(
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<SortBoxOrder>(
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( late final showPreviewsOption = ToggleOption(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
@ -87,3 +112,9 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions {
values: BehaviorSubject.seeded(_sizeWarningValues), values: BehaviorSubject.seeded(_sizeWarningValues),
); );
} }
enum FilesSortProperty {
name,
modifiedDate,
size,
}

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

@ -0,0 +1,13 @@
part of '../neon_files.dart';
final filesSortBox = SortBox<FilesSortProperty, WebDavFile>(
{
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),
},
);

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

@ -71,128 +71,133 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
child: const Icon(Icons.add), child: const Icon(Icons.add),
) )
: null, : null,
body: NeonListView<Widget>( body: SortBoxBuilder<FilesSortProperty, WebDavFile>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}', sortBox: filesSortBox,
withFloatingActionButton: true, sortPropertyOption: widget.bloc.options.filesSortPropertyOption,
items: [ sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption,
for (final uploadTask in files.data == null input: files.data,
? <UploadTask>[] builder: (final context, final sorted) => NeonListView<Widget>(
: uploadTasksSnapshot.data!.where( scrollKey: 'files-${pathSnapshot.data!.join('/')}',
(final task) => files.data! withFloatingActionButton: true,
.where((final file) => _pathMatchesFile(task.path, file.name)) items: [
.isEmpty, for (final uploadTask in sorted == null
)) ...[ ? <UploadTask>[]
StreamBuilder<int>( : uploadTasksSnapshot.data!.where(
stream: uploadTask.progress, (final task) =>
builder: (final context, final uploadTaskProgressSnapshot) => sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty,
!uploadTaskProgressSnapshot.hasData )) ...[
? Container() StreamBuilder<int>(
: _buildFile( stream: uploadTask.progress,
context: context, builder: (final context, final uploadTaskProgressSnapshot) =>
details: FileDetails( !uploadTaskProgressSnapshot.hasData
path: uploadTask.path, ? Container()
isDirectory: false, : _buildFile(
size: uploadTask.size, context: context,
etag: null, details: FileDetails(
mimeType: null, path: uploadTask.path,
lastModified: uploadTask.lastModified, isDirectory: false,
hasPreview: null, size: uploadTask.size,
isFavorite: null, etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: null,
), ),
uploadProgress: uploadTaskProgressSnapshot.data, ),
downloadProgress: null, ],
), if (sorted != null) ...[
), for (final file in sorted) ...[
], if (!widget.onlyShowDirectories || file.isDirectory) ...[
if (files.data != null) ...[ Builder(
for (final file in files.data!) ...[ builder: (final context) {
if (!widget.onlyShowDirectories || file.isDirectory) ...[ final matchingUploadTasks = uploadTasksSnapshot.data!
Builder( .where((final task) => _pathMatchesFile(task.path, file.name));
builder: (final context) { final matchingDownloadTasks = downloadTasksSnapshot.data!
final matchingUploadTasks = uploadTasksSnapshot.data! .where((final task) => _pathMatchesFile(task.path, file.name));
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
return StreamBuilder<int?>( return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress ? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
: Stream.value(null), : Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) => _buildFile( builder: (final context, final uploadTaskProgressSnapshot) =>
context: context, StreamBuilder<int?>(
details: FileDetails( stream: matchingDownloadTasks.isNotEmpty
path: [...widget.bloc.path.value, file.name], ? matchingDownloadTasks.first.progress
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory, : Stream.value(null),
size: matchingUploadTasks.isNotEmpty builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
? matchingUploadTasks.first.size context: context,
: file.size!, details: FileDetails(
etag: matchingUploadTasks.isNotEmpty ? null : file.etag, path: [...widget.bloc.path.value, file.name],
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType, isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
lastModified: matchingUploadTasks.isNotEmpty size: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified ? matchingUploadTasks.first.size
: file.lastModified!, : file.size!,
hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview, etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite, 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,
isLoading: files.loading, error: files.error,
error: files.error, onRefresh: widget.bloc.refresh,
onRefresh: widget.bloc.refresh, builder: (final context, final widget) => widget,
builder: (final context, final widget) => widget, topScrollingChildren: [
topScrollingChildren: [ Align(
Align( alignment: Alignment.topLeft,
alignment: Alignment.topLeft, child: Container(
child: Container( margin: const EdgeInsets.symmetric(
margin: const EdgeInsets.symmetric( horizontal: 10,
horizontal: 10, ),
), child: Wrap(
child: Wrap( crossAxisAlignment: WrapCrossAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center, children: <Widget>[
children: <Widget>[ SizedBox(
SizedBox( height: 40,
height: 40, child: InkWell(
child: InkWell( onTap: () {
onTap: () { widget.bloc.setPath([]);
widget.bloc.setPath([]); },
}, child: const Icon(Icons.house),
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]),
), ),
], for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
] InkWell(
.intersperse( onTap: () {
const Icon( widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
Icons.keyboard_arrow_right, },
size: 40, child: Text(pathSnapshot.data![i]),
), ),
) ],
.toList(), ]
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
),
)
.toList(),
),
), ),
), ),
), ],
], ),
), ),
), ),
), ),

2
packages/neon/neon_files/pubspec.yaml

@ -28,6 +28,8 @@ dependencies:
rxdart: ^0.27.7 rxdart: ^0.27.7
settings: settings:
path: ../../settings path: ../../settings
sort_box:
path: ../../sort_box
dev_dependencies: dev_dependencies:
nit_picking: nit_picking:

Loading…
Cancel
Save