mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-08-02 20:17:54 +08:00
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:
@ -30,4 +30,5 @@ editors:
|
||||
takePhoto: Take Photo
|
||||
addImage: Add Image from Gallery
|
||||
editFileName: Edit File Name
|
||||
tags: Edit Tags
|
||||
pro: Pro
|
||||
|
@ -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() {
|
||||
|
@ -130,6 +130,7 @@ class Note with NotesNotifier {
|
||||
}
|
||||
|
||||
set tags(Set<String> tags) {
|
||||
assert(tags != null);
|
||||
if (!canHaveMetadata) return;
|
||||
|
||||
_tags = tags;
|
||||
|
@ -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,
|
||||
|
@ -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')),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
lib/widgets/note_tag_editor.dart
Normal file
104
lib/widgets/note_tag_editor.dart
Normal 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 = "";
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
24
test/md_yaml_doc_test.dart
Normal file
24
test/md_yaml_doc_test.dart
Normal 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);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user