diff --git a/assets/langs/en.yaml b/assets/langs/en.yaml index 224d39f9..ea81c910 100644 --- a/assets/langs/en.yaml +++ b/assets/langs/en.yaml @@ -1,4 +1,6 @@ settings: + ok: Ok + cancel: Cancel title: Settings author: label: Full Name @@ -71,11 +73,17 @@ settings: markdownEditor: Markdown Editor journalEditor: Journal Editor defaultFolder: Default Folder - sortingMode: + sortingField: modified: Last Modified created: Created filename: File Name title: Title + sortingOrder: + asc: Ascending + desc: Descending + sortingMode: + field: Field + order: Order remoteSync: auto: Automatic manual: Manual diff --git a/lib/core/notes_folder_config.dart b/lib/core/notes_folder_config.dart index e3f59f0c..499dee43 100644 --- a/lib/core/notes_folder_config.dart +++ b/lib/core/notes_folder_config.dart @@ -70,7 +70,7 @@ class NotesFolderConfig extends Equatable { return NotesFolderConfig( defaultEditor: settings.defaultEditor.toEditorType(), defaultView: settings.defaultView.toFolderViewType(), - sortingMode: settings.sortingMode, + sortingMode: SortingMode(settings.sortingField, settings.sortingOrder), showNoteSummary: settings.showNoteSummary, viewHeader: viewHeader, fileNameFormat: settings.noteFileNameFormat, @@ -82,7 +82,8 @@ class NotesFolderConfig extends Equatable { Future saveToSettings() async { var settings = Settings.instance; - settings.sortingMode = sortingMode; + settings.sortingField = sortingMode.field; + settings.sortingOrder = sortingMode.order; settings.showNoteSummary = showNoteSummary; settings.defaultEditor = SettingsEditorType.fromEditorType(defaultEditor); settings.defaultView = @@ -145,8 +146,12 @@ class NotesFolderConfig extends Equatable { Log.d('NotesFolderConfig::decode("$contents") -> ${err.toString()}'); } - var sortingMode = - SortingMode.fromInternalString(map["sortingMode"]?.toString()); + var sortingField = + SortingField.fromInternalString(map["sortingField"]?.toString()); + var sortingOrder = + SortingOrder.fromInternalString(map["sortingOrder"]?.toString()); + var sortingMode = SortingMode(sortingField, sortingOrder); + var defaultEditor = SettingsEditorType.fromInternalString(map["defaultEditor"]?.toString()); var defaultView = SettingsFolderViewType.fromInternalString( @@ -198,7 +203,8 @@ class NotesFolderConfig extends Equatable { } var map = { - "sortingMode": sortingMode.toInternalString(), + "sortingField": sortingMode.field.toInternalString(), + "sortingOrder": sortingMode.order.toInternalString(), "defaultEditor": SettingsEditorType.fromEditorType(defaultEditor).toInternalString(), "defaultView": SettingsFolderViewType.fromFolderViewType(defaultView) diff --git a/lib/core/sorted_notes_folder.dart b/lib/core/sorted_notes_folder.dart index 88ec5c33..594c8659 100644 --- a/lib/core/sorted_notes_folder.dart +++ b/lib/core/sorted_notes_folder.dart @@ -141,7 +141,8 @@ class SortedNotesFolder with NotesFolderNotifier implements NotesFolder { bool get isEmpty => folder.isEmpty; void changeSortingMode(SortingMode sm) { - Log.d("Setting sorting to me ${sm.toInternalString()}"); + Log.d( + "Setting sorting to me ${sm.field.toInternalString()} ${sm.order.toInternalString()}"); _sortingMode = sm; _sortFunc = _sortingMode.sortingFunction(); diff --git a/lib/core/sorting_mode.dart b/lib/core/sorting_mode.dart index 7d7a9158..fc868c02 100644 --- a/lib/core/sorting_mode.dart +++ b/lib/core/sorting_mode.dart @@ -4,28 +4,14 @@ import 'package:gitjournal/core/note.dart'; typedef NoteSortingFunction = int Function(Note a, Note b); -class SortingMode { - static const Modified = SortingMode( - "settings.sortingMode.modified", - "Modified", - ); - static const Created = SortingMode( - "settings.sortingMode.created", - "Created", - ); - static const FileName = SortingMode( - "settings.sortingMode.filename", - "FileName", - ); - static const Title = SortingMode( - "settings.sortingMode.title", - "Title", - ); - static const Default = Modified; +class SortingOrder { + static const Ascending = SortingOrder("settings.sortingOrder.asc", "asc"); + static const Descending = SortingOrder("settings.sortingOrder.desc", "desc"); + static const Default = Descending; final String _str; final String _publicString; - const SortingMode(this._publicString, this._str); + const SortingOrder(this._publicString, this._str); String toInternalString() { return _str; @@ -35,14 +21,12 @@ class SortingMode { return tr(_publicString); } - static const options = [ - Modified, - Created, - FileName, - Title, + static const options = [ + Ascending, + Descending, ]; - static SortingMode fromInternalString(String str) { + static SortingOrder fromInternalString(String str) { for (var opt in options) { if (opt.toInternalString() == str) { return opt; @@ -51,7 +35,7 @@ class SortingMode { return Default; } - static SortingMode fromPublicString(String str) { + static SortingOrder fromPublicString(String str) { for (var opt in options) { if (opt.toPublicString() == str) { return opt; @@ -62,67 +46,166 @@ class SortingMode { @override String toString() { - assert(false, "SortingMode toString should never be called"); + assert(false, "SortingOrder toString should never be called"); return ""; } +} - // vHanda FIXME: The modified and created should come from Git if not present in the document - NoteSortingFunction sortingFunction() { - switch (_str) { - case "Created": - return (Note a, Note b) { - var aDt = a.created; - var bDt = b.created; - if (aDt == null && bDt != null) { - return 1; - } - if (aDt != null && bDt == null) { - return -1; - } - if (bDt == null && aDt == null) { - return a.fileName.compareTo(b.fileName); - } - return bDt.compareTo(aDt); - }; +class SortingField { + static const Modified = SortingField( + "settings.sortingField.modified", + "Modified", + ); + static const Created = SortingField( + "settings.sortingField.created", + "Created", + ); + static const FileName = SortingField( + "settings.sortingField.filename", + "FileName", + ); + static const Title = SortingField( + "settings.sortingField.title", + "Title", + ); - case "Title": - return (Note a, Note b) { - var aTitleExists = a.title != null && a.title.isNotEmpty; - var bTitleExists = b.title != null && b.title.isNotEmpty; + static const Default = Modified; - if (!aTitleExists && bTitleExists) { - return 1; - } - if (aTitleExists && !bTitleExists) { - return -1; - } - if (!aTitleExists && !bTitleExists) { - return a.fileName.compareTo(b.fileName); - } - return a.title.compareTo(b.title); - }; + final String _str; + final String _publicString; + const SortingField(this._publicString, this._str); - case "FileName": - return (Note a, Note b) { - return a.fileName.compareTo(b.fileName); - }; + String toInternalString() { + return _str; + } - case "Modified": - default: - return (Note a, Note b) { - var aDt = a.modified; - var bDt = b.modified; - if (aDt == null && bDt != null) { - return 1; - } - if (aDt != null && bDt == null) { - return -1; - } - if (bDt == null && aDt == null) { - return a.fileName.compareTo(b.fileName); - } - return bDt.compareTo(aDt); - }; + String toPublicString() { + return tr(_publicString); + } + + static const options = [ + Modified, + Created, + FileName, + Title, + ]; + + static SortingField fromInternalString(String str) { + for (var opt in options) { + if (opt.toInternalString() == str) { + return opt; + } } + return Default; + } + + @override + String toString() { + assert(false, "SortingField toString should never be called"); + return ""; } } + +class SortingMode { + final SortingField field; + final SortingOrder order; + + SortingMode(this.field, this.order); + + NoteSortingFunction sortingFunction() { + switch (field) { + case SortingField.Created: + return order == SortingOrder.Descending + ? _sortCreatedDesc + : _reverse(_sortCreatedDesc); + + case SortingField.Title: + return order == SortingOrder.Descending + ? _reverse(_sortTitleAsc) + : _sortTitleAsc; + + case SortingField.FileName: + return order == SortingOrder.Descending + ? _reverse(_sortFileNameAsc) + : _sortFileNameAsc; + + case SortingField.Modified: + default: + return order == SortingOrder.Descending + ? _sortModifiedDesc + : _reverse(_sortModifiedDesc); + } + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SortingMode && other.field == field && other.order == order; + + @override + int get hashCode => order.hashCode ^ field.hashCode; +} + +int _sortCreatedDesc(Note a, Note b) { + var aDt = a.created; + var bDt = b.created; + if (aDt == null && bDt != null) { + return 1; + } + if (aDt != null && bDt == null) { + return -1; + } + if (bDt == null && aDt == null) { + return a.fileName.compareTo(b.fileName); + } + return bDt.compareTo(aDt); +} + +int _sortModifiedDesc(Note a, Note b) { + var aDt = a.modified; + var bDt = b.modified; + if (aDt == null && bDt != null) { + return 1; + } + if (aDt != null && bDt == null) { + return -1; + } + if (bDt == null && aDt == null) { + return a.fileName.compareTo(b.fileName); + } + return bDt.compareTo(aDt); +} + +int _sortTitleAsc(Note a, Note b) { + var aTitleExists = a.title != null && a.title.isNotEmpty; + var bTitleExists = b.title != null && b.title.isNotEmpty; + + if (!aTitleExists && bTitleExists) { + return 1; + } + if (aTitleExists && !bTitleExists) { + return -1; + } + if (!aTitleExists && !bTitleExists) { + return a.fileName.compareTo(b.fileName); + } + return a.title.compareTo(b.title); +} + +int _sortFileNameAsc(Note a, Note b) { + return a.fileName.compareTo(b.fileName); +} + +NoteSortingFunction _reverse(NoteSortingFunction func) { + return (Note a, Note b) { + int r = func(a, b); + if (r == 0) { + return r; + } + if (r < 0) { + return 1; + } else { + return -1; + } + }; +} diff --git a/lib/folder_views/journal_view.dart b/lib/folder_views/journal_view.dart index 5b0e41ad..2fa322be 100644 --- a/lib/folder_views/journal_view.dart +++ b/lib/folder_views/journal_view.dart @@ -39,8 +39,8 @@ class JournalView extends StatelessWidget { var textTheme = Theme.of(context).textTheme; DateTime date; - var sortingMode = folder.config.sortingMode; - if (sortingMode == SortingMode.Created) { + var sortingField = folder.config.sortingMode.field; + if (sortingField == SortingField.Created) { date = note.created; } else { date = note.modified; diff --git a/lib/folder_views/standard_view.dart b/lib/folder_views/standard_view.dart index 3f58e867..6a65cea5 100644 --- a/lib/folder_views/standard_view.dart +++ b/lib/folder_views/standard_view.dart @@ -79,10 +79,10 @@ class StandardView extends StatelessWidget { Widget trailing = Container(); DateTime date; - var sortingMode = folder.config.sortingMode; - if (sortingMode == SortingMode.Modified) { + var sortingField = folder.config.sortingMode.field; + if (sortingField == SortingField.Modified) { date = note.modified; - } else if (sortingMode == SortingMode.Created) { + } else if (sortingField == SortingField.Created) { date = note.created; } diff --git a/lib/screens/folder_view.dart b/lib/screens/folder_view.dart index 8bd6954f..75dd6af3 100644 --- a/lib/screens/folder_view.dart +++ b/lib/screens/folder_view.dart @@ -19,7 +19,7 @@ import 'package:gitjournal/widgets/app_bar_menu_button.dart'; import 'package:gitjournal/widgets/app_drawer.dart'; import 'package:gitjournal/widgets/new_note_nav_bar.dart'; import 'package:gitjournal/widgets/note_search_delegate.dart'; -import 'package:gitjournal/widgets/sorting_order_selector.dart'; +import 'package:gitjournal/widgets/sorting_mode_selector.dart'; import 'package:gitjournal/widgets/sync_button.dart'; enum DropDownChoices { @@ -190,7 +190,7 @@ class _FolderViewState extends State { var newSortingMode = await showDialog( context: context, builder: (BuildContext context) => - SortingOrderSelector(sortedNotesFolder.sortingMode), + SortingModeSelector(sortedNotesFolder.sortingMode), ); if (newSortingMode != null) { diff --git a/lib/settings.dart b/lib/settings.dart index 9cc8d34a..eac5c30e 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -32,7 +32,8 @@ class Settings extends ChangeNotifier { String journalEditordefaultNewNoteFolderSpec = ""; RemoteSyncFrequency remoteSyncFrequency = RemoteSyncFrequency.Default; - SortingMode sortingMode = SortingMode.Default; + SortingField sortingField = SortingField.Default; + SortingOrder sortingOrder = SortingOrder.Default; SettingsEditorType defaultEditor = SettingsEditorType.Default; SettingsFolderViewType defaultView = SettingsFolderViewType.Default; bool showNoteSummary = true; @@ -88,7 +89,10 @@ class Settings extends ChangeNotifier { remoteSyncFrequency = RemoteSyncFrequency.fromInternalString( pref.getString("remoteSyncFrequency")); - sortingMode = SortingMode.fromInternalString(pref.getString("sortingMode")); + sortingField = + SortingField.fromInternalString(pref.getString("sortingField")); + sortingOrder = + SortingOrder.fromInternalString(pref.getString("sortingOrder")); defaultEditor = SettingsEditorType.fromInternalString(pref.getString("defaultEditor")); defaultView = SettingsFolderViewType.fromInternalString( @@ -169,8 +173,10 @@ class Settings extends ChangeNotifier { "remoteSyncFrequency", remoteSyncFrequency.toInternalString(), defaultSet.remoteSyncFrequency.toInternalString()); - _setString(pref, "sortingMode", sortingMode.toInternalString(), - defaultSet.sortingMode.toInternalString()); + _setString(pref, "sortingField", sortingField.toInternalString(), + defaultSet.sortingField.toInternalString()); + _setString(pref, "sortingOrder", sortingOrder.toInternalString(), + defaultSet.sortingOrder.toInternalString()); _setString(pref, "defaultEditor", defaultEditor.toInternalString(), defaultSet.defaultEditor.toInternalString()); _setString(pref, "defaultView", defaultView.toInternalString(), @@ -252,7 +258,8 @@ class Settings extends ChangeNotifier { journalEditordefaultNewNoteFolderSpec, "defaultEditor": defaultEditor.toInternalString(), "defaultView": defaultView.toInternalString(), - "sortingMode": sortingMode.toInternalString(), + "sortingField": sortingField.toInternalString(), + "sortingOrder": sortingOrder.toInternalString(), "remoteSyncFrequency": remoteSyncFrequency.toInternalString(), "showNoteSummary": showNoteSummary.toString(), "folderViewHeaderType": folderViewHeaderType, diff --git a/lib/widgets/sorting_mode_selector.dart b/lib/widgets/sorting_mode_selector.dart new file mode 100644 index 00000000..d3e4d6f8 --- /dev/null +++ b/lib/widgets/sorting_mode_selector.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; + +import 'package:easy_localization/easy_localization.dart'; + +import 'package:gitjournal/core/sorting_mode.dart'; +import 'package:gitjournal/screens/settings_screen.dart'; + +class SortingModeSelector extends StatefulWidget { + final SortingMode selectedMode; + + SortingModeSelector(this.selectedMode); + + @override + _SortingModeSelectorState createState() => _SortingModeSelectorState(); +} + +class _SortingModeSelectorState extends State { + SortingField field; + SortingOrder order; + + @override + void initState() { + super.initState(); + field = widget.selectedMode.field; + order = widget.selectedMode.order; + } + + @override + Widget build(BuildContext context) { + var children = [ + SettingsHeader(tr('settings.sortingMode.field')), + for (var sf in SortingField.options) _buildSortingTile(sf), + SettingsHeader(tr('settings.sortingMode.order')), + for (var so in SortingOrder.options) _buildSortingOrderTile(so), + ]; + + return AlertDialog( + title: Text(tr("widgets.SortingOrderSelector.title")), + content: Column( + children: children, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + actions: [ + OutlineButton( + child: Text(tr('settings.cancel')), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + OutlineButton( + child: Text(tr('settings.ok')), + onPressed: () { + Navigator.of(context).pop(SortingMode(field, order)); + }, + ), + ], + ); + } + + RadioListTile _buildSortingTile(SortingField sf) { + return RadioListTile( + title: Text(sf.toPublicString()), + value: sf, + groupValue: field, + onChanged: (SortingField sf) { + setState(() { + field = sf; + }); + }, + ); + } + + RadioListTile _buildSortingOrderTile(SortingOrder so) { + return RadioListTile( + title: Text(so.toPublicString()), + value: so, + groupValue: order, + onChanged: (SortingOrder so) { + setState(() { + order = so; + }); + }, + ); + } +} diff --git a/lib/widgets/sorting_order_selector.dart b/lib/widgets/sorting_order_selector.dart deleted file mode 100644 index c53b5631..00000000 --- a/lib/widgets/sorting_order_selector.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:easy_localization/easy_localization.dart'; - -import 'package:gitjournal/core/sorting_mode.dart'; - -class SortingOrderSelector extends StatelessWidget { - final SortingMode selectedMode; - - SortingOrderSelector(this.selectedMode); - - @override - Widget build(BuildContext context) { - var children = [ - _buildSortingTile(context, SortingMode.Modified), - _buildSortingTile(context, SortingMode.Created), - _buildSortingTile(context, SortingMode.Title), - _buildSortingTile(context, SortingMode.FileName), - ]; - - return AlertDialog( - title: Text(tr("widgets.SortingOrderSelector.title")), - content: Column( - children: children, - mainAxisSize: MainAxisSize.min, - ), - ); - } - - RadioListTile _buildSortingTile( - BuildContext context, - SortingMode sm, - ) { - return RadioListTile( - title: Text(sm.toPublicString()), - value: sm, - groupValue: selectedMode, - onChanged: (SortingMode sm) => Navigator.of(context).pop(sm), - ); - } -} diff --git a/test/notes_folder_config_test.dart b/test/notes_folder_config_test.dart index f2d74809..88746ca1 100644 --- a/test/notes_folder_config_test.dart +++ b/test/notes_folder_config_test.dart @@ -29,7 +29,8 @@ void main() { defaultEditor: EditorType.Checklist, defaultView: FolderViewType.Standard, showNoteSummary: true, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), viewHeader: StandardViewHeader.TitleOrFileName, fileNameFormat: NoteFileNameFormat.Default, folder: folder, diff --git a/test/sorted_notes_folder_test.dart b/test/sorted_notes_folder_test.dart index a4da22ed..76c34b8d 100644 --- a/test/sorted_notes_folder_test.dart +++ b/test/sorted_notes_folder_test.dart @@ -39,7 +39,8 @@ void main() { test('Should load the notes sorted', () async { var sf = SortedNotesFolder( folder: folder, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), ); expect(sf.hasNotes, true); expect(sf.isEmpty, false); @@ -57,7 +58,8 @@ void main() { test('Should on modification remains sorted', () async { var sf = SortedNotesFolder( folder: folder, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), ); var i = sf.notes.indexWhere((n) => n.body == "1"); @@ -73,7 +75,8 @@ void main() { test('Should add new note correctly', () async { var sf = SortedNotesFolder( folder: folder, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), ); var note = Note(folder, p.join(folder.folderPath, "new.md")); @@ -96,7 +99,8 @@ void main() { test('Should add new note to end works correctly', () async { var sf = SortedNotesFolder( folder: folder, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), ); var note = Note(folder, p.join(folder.folderPath, "new.md")); @@ -120,7 +124,8 @@ void main() { var folder = NotesFolderFS(null, tempDir.path); var sf = SortedNotesFolder( folder: folder, - sortingMode: SortingMode.Modified, + sortingMode: + SortingMode(SortingField.Modified, SortingOrder.Descending), ); await folder.loadRecursively(); diff --git a/test/sorting_mode_test.dart b/test/sorting_mode_test.dart index 6ded98d3..d554fd75 100644 --- a/test/sorting_mode_test.dart +++ b/test/sorting_mode_test.dart @@ -21,7 +21,8 @@ void main() { n4.created = null; var notes = [n1, n2, n3, n4]; - var sortFn = SortingMode.Created.sortingFunction(); + var sortFn = SortingMode(SortingField.Created, SortingOrder.Descending) + .sortingFunction(); notes.sort(sortFn); expect(notes[0], n2); @@ -45,7 +46,8 @@ void main() { n4.modified = null; var notes = [n1, n2, n3, n4]; - var sortFn = SortingMode.Modified.sortingFunction(); + var sortFn = SortingMode(SortingField.Modified, SortingOrder.Descending) + .sortingFunction(); notes.sort(sortFn); expect(notes[0], n2);