mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-29 18:38:36 +08:00
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.
This commit is contained in:
@ -54,6 +54,8 @@ screens:
|
|||||||
delete: Delete Folder
|
delete: Delete Folder
|
||||||
decoration: Folder Name
|
decoration: Folder Name
|
||||||
empty: Please enter a name
|
empty: Please enter a name
|
||||||
|
tags:
|
||||||
|
title: Tags
|
||||||
widgets:
|
widgets:
|
||||||
rename:
|
rename:
|
||||||
yes: Rename
|
yes: Rename
|
||||||
|
@ -6,6 +6,7 @@ import 'package:firebase_analytics/observer.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gitjournal/analytics.dart';
|
import 'package:gitjournal/analytics.dart';
|
||||||
import 'package:gitjournal/screens/folder_listing.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/note_editor.dart';
|
||||||
import 'package:gitjournal/screens/purchase_screen.dart';
|
import 'package:gitjournal/screens/purchase_screen.dart';
|
||||||
import 'package:gitjournal/screens/purchase_thankyou_screen.dart';
|
import 'package:gitjournal/screens/purchase_thankyou_screen.dart';
|
||||||
@ -293,7 +294,7 @@ class _JournalAppState extends State<JournalApp> {
|
|||||||
//debugShowMaterialGrid: true,
|
//debugShowMaterialGrid: true,
|
||||||
onGenerateRoute: (settings) {
|
onGenerateRoute: (settings) {
|
||||||
var route = settings.name;
|
var route = settings.name;
|
||||||
if (route == '/folders') {
|
if (route == '/folders' || route == '/tags') {
|
||||||
return PageRouteBuilder(
|
return PageRouteBuilder(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer),
|
pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer),
|
||||||
@ -320,6 +321,8 @@ class _JournalAppState extends State<JournalApp> {
|
|||||||
return HomeScreen();
|
return HomeScreen();
|
||||||
case '/folders':
|
case '/folders':
|
||||||
return FolderListingScreen();
|
return FolderListingScreen();
|
||||||
|
case '/tags':
|
||||||
|
return TagListingScreen();
|
||||||
case '/settings':
|
case '/settings':
|
||||||
return SettingsScreen();
|
return SettingsScreen();
|
||||||
case '/setupRemoteGit':
|
case '/setupRemoteGit':
|
||||||
|
@ -2,13 +2,17 @@ import 'package:gitjournal/core/note.dart';
|
|||||||
import 'package:gitjournal/core/notes_folder.dart';
|
import 'package:gitjournal/core/notes_folder.dart';
|
||||||
import 'package:gitjournal/core/notes_folder_notifier.dart';
|
import 'package:gitjournal/core/notes_folder_notifier.dart';
|
||||||
|
|
||||||
|
typedef NotesFilter = bool Function(Note note);
|
||||||
|
|
||||||
class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder {
|
class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder {
|
||||||
final NotesFolder _parentFolder;
|
final NotesFolder _parentFolder;
|
||||||
|
final NotesFilter filter;
|
||||||
|
final String title;
|
||||||
|
|
||||||
var _notes = <Note>[];
|
var _notes = <Note>[];
|
||||||
var _folders = <NotesFolder>[];
|
var _folders = <NotesFolder>[];
|
||||||
|
|
||||||
FlattenedNotesFolder(this._parentFolder) {
|
FlattenedNotesFolder(this._parentFolder, {this.filter, this.title}) {
|
||||||
_addFolder(_parentFolder);
|
_addFolder(_parentFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +61,17 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _noteAdded(int _, Note note) {
|
void _noteAdded(int _, Note note) {
|
||||||
|
if (filter != null && !filter(note)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_notes.add(note);
|
_notes.add(note);
|
||||||
notifyNoteAdded(-1, note);
|
notifyNoteAdded(-1, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _noteRemoved(int _, Note note) {
|
void _noteRemoved(int _, Note note) {
|
||||||
|
if (filter != null && !filter(note)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var i = _notes.indexWhere((n) => n.filePath == note.filePath);
|
var i = _notes.indexWhere((n) => n.filePath == note.filePath);
|
||||||
assert(i != -1);
|
assert(i != -1);
|
||||||
|
|
||||||
@ -70,7 +80,22 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _noteModified(int i, Note note) {
|
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
|
@override
|
||||||
@ -97,7 +122,7 @@ class FlattenedNotesFolder with NotesFolderNotifier implements NotesFolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => "All Notes";
|
String get name => title ?? "All Notes";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NotesFolderConfig get config {
|
NotesFolderConfig get config {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
class Features {
|
class Features {
|
||||||
static bool perFolderConfig = false;
|
static bool perFolderConfig = false;
|
||||||
static bool purchaseProModeAvailable = true;
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import 'package:collection/collection.dart';
|
|||||||
|
|
||||||
import 'package:gitjournal/core/note.dart';
|
import 'package:gitjournal/core/note.dart';
|
||||||
import 'package:gitjournal/core/md_yaml_doc.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/core/notes_folder_fs.dart';
|
||||||
import 'package:gitjournal/editors/journal_editor.dart';
|
import 'package:gitjournal/editors/journal_editor.dart';
|
||||||
import 'package:gitjournal/editors/markdown_editor.dart';
|
import 'package:gitjournal/editors/markdown_editor.dart';
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gitjournal/features.dart';
|
|
||||||
import 'package:gitjournal/screens/settings_screen.dart';
|
import 'package:gitjournal/screens/settings_screen.dart';
|
||||||
import 'package:gitjournal/settings.dart';
|
import 'package:gitjournal/settings.dart';
|
||||||
import 'package:gitjournal/screens/settings_widgets.dart';
|
import 'package:gitjournal/screens/settings_widgets.dart';
|
||||||
@ -48,25 +47,24 @@ class SettingsEditorsScreenState extends State<SettingsEditorsScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (Features.purchaseProModeAvailable) SettingsHeader("Journal Editor"),
|
SettingsHeader("Journal Editor"),
|
||||||
if (Features.purchaseProModeAvailable)
|
ProSettingOverlay(
|
||||||
ProSettingOverlay(
|
child: ListTile(
|
||||||
child: ListTile(
|
title: const Text("Default Folder"),
|
||||||
title: const Text("Default Folder"),
|
subtitle: Text(defaultNewFolder),
|
||||||
subtitle: Text(defaultNewFolder),
|
onTap: () async {
|
||||||
onTap: () async {
|
var destFolder = await showDialog<NotesFolderFS>(
|
||||||
var destFolder = await showDialog<NotesFolderFS>(
|
context: context,
|
||||||
context: context,
|
builder: (context) => FolderSelectionDialog(),
|
||||||
builder: (context) => FolderSelectionDialog(),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
Settings.instance.journalEditordefaultNewNoteFolderSpec =
|
Settings.instance.journalEditordefaultNewNoteFolderSpec =
|
||||||
destFolder != null ? destFolder.pathSpec() : "";
|
destFolder != null ? destFolder.pathSpec() : "";
|
||||||
Settings.instance.save();
|
Settings.instance.save();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
59
lib/screens/tag_listing.dart
Normal file
59
lib/screens/tag_listing.dart
Normal file
@ -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<NotesFolderFS>(context);
|
||||||
|
var allTags = rootFolder.getNoteTagsRecursively();
|
||||||
|
var allTagsSorted = SplayTreeSet<String>.from(allTags);
|
||||||
|
|
||||||
|
var listView = ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
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<NotesFolderFS>(context);
|
||||||
|
var folder = FlattenedNotesFolder(
|
||||||
|
rootFolder,
|
||||||
|
filter: (Note n) => n.tags.contains(tag),
|
||||||
|
title: tag,
|
||||||
|
);
|
||||||
|
|
||||||
|
return FolderView(notesFolder: folder);
|
||||||
|
});
|
||||||
|
Navigator.of(context).push(route);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_email_sender/flutter_email_sender.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/settings.dart';
|
||||||
import 'package:gitjournal/utils/logger.dart';
|
import 'package:gitjournal/utils/logger.dart';
|
||||||
import 'package:launch_review/launch_review.dart';
|
import 'package:launch_review/launch_review.dart';
|
||||||
@ -65,7 +65,7 @@ class AppDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (setupGitButton != null) ...[setupGitButton, divider],
|
if (setupGitButton != null) ...[setupGitButton, divider],
|
||||||
if (Features.purchaseProModeAvailable && !Settings.instance.proMode)
|
if (!Settings.instance.proMode)
|
||||||
_buildDrawerTile(
|
_buildDrawerTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.power,
|
icon: Icons.power,
|
||||||
@ -79,8 +79,7 @@ class AppDrawer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (Features.purchaseProModeAvailable && !Settings.instance.proMode)
|
if (!Settings.instance.proMode) divider,
|
||||||
divider,
|
|
||||||
_buildDrawerTile(
|
_buildDrawerTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.note,
|
icon: Icons.note,
|
||||||
@ -95,6 +94,14 @@ class AppDrawer extends StatelessWidget {
|
|||||||
onTap: () => _navTopLevel(context, '/folders'),
|
onTap: () => _navTopLevel(context, '/folders'),
|
||||||
selected: currentRoute == "/folders",
|
selected: currentRoute == "/folders",
|
||||||
),
|
),
|
||||||
|
_buildDrawerTile(
|
||||||
|
context,
|
||||||
|
icon: FontAwesomeIcons.tag,
|
||||||
|
isFontAwesome: true,
|
||||||
|
title: "Tags",
|
||||||
|
onTap: () => _navTopLevel(context, '/tags'),
|
||||||
|
selected: currentRoute == "/tags",
|
||||||
|
),
|
||||||
divider,
|
divider,
|
||||||
_buildDrawerTile(
|
_buildDrawerTile(
|
||||||
context,
|
context,
|
||||||
@ -205,6 +212,7 @@ class AppDrawer extends StatelessWidget {
|
|||||||
@required IconData icon,
|
@required IconData icon,
|
||||||
@required String title,
|
@required String title,
|
||||||
@required Function onTap,
|
@required Function onTap,
|
||||||
|
bool isFontAwesome = false,
|
||||||
bool selected = false,
|
bool selected = false,
|
||||||
}) {
|
}) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
@ -213,8 +221,12 @@ class AppDrawer extends StatelessWidget {
|
|||||||
color: selected ? theme.accentColor : listTileTheme.textColor,
|
color: selected ? theme.accentColor : listTileTheme.textColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var iconW = !isFontAwesome
|
||||||
|
? Icon(icon, color: textStyle.color)
|
||||||
|
: FaIcon(icon, color: textStyle.color);
|
||||||
|
|
||||||
var tile = ListTile(
|
var tile = ListTile(
|
||||||
leading: Icon(icon, color: textStyle.color),
|
leading: iconW,
|
||||||
title: Text(title, style: textStyle),
|
title: Text(title, style: textStyle),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
|
Reference in New Issue
Block a user