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",
"filesOptionsUploadQueueParallelism": "Upload 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})?'**
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:

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)?';
}
@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';

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: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';

31
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<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(
storage: super.storage,
category: generalCategory,
@ -87,3 +112,9 @@ class FilesAppSpecificOptions extends NextcloudAppSpecificOptions {
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),
)
: null,
body: NeonListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: [
for (final uploadTask in files.data == null
? <UploadTask>[]
: uploadTasksSnapshot.data!.where(
(final task) => files.data!
.where((final file) => _pathMatchesFile(task.path, file.name))
.isEmpty,
)) ...[
StreamBuilder<int>(
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<FilesSortProperty, WebDavFile>(
sortBox: filesSortBox,
sortPropertyOption: widget.bloc.options.filesSortPropertyOption,
sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption,
input: files.data,
builder: (final context, final sorted) => NeonListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: [
for (final uploadTask in sorted == null
? <UploadTask>[]
: uploadTasksSnapshot.data!.where(
(final task) =>
sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty,
)) ...[
StreamBuilder<int>(
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<int?>(
stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
return StreamBuilder<int?>(
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<int?>(
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: <Widget>[
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: <Widget>[
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(),
),
),
),
),
],
],
),
),
),
),

2
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:

Loading…
Cancel
Save