From 4bac0c5eecf13c0e41ff263b042280e585960fc9 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 18 Aug 2023 09:27:25 +0200 Subject: [PATCH 1/5] refactor(neon,neon_files,neon_notes): improve nullability of SortBoxBuilder Signed-off-by: Nikolas Rimikis --- .../lib/src/sort_box/sort_box_builder.dart | 29 +++++++--- .../neon_files/lib/widgets/browser_view.dart | 53 +++++++++---------- .../neon_notes/lib/widgets/notes_view.dart | 4 +- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart index 276338fb..6c256e78 100644 --- a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart +++ b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart @@ -2,26 +2,39 @@ import 'package:flutter/widgets.dart'; import 'package:neon/src/settings/models/select_option.dart'; import 'package:sort_box/sort_box.dart'; +/// Signature for a function that creates a widget for a given sorted list. +/// +/// Used by [SortBoxBuilder] to display a sorted list of items. +typedef SortBoxWidgetBuilder = Widget Function(BuildContext context, List sorted); + +/// Sorted list builder. +/// +/// Used together with a [SortBox] to sort a given list. class SortBoxBuilder extends StatelessWidget { - const SortBoxBuilder({ + SortBoxBuilder({ required this.sortBox, required this.sortPropertyOption, required this.sortBoxOrderOption, - required this.input, + required final List? input, required this.builder, super.key, - }); + }) : input = input ?? []; final SortBox sortBox; final SelectOption sortPropertyOption; final SelectOption sortBoxOrderOption; - final List? input; - final Widget Function(BuildContext, List?) builder; + + /// Input list to sort. + final List input; + + /// Child builder using the sorted list. + final SortBoxWidgetBuilder builder; @override Widget build(final BuildContext context) { - if (input == null || (input?.isEmpty ?? false)) { - return builder(context, null); + if (input.length <= 1) { + // input is already sorted. + return builder(context, input); } return ValueListenableBuilder( @@ -31,7 +44,7 @@ class SortBoxBuilder extends StatelessWidget { builder: (final context, final order, final _) { final box = Box(property, order); - return builder(context, sortBox.sort(input!, box)); + return builder(context, sortBox.sort(input, box)); }, ), ); diff --git a/packages/neon/neon_files/lib/widgets/browser_view.dart b/packages/neon/neon_files/lib/widgets/browser_view.dart index 912daeac..2d06b8aa 100644 --- a/packages/neon/neon_files/lib/widgets/browser_view.dart +++ b/packages/neon/neon_files/lib/widgets/browser_view.dart @@ -60,8 +60,7 @@ class _FilesBrowserViewState extends State { items: [ for (final uploadTask in tasksSnapshot.requireData.whereType().where( (final task) => - sorted?.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty ?? - false, + sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty, )) ...[ FileListTile( bloc: widget.filesBloc, @@ -73,34 +72,32 @@ class _FilesBrowserViewState extends State { onPickFile: widget.onPickFile, ), ], - if (sorted != null) ...[ - for (final file in sorted) ...[ - if (!widget.onlyShowDirectories || 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.onlyShowDirectories || file.isDirectory) ...[ + 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, - enableFileActions: widget.enableFileActions, - onPickFile: widget.onPickFile, - ); - }, - ), - ], + return FileListTile( + bloc: widget.filesBloc, + browserBloc: widget.bloc, + details: details, + enableFileActions: widget.enableFileActions, + onPickFile: widget.onPickFile, + ); + }, + ), ], ], ], diff --git a/packages/neon/neon_notes/lib/widgets/notes_view.dart b/packages/neon/neon_notes/lib/widgets/notes_view.dart index 8042fe5b..3926f975 100644 --- a/packages/neon/neon_notes/lib/widgets/notes_view.dart +++ b/packages/neon/neon_notes/lib/widgets/notes_view.dart @@ -31,8 +31,8 @@ class NotesView extends StatelessWidget { scrollKey: 'notes-notes', withFloatingActionButton: true, items: [ - ...?sortedFavorites, - ...?sortedNonFavorites, + ...sortedFavorites, + ...sortedNonFavorites, ], isLoading: notes.isLoading, error: notes.error, From a7d861f0a167b21f00d89eed46219ec001c96608 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 18 Aug 2023 11:40:56 +0200 Subject: [PATCH 2/5] feat(sort_box,neon_files,neon_news,neon_notes): recursive sort Signed-off-by: Nikolas Rimikis --- packages/neon/neon_files/lib/sort/files.dart | 8 +- .../neon/neon_news/lib/sort/articles.dart | 8 +- packages/neon/neon_news/lib/sort/feeds.dart | 8 +- packages/neon/neon_news/lib/sort/folders.dart | 8 +- .../neon/neon_notes/lib/sort/categories.dart | 4 +- packages/neon/neon_notes/lib/sort/notes.dart | 4 +- packages/sort_box/lib/sort_box.dart | 42 ++++---- packages/sort_box/test/sort_box_test.dart | 100 ++++++++++++------ 8 files changed, 122 insertions(+), 60 deletions(-) diff --git a/packages/neon/neon_files/lib/sort/files.dart b/packages/neon/neon_files/lib/sort/files.dart index 1f65ac7c..aba203b7 100644 --- a/packages/neon/neon_files/lib/sort/files.dart +++ b/packages/neon/neon_files/lib/sort/files.dart @@ -7,7 +7,11 @@ final filesSortBox = SortBox( FilesSortProperty.size: (final file) => file.size ?? 0, }, { - FilesSortProperty.modifiedDate: Box(FilesSortProperty.name, SortBoxOrder.ascending), - FilesSortProperty.size: Box(FilesSortProperty.name, SortBoxOrder.ascending), + FilesSortProperty.modifiedDate: { + Box(FilesSortProperty.name, SortBoxOrder.ascending), + }, + FilesSortProperty.size: { + Box(FilesSortProperty.name, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_news/lib/sort/articles.dart b/packages/neon/neon_news/lib/sort/articles.dart index ff1087ba..18f69a15 100644 --- a/packages/neon/neon_news/lib/sort/articles.dart +++ b/packages/neon/neon_news/lib/sort/articles.dart @@ -7,7 +7,11 @@ final articlesSortBox = SortBox( ArticlesSortProperty.byFeed: (final article) => article.feedId, }, { - ArticlesSortProperty.alphabetical: Box(ArticlesSortProperty.publishDate, SortBoxOrder.descending), - ArticlesSortProperty.byFeed: Box(ArticlesSortProperty.alphabetical, SortBoxOrder.ascending), + ArticlesSortProperty.alphabetical: { + Box(ArticlesSortProperty.publishDate, SortBoxOrder.descending), + }, + ArticlesSortProperty.byFeed: { + Box(ArticlesSortProperty.alphabetical, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_news/lib/sort/feeds.dart b/packages/neon/neon_news/lib/sort/feeds.dart index 7425e875..5e9f37a5 100644 --- a/packages/neon/neon_news/lib/sort/feeds.dart +++ b/packages/neon/neon_news/lib/sort/feeds.dart @@ -6,7 +6,11 @@ final feedsSortBox = SortBox( FeedsSortProperty.unreadCount: (final feed) => feed.unreadCount ?? 0, }, { - FeedsSortProperty.alphabetical: Box(FeedsSortProperty.unreadCount, SortBoxOrder.descending), - FeedsSortProperty.unreadCount: Box(FeedsSortProperty.alphabetical, SortBoxOrder.ascending), + FeedsSortProperty.alphabetical: { + Box(FeedsSortProperty.unreadCount, SortBoxOrder.descending), + }, + FeedsSortProperty.unreadCount: { + Box(FeedsSortProperty.alphabetical, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_news/lib/sort/folders.dart b/packages/neon/neon_news/lib/sort/folders.dart index 633c5882..39061dcc 100644 --- a/packages/neon/neon_news/lib/sort/folders.dart +++ b/packages/neon/neon_news/lib/sort/folders.dart @@ -6,8 +6,12 @@ final foldersSortBox = SortBox( FoldersSortProperty.unreadCount: (final folderFeedsWrapper) => folderFeedsWrapper.$3, }, { - FoldersSortProperty.alphabetical: Box(FoldersSortProperty.unreadCount, SortBoxOrder.descending), - FoldersSortProperty.unreadCount: Box(FoldersSortProperty.alphabetical, SortBoxOrder.ascending), + FoldersSortProperty.alphabetical: { + Box(FoldersSortProperty.unreadCount, SortBoxOrder.descending), + }, + FoldersSortProperty.unreadCount: { + Box(FoldersSortProperty.alphabetical, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_notes/lib/sort/categories.dart b/packages/neon/neon_notes/lib/sort/categories.dart index 42f461c4..53cbbc0e 100644 --- a/packages/neon/neon_notes/lib/sort/categories.dart +++ b/packages/neon/neon_notes/lib/sort/categories.dart @@ -6,7 +6,9 @@ final categoriesSortBox = SortBox( CategoriesSortProperty.notesCount: (final category) => category.count, }, { - CategoriesSortProperty.notesCount: Box(CategoriesSortProperty.alphabetical, SortBoxOrder.ascending), + CategoriesSortProperty.notesCount: { + Box(CategoriesSortProperty.alphabetical, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_notes/lib/sort/notes.dart b/packages/neon/neon_notes/lib/sort/notes.dart index 137dcdfb..04606eb4 100644 --- a/packages/neon/neon_notes/lib/sort/notes.dart +++ b/packages/neon/neon_notes/lib/sort/notes.dart @@ -6,6 +6,8 @@ final notesSortBox = SortBox( NotesSortProperty.lastModified: (final note) => note.modified, }, { - NotesSortProperty.alphabetical: Box(NotesSortProperty.lastModified, SortBoxOrder.descending), + NotesSortProperty.alphabetical: { + Box(NotesSortProperty.lastModified, SortBoxOrder.descending), + }, }, ); diff --git a/packages/sort_box/lib/sort_box.dart b/packages/sort_box/lib/sort_box.dart index 6c174810..7c922d2e 100644 --- a/packages/sort_box/lib/sort_box.dart +++ b/packages/sort_box/lib/sort_box.dart @@ -5,40 +5,42 @@ typedef ComparableGetter = Comparable Function(T); class SortBox { SortBox( this._properties, - this._secondaryBoxes, + this._boxes, ); final Map> _properties; - final Map> _secondaryBoxes; + final Map>> _boxes; List sort(final List input, final Box box) { if (input.length <= 1) { return input; } - final comparableGetter = _properties[box.property]!; - final secondaryBox = _secondaryBoxes[box.property]; - final comparableGetter2 = _properties[secondaryBox?.property]; + final sorted = input + ..sort((final item1, final item2) => _compare(item1, item2, box, _boxes[box.property]?.iterator)); + + return sorted; + } - return input - ..sort( - (final item1, final item2) { - final first = _compare(item1, item2, box.order, comparableGetter); + int _compare( + final R item1, + final R item2, + final Box box, + final Iterator>? iterator, + ) { + final comparableGetter = _properties[box.property]!; - if (first == 0 && secondaryBox != null) { - return _compare(item1, item2, secondaryBox.order, comparableGetter2!); - } + final comparable1 = comparableGetter(item1); + final comparable2 = comparableGetter(item2); - return first; - }, - ); - } + final order = + box.order == SortBoxOrder.ascending ? comparable1.compareTo(comparable2) : comparable2.compareTo(comparable1); - int _compare(final R item1, final R item2, final SortBoxOrder order, final ComparableGetter getter) { - final comparable1 = getter(item1); - final comparable2 = getter(item2); + if (order == 0 && iterator != null && iterator.moveNext()) { + return _compare(item1, item2, iterator.current, iterator); + } - return order == SortBoxOrder.ascending ? comparable1.compareTo(comparable2) : comparable2.compareTo(comparable1); + return order; } } diff --git a/packages/sort_box/test/sort_box_test.dart b/packages/sort_box/test/sort_box_test.dart index 2ea10bfc..02032676 100644 --- a/packages/sort_box/test/sort_box_test.dart +++ b/packages/sort_box/test/sort_box_test.dart @@ -4,16 +4,19 @@ import 'package:test/test.dart'; enum FruitSort { alphabetical, count, + price, } class Fruit { - Fruit( + const Fruit( this.name, - this.count, - ); + this.count, [ + this.price, + ]); final String name; final int count; + final int? price; @override String toString() => 'Fruit(name: $name, count: $count)'; @@ -24,21 +27,30 @@ void main() { { FruitSort.alphabetical: (final fruit) => fruit.name.toLowerCase(), FruitSort.count: (final fruit) => fruit.count, + FruitSort.price: (final fruit) => fruit.price!, }, { - FruitSort.alphabetical: Box(FruitSort.count, SortBoxOrder.ascending), - FruitSort.count: Box(FruitSort.alphabetical, SortBoxOrder.ascending), + FruitSort.alphabetical: { + Box(FruitSort.count, SortBoxOrder.ascending), + }, + FruitSort.count: { + Box(FruitSort.alphabetical, SortBoxOrder.ascending), + }, + FruitSort.price: { + Box(FruitSort.alphabetical, SortBoxOrder.descending), + Box(FruitSort.count, SortBoxOrder.ascending), + }, }, ); group('Primary', () { test('Alphabetical', () { final fruits = [ - Fruit('Apple', 1), - Fruit('Banana', 2), - Fruit('Apple', 3), - Fruit('Banana', 4), - Fruit('Apple', 5), + const Fruit('Apple', 1), + const Fruit('Banana', 2), + const Fruit('Apple', 3), + const Fruit('Banana', 4), + const Fruit('Apple', 5), ]; final sorted = sortBox.sort(fruits, Box(FruitSort.alphabetical, SortBoxOrder.ascending)); @@ -52,11 +64,11 @@ void main() { test('Count', () { final fruits = [ - Fruit('Apple', 1), - Fruit('Banana', 5), - Fruit('Apple', 4), - Fruit('Banana', 2), - Fruit('Apple', 3), + const Fruit('Apple', 1), + const Fruit('Banana', 5), + const Fruit('Apple', 4), + const Fruit('Banana', 2), + const Fruit('Apple', 3), ]; final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); @@ -73,11 +85,11 @@ void main() { group('Secondary', () { test('Alphabetical', () { final fruits = [ - Fruit('Apple', 1), - Fruit('Banana', 2), - Fruit('Apple', 2), - Fruit('Banana', 1), - Fruit('Apple', 2), + const Fruit('Apple', 1), + const Fruit('Banana', 2), + const Fruit('Apple', 2), + const Fruit('Banana', 1), + const Fruit('Apple', 2), ]; final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); @@ -94,11 +106,11 @@ void main() { test('Count', () { final fruits = [ - Fruit('Apple', 3), - Fruit('Banana', 4), - Fruit('Apple', 1), - Fruit('Banana', 2), - Fruit('Apple', 5), + const Fruit('Apple', 3), + const Fruit('Banana', 4), + const Fruit('Apple', 1), + const Fruit('Banana', 2), + const Fruit('Apple', 5), ]; final sorted = sortBox.sort(fruits, Box(FruitSort.alphabetical, SortBoxOrder.ascending)); @@ -116,11 +128,11 @@ void main() { test('Primary all equal', () { final fruits = [ - Fruit('Coconut', 1), - Fruit('Banana', 1), - Fruit('Apple', 1), - Fruit('Elderberry', 1), - Fruit('Damson', 1), + const Fruit('Coconut', 1), + const Fruit('Banana', 1), + const Fruit('Apple', 1), + const Fruit('Elderberry', 1), + const Fruit('Damson', 1), ]; final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); @@ -130,4 +142,32 @@ void main() { } }); }); + + group('Third', () { + test('Count', () { + final fruits = [ + const Fruit('Apple', 1, 3), + const Fruit('Banana', 2, 2), + const Fruit('Apple', 2, 0), + const Fruit('Banana', 1, 3), + const Fruit('Apple', 2, 3), + ]; + final sorted = sortBox.sort(fruits, Box(FruitSort.price, SortBoxOrder.ascending)); + + final price = [0, 2, 3, 3, 3]; + for (var i = 0; i < 5; i++) { + expect(sorted[i].price, price[i]); + } + + final names = ['Apple', 'Banana', 'Banana', 'Apple', 'Apple']; + for (var i = 0; i < 5; i++) { + expect(sorted[i].name, names[i]); + } + + final counts = [2, 2, 1, 1, 2]; + for (var i = 0; i < 5; i++) { + expect(sorted[i].count, counts[i]); + } + }); + }); } From 4d96f08186106aa6114090a3205e4bbc31e41b37 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Sat, 19 Aug 2023 17:22:33 +0200 Subject: [PATCH 3/5] refactor(sort_box,neon,neon_files,neon_news): use Dart3 records and switch Signed-off-by: Nikolas Rimikis --- .../lib/src/sort_box/sort_box_builder.dart | 2 +- packages/neon/neon_files/lib/sort/files.dart | 4 +-- .../neon/neon_news/lib/sort/articles.dart | 4 +-- packages/neon/neon_news/lib/sort/feeds.dart | 4 +-- packages/neon/neon_news/lib/sort/folders.dart | 4 +-- .../neon/neon_notes/lib/sort/categories.dart | 2 +- packages/neon/neon_notes/lib/sort/notes.dart | 2 +- packages/sort_box/lib/sort_box.dart | 30 +++++++------------ packages/sort_box/test/sort_box_test.dart | 20 ++++++------- 9 files changed, 32 insertions(+), 40 deletions(-) diff --git a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart index 6c256e78..29d7f58d 100644 --- a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart +++ b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart @@ -42,7 +42,7 @@ class SortBoxBuilder extends StatelessWidget { builder: (final context, final property, final _) => ValueListenableBuilder( valueListenable: sortBoxOrderOption, builder: (final context, final order, final _) { - final box = Box(property, order); + final box = (property, order); return builder(context, sortBox.sort(input, box)); }, diff --git a/packages/neon/neon_files/lib/sort/files.dart b/packages/neon/neon_files/lib/sort/files.dart index aba203b7..d0d7d96e 100644 --- a/packages/neon/neon_files/lib/sort/files.dart +++ b/packages/neon/neon_files/lib/sort/files.dart @@ -8,10 +8,10 @@ final filesSortBox = SortBox( }, { FilesSortProperty.modifiedDate: { - Box(FilesSortProperty.name, SortBoxOrder.ascending), + (FilesSortProperty.name, SortBoxOrder.ascending), }, FilesSortProperty.size: { - Box(FilesSortProperty.name, SortBoxOrder.ascending), + (FilesSortProperty.name, SortBoxOrder.ascending), }, }, ); diff --git a/packages/neon/neon_news/lib/sort/articles.dart b/packages/neon/neon_news/lib/sort/articles.dart index 18f69a15..4a082afb 100644 --- a/packages/neon/neon_news/lib/sort/articles.dart +++ b/packages/neon/neon_news/lib/sort/articles.dart @@ -8,10 +8,10 @@ final articlesSortBox = SortBox( }, { ArticlesSortProperty.alphabetical: { - Box(ArticlesSortProperty.publishDate, SortBoxOrder.descending), + (ArticlesSortProperty.publishDate, SortBoxOrder.descending), }, ArticlesSortProperty.byFeed: { - Box(ArticlesSortProperty.alphabetical, SortBoxOrder.ascending), + (ArticlesSortProperty.alphabetical, SortBoxOrder.ascending), }, }, ); diff --git a/packages/neon/neon_news/lib/sort/feeds.dart b/packages/neon/neon_news/lib/sort/feeds.dart index 5e9f37a5..c25d3596 100644 --- a/packages/neon/neon_news/lib/sort/feeds.dart +++ b/packages/neon/neon_news/lib/sort/feeds.dart @@ -7,10 +7,10 @@ final feedsSortBox = SortBox( }, { FeedsSortProperty.alphabetical: { - Box(FeedsSortProperty.unreadCount, SortBoxOrder.descending), + (FeedsSortProperty.unreadCount, SortBoxOrder.descending), }, FeedsSortProperty.unreadCount: { - Box(FeedsSortProperty.alphabetical, SortBoxOrder.ascending), + (FeedsSortProperty.alphabetical, SortBoxOrder.ascending), }, }, ); diff --git a/packages/neon/neon_news/lib/sort/folders.dart b/packages/neon/neon_news/lib/sort/folders.dart index 39061dcc..cfc1bc9d 100644 --- a/packages/neon/neon_news/lib/sort/folders.dart +++ b/packages/neon/neon_news/lib/sort/folders.dart @@ -7,10 +7,10 @@ final foldersSortBox = SortBox( }, { FoldersSortProperty.alphabetical: { - Box(FoldersSortProperty.unreadCount, SortBoxOrder.descending), + (FoldersSortProperty.unreadCount, SortBoxOrder.descending), }, FoldersSortProperty.unreadCount: { - Box(FoldersSortProperty.alphabetical, SortBoxOrder.ascending), + (FoldersSortProperty.alphabetical, SortBoxOrder.ascending), }, }, ); diff --git a/packages/neon/neon_notes/lib/sort/categories.dart b/packages/neon/neon_notes/lib/sort/categories.dart index 53cbbc0e..32c16df8 100644 --- a/packages/neon/neon_notes/lib/sort/categories.dart +++ b/packages/neon/neon_notes/lib/sort/categories.dart @@ -7,7 +7,7 @@ final categoriesSortBox = SortBox( }, { CategoriesSortProperty.notesCount: { - Box(CategoriesSortProperty.alphabetical, SortBoxOrder.ascending), + (CategoriesSortProperty.alphabetical, SortBoxOrder.ascending), }, }, ); diff --git a/packages/neon/neon_notes/lib/sort/notes.dart b/packages/neon/neon_notes/lib/sort/notes.dart index 04606eb4..430bf0f9 100644 --- a/packages/neon/neon_notes/lib/sort/notes.dart +++ b/packages/neon/neon_notes/lib/sort/notes.dart @@ -7,7 +7,7 @@ final notesSortBox = SortBox( }, { NotesSortProperty.alphabetical: { - Box(NotesSortProperty.lastModified, SortBoxOrder.descending), + (NotesSortProperty.lastModified, SortBoxOrder.descending), }, }, ); diff --git a/packages/sort_box/lib/sort_box.dart b/packages/sort_box/lib/sort_box.dart index 7c922d2e..733238ac 100644 --- a/packages/sort_box/lib/sort_box.dart +++ b/packages/sort_box/lib/sort_box.dart @@ -9,15 +9,14 @@ class SortBox { ); final Map> _properties; - final Map>> _boxes; + final Map> _boxes; - List sort(final List input, final Box box) { + List sort(final List input, final (T property, SortBoxOrder order) box) { if (input.length <= 1) { return input; } - final sorted = input - ..sort((final item1, final item2) => _compare(item1, item2, box, _boxes[box.property]?.iterator)); + final sorted = input..sort((final item1, final item2) => _compare(item1, item2, box, _boxes[box.$1]?.iterator)); return sorted; } @@ -25,16 +24,19 @@ class SortBox { int _compare( final R item1, final R item2, - final Box box, - final Iterator>? iterator, + final (T property, SortBoxOrder order) box, + final Iterator<(T property, SortBoxOrder order)>? iterator, ) { - final comparableGetter = _properties[box.property]!; + final (property, sortBoxOrder) = box; + final comparableGetter = _properties[property]!; final comparable1 = comparableGetter(item1); final comparable2 = comparableGetter(item2); - final order = - box.order == SortBoxOrder.ascending ? comparable1.compareTo(comparable2) : comparable2.compareTo(comparable1); + final order = switch (sortBoxOrder) { + SortBoxOrder.ascending => comparable1.compareTo(comparable2), + SortBoxOrder.descending => comparable2.compareTo(comparable1), + }; if (order == 0 && iterator != null && iterator.moveNext()) { return _compare(item1, item2, iterator.current, iterator); @@ -48,13 +50,3 @@ enum SortBoxOrder { ascending, descending, } - -class Box { - Box( - this.property, - this.order, - ); - - final T property; - final SortBoxOrder order; -} diff --git a/packages/sort_box/test/sort_box_test.dart b/packages/sort_box/test/sort_box_test.dart index 02032676..01fa842b 100644 --- a/packages/sort_box/test/sort_box_test.dart +++ b/packages/sort_box/test/sort_box_test.dart @@ -31,14 +31,14 @@ void main() { }, { FruitSort.alphabetical: { - Box(FruitSort.count, SortBoxOrder.ascending), + (FruitSort.count, SortBoxOrder.ascending), }, FruitSort.count: { - Box(FruitSort.alphabetical, SortBoxOrder.ascending), + (FruitSort.alphabetical, SortBoxOrder.ascending), }, FruitSort.price: { - Box(FruitSort.alphabetical, SortBoxOrder.descending), - Box(FruitSort.count, SortBoxOrder.ascending), + (FruitSort.alphabetical, SortBoxOrder.descending), + (FruitSort.count, SortBoxOrder.ascending), }, }, ); @@ -52,7 +52,7 @@ void main() { const Fruit('Banana', 4), const Fruit('Apple', 5), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.alphabetical, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.alphabetical, SortBoxOrder.ascending)); for (var i = 0; i < 3; i++) { expect(sorted[i].name, 'Apple'); @@ -70,7 +70,7 @@ void main() { const Fruit('Banana', 2), const Fruit('Apple', 3), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.count, SortBoxOrder.ascending)); final names = ['Apple', 'Banana', 'Apple', 'Apple', 'Banana']; for (var i = 0; i < 5; i++) { @@ -91,7 +91,7 @@ void main() { const Fruit('Banana', 1), const Fruit('Apple', 2), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.count, SortBoxOrder.ascending)); final names = ['Apple', 'Banana', 'Apple', 'Apple', 'Banana']; for (var i = 0; i < 5; i++) { @@ -112,7 +112,7 @@ void main() { const Fruit('Banana', 2), const Fruit('Apple', 5), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.alphabetical, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.alphabetical, SortBoxOrder.ascending)); for (var i = 0; i < 3; i++) { expect(sorted[i].name, 'Apple'); @@ -134,7 +134,7 @@ void main() { const Fruit('Elderberry', 1), const Fruit('Damson', 1), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.count, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.count, SortBoxOrder.ascending)); final names = ['Apple', 'Banana', 'Coconut', 'Damson', 'Elderberry']; for (var i = 0; i < 5; i++) { @@ -152,7 +152,7 @@ void main() { const Fruit('Banana', 1, 3), const Fruit('Apple', 2, 3), ]; - final sorted = sortBox.sort(fruits, Box(FruitSort.price, SortBoxOrder.ascending)); + final sorted = sortBox.sort(fruits, (FruitSort.price, SortBoxOrder.ascending)); final price = [0, 2, 3, 3, 3]; for (var i = 0; i < 5; i++) { From 05c824bf3e09d7bef10f33fa873acc4112fe4710 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 25 Aug 2023 13:32:13 +0200 Subject: [PATCH 4/5] feat(sort_box,neon): ability to presort the values Signed-off-by: Nikolas Rimikis --- .../lib/src/sort_box/sort_box_builder.dart | 6 ++- packages/sort_box/lib/sort_box.dart | 45 +++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart index 29d7f58d..afa7227b 100644 --- a/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart +++ b/packages/neon/neon/lib/src/sort_box/sort_box_builder.dart @@ -17,6 +17,7 @@ class SortBoxBuilder extends StatelessWidget { required this.sortBoxOrderOption, required final List? input, required this.builder, + this.presort, super.key, }) : input = input ?? []; @@ -30,6 +31,9 @@ class SortBoxBuilder extends StatelessWidget { /// Child builder using the sorted list. final SortBoxWidgetBuilder builder; + /// Pre sorts input. + final Set<(T property, SortBoxOrder order)>? presort; + @override Widget build(final BuildContext context) { if (input.length <= 1) { @@ -44,7 +48,7 @@ class SortBoxBuilder extends StatelessWidget { builder: (final context, final order, final _) { final box = (property, order); - return builder(context, sortBox.sort(input, box)); + return builder(context, sortBox.sort(input, box, presort)); }, ), ); diff --git a/packages/sort_box/lib/sort_box.dart b/packages/sort_box/lib/sort_box.dart index 733238ac..c7e4eca4 100644 --- a/packages/sort_box/lib/sort_box.dart +++ b/packages/sort_box/lib/sort_box.dart @@ -1,22 +1,47 @@ -// ignore_for_file: public_member_api_docs - +/// Signature of a function returning a [Comparable]. typedef ComparableGetter = Comparable Function(T); +/// Sorting Box to sort [List]s on multiple properties. class SortBox { + /// Constructs a new SortBox. + /// + /// A *Box* is a record of a property and how to order it. SortBox( this._properties, this._boxes, ); + /// A mapping of all values [T] to their [ComparableGetter]. final Map> _properties; + + /// A mapping of values [T] to their *Boxes*. + /// + /// The Boxes are applied if two elements are considered equal regarding their property [T]. final Map> _boxes; - List sort(final List input, final (T property, SortBoxOrder order) box) { + /// Sorts the [input] list according to their [box]. + /// + /// A box contains the property and [SortBoxOrder] how the list should be sorted. + /// In case the property of two elements is considered equal all following boxes specified at `_boxes[property]` are applied. + /// If specified [presort] will be applied before [box] and [_boxes]. + /// + /// This function sorts the input in place and a reference to it mutating the provided list. + List sort( + final List input, + final (T property, SortBoxOrder order) box, [ + final Set<(T property, SortBoxOrder order)>? presort, + ]) { if (input.length <= 1) { return input; } - final sorted = input..sort((final item1, final item2) => _compare(item1, item2, box, _boxes[box.$1]?.iterator)); + final boxes = { + ...?presort, + box, + ...?_boxes[box.$1], + }; + + final sorted = input..sort((final item1, final item2) => _compare(item1, item2, boxes.iterator..moveNext())); return sorted; } @@ -24,9 +49,9 @@ class SortBox { int _compare( final R item1, final R item2, - final (T property, SortBoxOrder order) box, - final Iterator<(T property, SortBoxOrder order)>? iterator, + final Iterator<(T property, SortBoxOrder order)> iterator, ) { + final box = iterator.current; final (property, sortBoxOrder) = box; final comparableGetter = _properties[property]!; @@ -38,15 +63,19 @@ class SortBox { SortBoxOrder.descending => comparable2.compareTo(comparable1), }; - if (order == 0 && iterator != null && iterator.moveNext()) { - return _compare(item1, item2, iterator.current, iterator); + if (order == 0 && iterator.moveNext()) { + return _compare(item1, item2, iterator); } return order; } } +/// Sorting order used by [SortBox]. enum SortBoxOrder { + /// Ascending sorting order. ascending, + + /// Descending sorting order. descending, } From a3419a838c0198e25d373a7cf9b195b0aad82bc2 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 18 Aug 2023 13:55:53 +0200 Subject: [PATCH 5/5] refactor(neon_notes): use presorting in the NotesView Signed-off-by: Nikolas Rimikis --- packages/neon/neon_notes/lib/options.dart | 1 + packages/neon/neon_notes/lib/sort/notes.dart | 4 +++ .../neon_notes/lib/widgets/notes_view.dart | 34 +++++++------------ 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/neon/neon_notes/lib/options.dart b/packages/neon/neon_notes/lib/options.dart index 72c7cb26..f7ac1957 100644 --- a/packages/neon/neon_notes/lib/options.dart +++ b/packages/neon/neon_notes/lib/options.dart @@ -108,6 +108,7 @@ enum DefaultNoteViewType { enum NotesSortProperty { lastModified, alphabetical, + favorite, } enum CategoriesSortProperty { diff --git a/packages/neon/neon_notes/lib/sort/notes.dart b/packages/neon/neon_notes/lib/sort/notes.dart index 430bf0f9..bed700b2 100644 --- a/packages/neon/neon_notes/lib/sort/notes.dart +++ b/packages/neon/neon_notes/lib/sort/notes.dart @@ -4,10 +4,14 @@ final notesSortBox = SortBox( { NotesSortProperty.alphabetical: (final note) => note.title.toLowerCase(), NotesSortProperty.lastModified: (final note) => note.modified, + NotesSortProperty.favorite: (final note) => note.favorite ? 0 : 1, }, { NotesSortProperty.alphabetical: { (NotesSortProperty.lastModified, SortBoxOrder.descending), }, + NotesSortProperty.lastModified: { + (NotesSortProperty.alphabetical, SortBoxOrder.ascending), + }, }, ); diff --git a/packages/neon/neon_notes/lib/widgets/notes_view.dart b/packages/neon/neon_notes/lib/widgets/notes_view.dart index 3926f975..03ef7266 100644 --- a/packages/neon/neon_notes/lib/widgets/notes_view.dart +++ b/packages/neon/neon_notes/lib/widgets/notes_view.dart @@ -15,30 +15,20 @@ class NotesView extends StatelessWidget { stream: bloc.notes, builder: (final context, final notes) => SortBoxBuilder( sortBox: notesSortBox, + presort: const { + (NotesSortProperty.favorite, SortBoxOrder.ascending), + }, sortPropertyOption: bloc.options.notesSortPropertyOption, sortBoxOrderOption: bloc.options.notesSortBoxOrderOption, - input: category != null - ? notes.data?.where((final note) => note.favorite && note.category == category).toList() - : notes.data?.where((final note) => note.favorite).toList(), - builder: (final context, final sortedFavorites) => SortBoxBuilder( - sortBox: notesSortBox, - sortPropertyOption: bloc.options.notesSortPropertyOption, - sortBoxOrderOption: bloc.options.notesSortBoxOrderOption, - input: category != null - ? notes.data?.where((final note) => !note.favorite && note.category == category).toList() - : notes.data?.where((final note) => !note.favorite).toList(), - builder: (final context, final sortedNonFavorites) => NeonListView( - scrollKey: 'notes-notes', - withFloatingActionButton: true, - items: [ - ...sortedFavorites, - ...sortedNonFavorites, - ], - isLoading: notes.isLoading, - error: notes.error, - onRefresh: bloc.refresh, - builder: _buildNote, - ), + input: category != null ? notes.data?.where((final note) => note.category == category).toList() : notes.data, + builder: (final context, final sorted) => NeonListView( + scrollKey: 'notes-notes', + withFloatingActionButton: true, + items: sorted, + isLoading: notes.isLoading, + error: notes.error, + onRefresh: bloc.refresh, + builder: _buildNote, ), ), );