From 5c04bf204e8ffa64e40e1f216784eb6c0c7a7c7d Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Thu, 14 May 2020 16:04:33 +0200 Subject: [PATCH] Add Tags sidebar Fixes #114 This was there because I couldn't figure out how to configure Billing in iOS, now that it is done, I can have the pro mode in iOS as well. --- assets/langs/en.yaml | 2 + lib/app.dart | 5 ++- lib/core/flattened_notes_folder.dart | 31 +++++++++++++-- lib/features.dart | 1 - lib/screens/note_editor.dart | 1 - lib/screens/settings_editors.dart | 34 ++++++++-------- lib/screens/tag_listing.dart | 59 ++++++++++++++++++++++++++++ lib/widgets/app_drawer.dart | 22 ++++++++--- 8 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 lib/screens/tag_listing.dart diff --git a/assets/langs/en.yaml b/assets/langs/en.yaml index 7499fbcc..3823d923 100644 --- a/assets/langs/en.yaml +++ b/assets/langs/en.yaml @@ -54,6 +54,8 @@ screens: delete: Delete Folder decoration: Folder Name empty: Please enter a name + tags: + title: Tags widgets: rename: yes: Rename diff --git a/lib/app.dart b/lib/app.dart index c99a7d51..6614eabf 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,6 +6,7 @@ import 'package:firebase_analytics/observer.dart'; import 'package:flutter/material.dart'; import 'package:gitjournal/analytics.dart'; import 'package:gitjournal/screens/folder_listing.dart'; +import 'package:gitjournal/screens/tag_listing.dart'; import 'package:gitjournal/screens/note_editor.dart'; import 'package:gitjournal/screens/purchase_screen.dart'; import 'package:gitjournal/screens/purchase_thankyou_screen.dart'; @@ -293,7 +294,7 @@ class _JournalAppState extends State { //debugShowMaterialGrid: true, onGenerateRoute: (settings) { var route = settings.name; - if (route == '/folders') { + if (route == '/folders' || route == '/tags') { return PageRouteBuilder( settings: settings, pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer), @@ -320,6 +321,8 @@ class _JournalAppState extends State { return HomeScreen(); case '/folders': return FolderListingScreen(); + case '/tags': + return TagListingScreen(); case '/settings': return SettingsScreen(); case '/setupRemoteGit': diff --git a/lib/core/flattened_notes_folder.dart b/lib/core/flattened_notes_folder.dart index 874be6c6..ce997164 100644 --- a/lib/core/flattened_notes_folder.dart +++ b/lib/core/flattened_notes_folder.dart @@ -2,13 +2,17 @@ import 'package:gitjournal/core/note.dart'; import 'package:gitjournal/core/notes_folder.dart'; import 'package:gitjournal/core/notes_folder_notifier.dart'; +typedef NotesFilter = bool Function(Note note); + class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder { final NotesFolder _parentFolder; + final NotesFilter filter; + final String title; var _notes = []; var _folders = []; - FlattenedNotesFolder(this._parentFolder) { + FlattenedNotesFolder(this._parentFolder, {this.filter, this.title}) { _addFolder(_parentFolder); } @@ -57,11 +61,17 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder { } void _noteAdded(int _, Note note) { + if (filter != null && !filter(note)) { + return; + } _notes.add(note); notifyNoteAdded(-1, note); } void _noteRemoved(int _, Note note) { + if (filter != null && !filter(note)) { + return; + } var i = _notes.indexWhere((n) => n.filePath == note.filePath); assert(i != -1); @@ -70,7 +80,22 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder { } void _noteModified(int i, Note note) { - notifyNoteModified(-1, note); + if (filter == null) { + notifyNoteModified(-1, note); + return; + } + + if (_notes.contains(note)) { + if (filter(note)) { + notifyNoteModified(-1, note); + } else { + _noteRemoved(-1, note); + } + } else { + if (filter(note)) { + _noteAdded(-1, note); + } + } } @override @@ -97,7 +122,7 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder { } @override - String get name => "All Notes"; + String get name => title ?? "All Notes"; @override NotesFolderConfig get config { diff --git a/lib/features.dart b/lib/features.dart index e0ba859e..51d98688 100644 --- a/lib/features.dart +++ b/lib/features.dart @@ -1,4 +1,3 @@ class Features { static bool perFolderConfig = false; - static bool purchaseProModeAvailable = true; } diff --git a/lib/screens/note_editor.dart b/lib/screens/note_editor.dart index db547a67..facaad89 100644 --- a/lib/screens/note_editor.dart +++ b/lib/screens/note_editor.dart @@ -5,7 +5,6 @@ import 'package:collection/collection.dart'; import 'package:gitjournal/core/note.dart'; import 'package:gitjournal/core/md_yaml_doc.dart'; -import 'package:gitjournal/core/notes_folder.dart'; import 'package:gitjournal/core/notes_folder_fs.dart'; import 'package:gitjournal/editors/journal_editor.dart'; import 'package:gitjournal/editors/markdown_editor.dart'; diff --git a/lib/screens/settings_editors.dart b/lib/screens/settings_editors.dart index 9c0848ec..d278d6a0 100644 --- a/lib/screens/settings_editors.dart +++ b/lib/screens/settings_editors.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:gitjournal/features.dart'; import 'package:gitjournal/screens/settings_screen.dart'; import 'package:gitjournal/settings.dart'; import 'package:gitjournal/screens/settings_widgets.dart'; @@ -48,25 +47,24 @@ class SettingsEditorsScreenState extends State { setState(() {}); }, ), - if (Features.purchaseProModeAvailable) SettingsHeader("Journal Editor"), - if (Features.purchaseProModeAvailable) - ProSettingOverlay( - child: ListTile( - title: const Text("Default Folder"), - subtitle: Text(defaultNewFolder), - onTap: () async { - var destFolder = await showDialog( - context: context, - builder: (context) => FolderSelectionDialog(), - ); + SettingsHeader("Journal Editor"), + ProSettingOverlay( + child: ListTile( + title: const Text("Default Folder"), + subtitle: Text(defaultNewFolder), + onTap: () async { + var destFolder = await showDialog( + context: context, + builder: (context) => FolderSelectionDialog(), + ); - Settings.instance.journalEditordefaultNewNoteFolderSpec = - destFolder != null ? destFolder.pathSpec() : ""; - Settings.instance.save(); - setState(() {}); - }, - ), + Settings.instance.journalEditordefaultNewNoteFolderSpec = + destFolder != null ? destFolder.pathSpec() : ""; + Settings.instance.save(); + setState(() {}); + }, ), + ), ]); return Scaffold( diff --git a/lib/screens/tag_listing.dart b/lib/screens/tag_listing.dart new file mode 100644 index 00000000..3351a2ab --- /dev/null +++ b/lib/screens/tag_listing.dart @@ -0,0 +1,59 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:gitjournal/core/flattened_notes_folder.dart'; +import 'package:gitjournal/core/note.dart'; +import 'package:gitjournal/core/notes_folder_fs.dart'; +import 'package:gitjournal/screens/folder_view.dart'; +import 'package:gitjournal/widgets/app_bar_menu_button.dart'; +import 'package:gitjournal/widgets/app_drawer.dart'; +import 'package:provider/provider.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class TagListingScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + var rootFolder = Provider.of(context); + var allTags = rootFolder.getNoteTagsRecursively(); + var allTagsSorted = SplayTreeSet.from(allTags); + + var listView = ListView( + children: [ + for (var tag in allTagsSorted) _buildTagTile(context, tag), + ], + ); + + return Scaffold( + appBar: AppBar( + title: Text(tr('screens.tags.title')), + leading: GJAppBarMenuButton(), + ), + body: Scrollbar(child: listView), + drawer: AppDrawer(), + ); + } + + Widget _buildTagTile(BuildContext context, String tag) { + var theme = Theme.of(context); + var titleColor = theme.textTheme.headline1.color; + + return ListTile( + leading: FaIcon(FontAwesomeIcons.tag, color: titleColor), + title: Text(tag), + onTap: () { + var route = MaterialPageRoute(builder: (context) { + var rootFolder = Provider.of(context); + var folder = FlattenedNotesFolder( + rootFolder, + filter: (Note n) => n.tags.contains(tag), + title: tag, + ); + + return FolderView(notesFolder: folder); + }); + Navigator.of(context).push(route); + }, + ); + } +} diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 2d2f3dda..473f841c 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_email_sender/flutter_email_sender.dart'; -import 'package:gitjournal/features.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:gitjournal/settings.dart'; import 'package:gitjournal/utils/logger.dart'; import 'package:launch_review/launch_review.dart'; @@ -65,7 +65,7 @@ class AppDrawer extends StatelessWidget { ), ), if (setupGitButton != null) ...[setupGitButton, divider], - if (Features.purchaseProModeAvailable && !Settings.instance.proMode) + if (!Settings.instance.proMode) _buildDrawerTile( context, icon: Icons.power, @@ -79,8 +79,7 @@ class AppDrawer extends StatelessWidget { ); }, ), - if (Features.purchaseProModeAvailable && !Settings.instance.proMode) - divider, + if (!Settings.instance.proMode) divider, _buildDrawerTile( context, icon: Icons.note, @@ -95,6 +94,14 @@ class AppDrawer extends StatelessWidget { onTap: () => _navTopLevel(context, '/folders'), selected: currentRoute == "/folders", ), + _buildDrawerTile( + context, + icon: FontAwesomeIcons.tag, + isFontAwesome: true, + title: "Tags", + onTap: () => _navTopLevel(context, '/tags'), + selected: currentRoute == "/tags", + ), divider, _buildDrawerTile( context, @@ -205,6 +212,7 @@ class AppDrawer extends StatelessWidget { @required IconData icon, @required String title, @required Function onTap, + bool isFontAwesome = false, bool selected = false, }) { var theme = Theme.of(context); @@ -213,8 +221,12 @@ class AppDrawer extends StatelessWidget { color: selected ? theme.accentColor : listTileTheme.textColor, ); + var iconW = !isFontAwesome + ? Icon(icon, color: textStyle.color) + : FaIcon(icon, color: textStyle.color); + var tile = ListTile( - leading: Icon(icon, color: textStyle.color), + leading: iconW, title: Text(title, style: textStyle), onTap: onTap, selected: selected,