Add basic tag support

This allows you to be modify the tags from the NoteEditor. Related to
 #114. We still need to add some way to filter the notes by tag. Also,
the current tag editor doesn't show the tags from the other notes.
This commit is contained in:
Vishesh Handa
2020-05-13 01:01:40 +02:00
parent 52fd859990
commit 2847e3f60f
11 changed files with 189 additions and 11 deletions

View File

@ -30,4 +30,5 @@ editors:
takePhoto: Take Photo
addImage: Add Image from Gallery
editFileName: Edit File Name
tags: Edit Tags
pro: Pro

View File

@ -1,4 +1,7 @@
import 'dart:collection';
import 'package:collection/collection.dart';
Function _deepEq = const DeepCollectionEquality().equals;
class MdYamlDoc {
String body = "";
@ -24,13 +27,7 @@ class MdYamlDoc {
other is MdYamlDoc &&
runtimeType == other.runtimeType &&
body == other.body &&
_equalMaps(props, other.props);
static bool _equalMaps(Map a, Map b) {
if (a.length != b.length) return false;
return a.keys
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
}
_deepEq(props, other.props);
@override
String toString() {

View File

@ -130,6 +130,7 @@ class Note with NotesNotifier {
}
set tags(Set<String> tags) {
assert(tags != null);
if (!canHaveMetadata) return;
_tags = tags;

View File

@ -23,6 +23,8 @@ class ChecklistEditor extends StatefulWidget implements Editor {
@override
final NoteCallback renameNoteSelected;
@override
final NoteCallback editTagsSelected;
@override
final NoteCallback moveNoteToFolderSelected;
@override
final NoteCallback discardChangesSelected;
@ -37,6 +39,7 @@ class ChecklistEditor extends StatefulWidget implements Editor {
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
@required this.editTagsSelected,
@required this.moveNoteToFolderSelected,
@required this.discardChangesSelected,
@required this.isNewNote,

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes_folder_fs.dart';
import 'package:gitjournal/error_reporting.dart';
@ -16,6 +17,7 @@ abstract class Editor {
NoteCallback get noteEditorChooserSelected;
NoteCallback get exitEditorSelected;
NoteCallback get renameNoteSelected;
NoteCallback get editTagsSelected;
NoteCallback get moveNoteToFolderSelected;
NoteCallback get discardChangesSelected;
}
@ -227,6 +229,16 @@ Widget _buildBottomMenuSheet(
Share.share(note.body);
},
),
ListTile(
leading: const FaIcon(FontAwesomeIcons.tag),
title: Text(tr('editors.common.tags')),
onTap: () {
var note = editorState.getNote();
Navigator.of(context).pop();
editor.editTagsSelected(note);
},
),
ListTile(
leading: Icon(Icons.edit),
title: Text(tr('editors.common.editFileName')),

View File

@ -19,6 +19,8 @@ class JournalEditor extends StatefulWidget implements Editor {
@override
final NoteCallback renameNoteSelected;
@override
final NoteCallback editTagsSelected;
@override
final NoteCallback moveNoteToFolderSelected;
@override
final NoteCallback discardChangesSelected;
@ -33,6 +35,7 @@ class JournalEditor extends StatefulWidget implements Editor {
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
@required this.editTagsSelected,
@required this.moveNoteToFolderSelected,
@required this.discardChangesSelected,
this.isNewNote = false,

View File

@ -21,6 +21,8 @@ class MarkdownEditor extends StatefulWidget implements Editor {
@override
final NoteCallback renameNoteSelected;
@override
final NoteCallback editTagsSelected;
@override
final NoteCallback moveNoteToFolderSelected;
@override
final NoteCallback discardChangesSelected;
@ -35,6 +37,7 @@ class MarkdownEditor extends StatefulWidget implements Editor {
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
@required this.editTagsSelected,
@required this.moveNoteToFolderSelected,
@required this.discardChangesSelected,
@required this.isNewNote,

View File

@ -19,6 +19,8 @@ class RawEditor extends StatefulWidget implements Editor {
@override
final NoteCallback renameNoteSelected;
@override
final NoteCallback editTagsSelected;
@override
final NoteCallback moveNoteToFolderSelected;
@override
final NoteCallback discardChangesSelected;
@ -33,6 +35,7 @@ class RawEditor extends StatefulWidget implements Editor {
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
@required this.editTagsSelected,
@required this.moveNoteToFolderSelected,
@required this.discardChangesSelected,
@required this.isNewNote,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/md_yaml_doc.dart';
@ -8,8 +9,10 @@ import 'package:gitjournal/editors/markdown_editor.dart';
import 'package:gitjournal/editors/raw_editor.dart';
import 'package:gitjournal/editors/checklist_editor.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/utils/logger.dart';
import 'package:gitjournal/widgets/folder_selection_dialog.dart';
import 'package:gitjournal/widgets/note_editor_selector.dart';
import 'package:gitjournal/widgets/note_tag_editor.dart';
import 'package:gitjournal/widgets/rename_dialog.dart';
import 'package:provider/provider.dart';
@ -101,6 +104,7 @@ class NoteEditorState extends State<NoteEditor> {
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
editTagsSelected: _editTagsSelected,
moveNoteToFolderSelected: _moveNoteToFolderSelected,
discardChangesSelected: _discardChangesSelected,
isNewNote: _isNewNote,
@ -114,6 +118,7 @@ class NoteEditorState extends State<NoteEditor> {
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
editTagsSelected: _editTagsSelected,
moveNoteToFolderSelected: _moveNoteToFolderSelected,
discardChangesSelected: _discardChangesSelected,
isNewNote: _isNewNote,
@ -127,6 +132,7 @@ class NoteEditorState extends State<NoteEditor> {
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
editTagsSelected: _editTagsSelected,
moveNoteToFolderSelected: _moveNoteToFolderSelected,
discardChangesSelected: _discardChangesSelected,
isNewNote: _isNewNote,
@ -140,6 +146,7 @@ class NoteEditorState extends State<NoteEditor> {
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
editTagsSelected: _editTagsSelected,
moveNoteToFolderSelected: _moveNoteToFolderSelected,
discardChangesSelected: _discardChangesSelected,
isNewNote: _isNewNote,
@ -250,9 +257,9 @@ class NoteEditorState extends State<NoteEditor> {
bool hasBeenModified = newSimplified != originalSimplified;
if (hasBeenModified) {
print("Note modified");
print("Original: $originalSimplified");
print("New: $newSimplified");
Log.d("Note modified");
Log.d("Original: $originalSimplified");
Log.d("New: $newSimplified");
return true;
}
}
@ -262,7 +269,7 @@ class NoteEditorState extends State<NoteEditor> {
void _saveNote(Note note) {
if (!_noteModified(note)) return;
print("Note modified - saving");
Log.d("Note modified - saving");
var stateContainer = Provider.of<StateContainer>(context, listen: false);
_isNewNote ? stateContainer.addNote(note) : stateContainer.updateNote(note);
}
@ -334,4 +341,24 @@ class NoteEditorState extends State<NoteEditor> {
],
);
}
void _editTagsSelected(Note _note) async {
Log.i("Note Tags: ${_note.tags}");
var route = MaterialPageRoute(
builder: (context) => NoteTagEditor(
selectedTags: note.tags,
allTags: note.tags,
),
);
var newTags = await Navigator.of(context).push(route);
assert(newTags != null);
Function eq = const SetEquality().equals;
if (!eq(note.tags, newTags)) {
setState(() {
Log.i("Settings tags to: $newTags");
note.tags = newTags;
});
}
}
}

View File

@ -0,0 +1,104 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class NoteTagEditor extends StatefulWidget {
final Set<String> selectedTags;
final Set<String> allTags;
NoteTagEditor({@required this.selectedTags, @required this.allTags});
@override
_NoteTagEditorState createState() => _NoteTagEditorState();
}
class _NoteTagEditorState extends State<NoteTagEditor> {
TextEditingController _textController;
Set<String> _selectedTags;
Set<String> _allTags;
@override
void initState() {
super.initState();
_selectedTags = Set<String>.from(widget.selectedTags);
_allTags = Set<String>.from(widget.allTags);
_textController = TextEditingController();
_textController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop(_selectedTags);
},
),
title: TextField(
controller: _textController,
style: theme.textTheme.headline6,
decoration: InputDecoration(
border: InputBorder.none,
hintText: tr('editors.common.tags'),
hintStyle: theme.inputDecorationTheme.hintStyle,
),
),
),
body: buildView(_textController.text),
);
}
Widget buildView(String query) {
var q = query.toLowerCase();
return ListView(
children: <Widget>[
if (query.isNotEmpty && !_allTags.contains(query)) _buildAddTag(query),
for (var tag in _allTags)
if (tag.toLowerCase().contains(q)) _buildTagTile(tag),
],
padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),
);
}
Widget _buildTagTile(String tag) {
var containsTag = _selectedTags.contains(tag);
var _onTap = () {
setState(() {
if (containsTag) {
_selectedTags.remove(tag);
} else {
_selectedTags.add(tag);
}
});
};
return ListTile(
leading: const FaIcon(FontAwesomeIcons.tag),
title: Text(tag),
trailing: Checkbox(value: containsTag, onChanged: (_) => _onTap()),
onTap: _onTap,
);
}
Widget _buildAddTag(String tag) {
return ListTile(
leading: const Icon(Icons.add),
title: Text(tag),
onTap: () {
setState(() {
_selectedTags.add(tag);
_allTags.add(tag);
_textController.text = "";
});
},
);
}
}

View File

@ -0,0 +1,24 @@
import 'dart:collection';
import 'package:gitjournal/core/md_yaml_doc.dart';
import 'package:test/test.dart';
void main() {
test('Equality', () {
// ignore: prefer_collection_literals
var aProps = LinkedHashMap<String, dynamic>();
aProps['a'] = 1;
aProps['title'] = "Foo";
aProps['list'] = ["Foo", "Bar", 1];
// ignore: prefer_collection_literals
var bProps = LinkedHashMap<String, dynamic>();
bProps['a'] = 1;
bProps['title'] = "Foo";
bProps['list'] = ["Foo", "Bar", 1];
var a = MdYamlDoc("a", aProps);
var b = MdYamlDoc("a", bProps);
expect(a, b);
});
}