Add the concept of Editors

* We no longer have a separate editing and browsing view - This does
mean we loose the ability to quick flip between notes by swiping.
However, this is more how a note editor would behave. I do later want to
add that capability back.

* We have 2 editors for now - Markdown and Raw. By default we use the
Markdown editor which can be toggled between Preview / Edit mode.

I later want to add a rich text editor and a todo editor as well.
This commit is contained in:
Vishesh Handa
2020-01-28 23:43:47 +01:00
parent 127faa39cf
commit 3f40a2992a
11 changed files with 584 additions and 458 deletions

View File

@ -156,9 +156,17 @@ class Note with ChangeNotifier implements Comparable<Note> {
}
void rename(String newName) {
// Do not let the user rename it to a non-markdown file
if (!newName.toLowerCase().endsWith('.md')) {
newName += '.md';
}
var parentDirName = p.dirname(filePath);
var newFilePath = p.join(parentDirName, newName);
File(filePath).renameSync(newFilePath);
if (_loadState != NoteLoadState.None) {
// for new notes
File(filePath).renameSync(newFilePath);
}
_filePath = newFilePath;
notifyListeners();

View File

@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/widgets/note_viewer.dart';
typedef NoteCallback = void Function(Note);
enum DropDownChoices { Rename }
class MarkdownEditor extends StatefulWidget {
final Note note;
final NoteCallback noteDeletionSelected;
final NoteCallback noteEditorChooserSelected;
final NoteCallback exitEditorSelected;
final NoteCallback renameNoteSelected;
final bool openEditorByDefault;
MarkdownEditor({
Key key,
@required this.note,
@required this.noteDeletionSelected,
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
this.openEditorByDefault = false,
}) : super(key: key);
@override
MarkdownEditorState createState() {
return MarkdownEditorState(note);
}
}
class MarkdownEditorState extends State<MarkdownEditor> {
Note note;
TextEditingController _textController = TextEditingController();
TextEditingController _titleTextController = TextEditingController();
bool editingMode = false;
MarkdownEditorState(this.note) {
_textController = TextEditingController(text: note.body);
_titleTextController = TextEditingController(text: note.title);
}
@override
void initState() {
super.initState();
editingMode = widget.openEditorByDefault;
}
@override
void dispose() {
_textController.dispose();
_titleTextController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var editor = Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_NoteTitleEditor(_titleTextController),
_NoteBodyEditor(_textController),
],
),
),
);
Widget body = editingMode ? editor : NoteViewer(note: note);
var fab = FloatingActionButton(
child: editingMode
? const Icon(Icons.remove_red_eye)
: const Icon(Icons.edit),
onPressed: _switchMode,
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
key: const ValueKey("NewEntry"),
icon: const Icon(Icons.check),
onPressed: () {
_updateNote();
widget.exitEditorSelected(note);
},
),
actions: <Widget>[
IconButton(
icon: editingMode
? const Icon(Icons.remove_red_eye)
: const Icon(Icons.edit),
onPressed: _switchMode,
),
IconButton(
icon: const Icon(Icons.library_books),
onPressed: () {
_updateNote();
widget.noteEditorChooserSelected(note);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_updateNote();
widget.noteDeletionSelected(note);
},
),
PopupMenuButton<DropDownChoices>(
onSelected: (DropDownChoices choice) {
_updateNote();
widget.renameNoteSelected(note);
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<DropDownChoices>>[
const PopupMenuItem<DropDownChoices>(
value: DropDownChoices.Rename,
child: Text('Edit File Name'),
),
],
),
],
),
body: body,
floatingActionButton: fab,
);
}
void _switchMode() {
setState(() {
editingMode = !editingMode;
_updateNote();
});
}
void _updateNote() {
note.title = _titleTextController.text.trim();
note.body = _textController.text.trim();
}
Note getNote() {
_updateNote();
return note;
}
}
class _NoteBodyEditor extends StatelessWidget {
final TextEditingController textController;
_NoteBodyEditor(this.textController);
@override
Widget build(BuildContext context) {
var style = Theme.of(context).textTheme.subhead;
return TextField(
autofocus: true,
autocorrect: false,
keyboardType: TextInputType.multiline,
maxLines: null,
style: style,
decoration: InputDecoration(
hintText: 'Write here',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
scrollPadding: const EdgeInsets.all(0.0),
);
}
}
class _NoteTitleEditor extends StatelessWidget {
final TextEditingController textController;
_NoteTitleEditor(this.textController);
@override
Widget build(BuildContext context) {
var style = Theme.of(context).textTheme.title;
return TextField(
keyboardType: TextInputType.text,
maxLines: 1,
style: style,
decoration: InputDecoration(
hintText: 'Title',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
);
}
}

136
lib/editors/raw_editor.dart Normal file
View File

@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/note_data_serializers.dart';
typedef NoteCallback = void Function(Note);
enum DropDownChoices { Rename }
class RawEditor extends StatefulWidget {
final Note note;
final NoteCallback noteDeletionSelected;
final NoteCallback noteEditorChooserSelected;
final NoteCallback exitEditorSelected;
final NoteCallback renameNoteSelected;
RawEditor({
Key key,
@required this.note,
@required this.noteDeletionSelected,
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
}) : super(key: key);
@override
RawEditorState createState() {
return RawEditorState(note);
}
}
class RawEditorState extends State<RawEditor> {
Note note;
TextEditingController _textController = TextEditingController();
final serializer = MarkdownYAMLSerializer();
RawEditorState(this.note) {
_textController = TextEditingController(text: serializer.encode(note.data));
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var editor = Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: _NoteEditor(_textController),
),
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
key: const ValueKey("NewEntry"),
icon: const Icon(Icons.check),
onPressed: () {
_updateNote();
widget.exitEditorSelected(note);
},
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.library_books),
onPressed: () {
_updateNote();
widget.noteEditorChooserSelected(note);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_updateNote();
widget.noteDeletionSelected(note);
},
),
PopupMenuButton<DropDownChoices>(
onSelected: (DropDownChoices choice) {
_updateNote();
widget.renameNoteSelected(note);
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<DropDownChoices>>[
const PopupMenuItem<DropDownChoices>(
value: DropDownChoices.Rename,
child: Text('Edit File Name'),
),
],
),
],
),
body: editor,
);
}
void _updateNote() {
note.data = serializer.decode(_textController.text);
}
Note getNote() {
_updateNote();
return note;
}
}
class _NoteEditor extends StatelessWidget {
final TextEditingController textController;
_NoteEditor(this.textController);
@override
Widget build(BuildContext context) {
var style =
Theme.of(context).textTheme.subhead.copyWith(fontFamily: "Roboto Mono");
return TextField(
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.multiline,
maxLines: null,
style: style,
decoration: InputDecoration(
hintText: 'Write here',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
scrollPadding: const EdgeInsets.all(0.0),
);
}
}

View File

@ -1,162 +0,0 @@
import 'package:fimber/fimber.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:page_transition/page_transition.dart';
import 'package:share/share.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/utils.dart';
import 'package:gitjournal/widgets/rename_dialog.dart';
import 'package:gitjournal/widgets/note_viewer.dart';
import 'journal_editor.dart';
enum NoteBrowserDropDownChoices { Rename }
class JournalBrowsingScreen extends StatefulWidget {
final List<Note> notes;
final int noteIndex;
const JournalBrowsingScreen({
@required this.notes,
@required this.noteIndex,
});
@override
JournalBrowsingScreenState createState() {
return JournalBrowsingScreenState(noteIndex: noteIndex);
}
}
class JournalBrowsingScreenState extends State<JournalBrowsingScreen> {
PageController pageController;
int currentPage;
JournalBrowsingScreenState({@required int noteIndex}) {
pageController = PageController(initialPage: noteIndex);
currentPage = noteIndex;
}
@override
Widget build(BuildContext context) {
var pageView = PageView.builder(
controller: pageController,
itemCount: widget.notes.length,
itemBuilder: (BuildContext context, int pos) {
var note = widget.notes[pos];
return NoteViewer(
key: ValueKey("Viewer_" + note.filePath),
note: widget.notes[pos],
);
},
onPageChanged: (int pageNum) {
setState(() {
currentPage = pageNum;
});
},
);
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
showDialog(context: context, builder: _buildAlertDialog);
},
),
IconButton(
icon: Icon(Icons.share),
onPressed: () {
Note note = widget.notes[_currentIndex()];
Share.share(note.body);
},
),
PopupMenuButton<NoteBrowserDropDownChoices>(
onSelected: (NoteBrowserDropDownChoices choice) async {
var note = widget.notes[currentPage];
switch (choice) {
case NoteBrowserDropDownChoices.Rename:
var fileName = await showDialog(
context: context,
builder: (_) => RenameDialog(
oldPath: note.filePath,
inputDecoration: 'File Name',
dialogTitle: "Rename File",
),
);
if (fileName is String) {
final container = StateContainer.of(context);
container.renameNote(note, fileName);
}
break;
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<NoteBrowserDropDownChoices>>[
const PopupMenuItem<NoteBrowserDropDownChoices>(
value: NoteBrowserDropDownChoices.Rename,
child: Text('Rename File'),
),
],
),
],
),
body: pageView,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.edit),
onPressed: () {
Note note = widget.notes[_currentIndex()];
Navigator.push(
context,
PageTransition(
type: PageTransitionType.fade,
child: JournalEditor.fromNote(note),
),
);
},
),
);
}
int _currentIndex() {
int currentIndex = pageController.page.round();
assert(currentIndex >= 0);
assert(currentIndex < widget.notes.length);
return currentIndex;
}
void _deleteNote(BuildContext context) {
final stateContainer = StateContainer.of(context);
var noteIndex = _currentIndex();
Note note = widget.notes[noteIndex];
stateContainer.removeNote(note);
Navigator.pop(context);
Fimber.d("Shwoing an undo snackbar");
showUndoDeleteSnackbar(context, stateContainer, note, noteIndex);
}
Widget _buildAlertDialog(BuildContext context) {
var title = "Are you sure you want to delete this Note?";
return AlertDialog(
content: Text(title),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
FlatButton(
onPressed: () {
Navigator.pop(context); // Alert box
_deleteNote(context);
},
child: const Text('Delete'),
),
],
);
}
}

View File

@ -1,273 +0,0 @@
import 'package:flutter/material.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes_folder.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/core/note_data.dart';
import 'package:gitjournal/core/note_data_serializers.dart';
import 'package:gitjournal/widgets/note_viewer.dart';
enum NoteEditorDropDownChoices { Discard, SwitchEditor }
class JournalEditor extends StatefulWidget {
final Note note;
final NotesFolder notesFolder;
JournalEditor.fromNote(this.note) : notesFolder = null;
JournalEditor.newNote(this.notesFolder) : note = null;
@override
JournalEditorState createState() {
if (note == null) {
return JournalEditorState.newNote(notesFolder);
} else {
return JournalEditorState.fromNote(note);
}
}
}
class JournalEditorState extends State<JournalEditor> {
Note note;
final bool newNote;
TextEditingController _textController = TextEditingController();
TextEditingController _titleTextController = TextEditingController();
bool rawEditor = false;
bool editingMode = false;
final serializer = MarkdownYAMLSerializer();
JournalEditorState.newNote(NotesFolder folder) : newNote = true {
note = Note.newNote(folder);
}
JournalEditorState.fromNote(this.note) : newNote = false {
_textController = TextEditingController(text: note.body);
_titleTextController = TextEditingController(text: note.title);
}
@override
void dispose() {
_textController.dispose();
_titleTextController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var markdownEditor = Column(
children: <Widget>[
if (!rawEditor) NoteTitleEditor(_titleTextController),
NoteMarkdownEditor(_textController, false),
],
);
var rawEditorWidget = NoteMarkdownEditor(_textController, true);
var editorW = rawEditor ? rawEditorWidget : markdownEditor;
var editor = Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(child: editorW),
);
Widget body = editingMode ? editor : NoteViewer(note: note);
var newJournalScreen = Scaffold(
appBar: AppBar(
leading: IconButton(
key: const ValueKey("NewEntry"),
icon: const Icon(Icons.check),
onPressed: () {
_saveNote(context);
Navigator.pop(context);
},
),
actions: <Widget>[
!rawEditor
? IconButton(
icon: editingMode
? Icon(Icons.remove_red_eye)
: Icon(Icons.edit),
onPressed: () {
setState(() {
editingMode = !editingMode;
});
},
)
: Container(),
PopupMenuButton<NoteEditorDropDownChoices>(
onSelected: (NoteEditorDropDownChoices choice) {
switch (choice) {
case NoteEditorDropDownChoices.Discard:
if (_noteModified()) {
showDialog(context: context, builder: _buildAlertDialog);
} else {
Navigator.pop(context);
}
break;
case NoteEditorDropDownChoices.SwitchEditor:
note.title = _titleTextController.text.trim();
setState(() {
if (rawEditor) {
rawEditor = false;
note.data = serializer.decode(_textController.text);
_textController.text = note.body;
_titleTextController.text = note.title;
} else {
rawEditor = true;
var noteData =
NoteData(_textController.text, note.data.props);
_textController.text = serializer.encode(noteData);
}
});
break;
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<NoteEditorDropDownChoices>>[
const PopupMenuItem<NoteEditorDropDownChoices>(
value: NoteEditorDropDownChoices.Discard,
child: Text('Discard'),
),
PopupMenuItem<NoteEditorDropDownChoices>(
value: NoteEditorDropDownChoices.SwitchEditor,
child: rawEditor
? const Text('Rich Editor')
: const Text('Raw Editor'),
),
],
),
],
),
body: body,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.check),
onPressed: () {
_saveNote(context);
Navigator.pop(context);
},
),
);
return WillPopScope(
onWillPop: () async {
_saveNote(context);
return true;
},
child: newJournalScreen,
);
}
Widget _buildAlertDialog(BuildContext context) {
var title = newNote
? "Do you want to discard this?"
: "Do you want to ignore the changes?";
var editText = newNote ? "Keep Writing" : "Keep Editing";
var discardText = newNote ? "Discard" : "Discard Changes";
return AlertDialog(
title: Text(title),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(editText),
),
FlatButton(
onPressed: () {
Navigator.pop(context); // Alert box
Navigator.pop(context); // Note Editor
},
child: Text(discardText),
),
],
);
}
bool _noteModified() {
var noteContent = _textController.text.trim();
var titleContent = _titleTextController.text.trim();
if (noteContent.isEmpty && titleContent.isEmpty) {
return false;
}
if (note != null) {
if (rawEditor) {
return serializer.encode(note.data) != noteContent;
} else {
return noteContent != note.body || titleContent != note.title;
}
}
return false;
}
void _saveNote(BuildContext context) {
if (!_noteModified()) return;
final stateContainer = StateContainer.of(context);
if (rawEditor == false) {
note.body = _textController.text.trim();
note.title = _titleTextController.text.trim();
} else {
note.data = serializer.decode(_textController.text);
}
if (!note.isEmpty()) {
newNote ? stateContainer.addNote(note) : stateContainer.updateNote(note);
}
}
}
class NoteMarkdownEditor extends StatelessWidget {
final TextEditingController textController;
final bool useMonospace;
NoteMarkdownEditor(this.textController, this.useMonospace);
@override
Widget build(BuildContext context) {
var style = Theme.of(context).textTheme.subhead;
if (useMonospace) {
style = style.copyWith(fontFamily: "Roboto Mono");
}
return TextField(
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.multiline,
maxLines: null,
style: style,
decoration: InputDecoration(
hintText: 'Write here',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
scrollPadding: const EdgeInsets.all(0.0),
);
}
}
class NoteTitleEditor extends StatelessWidget {
final TextEditingController textController;
NoteTitleEditor(this.textController);
@override
Widget build(BuildContext context) {
var style = Theme.of(context).textTheme.title;
return TextField(
autofocus: false,
keyboardType: TextInputType.text,
maxLines: 1,
style: style,
decoration: InputDecoration(
hintText: 'Title',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
);
}
}

View File

@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes_folder.dart';
import 'package:gitjournal/screens/journal_editor.dart';
import 'package:gitjournal/screens/journal_browsing.dart';
import 'package:gitjournal/screens/note_editor.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/widgets/app_drawer.dart';
import 'package:gitjournal/widgets/app_bar_menu_button.dart';
@ -33,11 +32,9 @@ class JournalListingScreen extends StatelessWidget {
Widget journalList = JournalList(
notes: allNotes,
noteSelectedFunction: (noteIndex) {
var note = allNotes[noteIndex];
var route = MaterialPageRoute(
builder: (context) => JournalBrowsingScreen(
notes: allNotes,
noteIndex: noteIndex,
),
builder: (context) => NoteEditor.fromNote(note),
);
Navigator.of(context).push(route);
},
@ -81,7 +78,7 @@ class JournalListingScreen extends StatelessWidget {
void _newPost(BuildContext context) {
var route = MaterialPageRoute(
builder: (context) => JournalEditor.newNote(notesFolder));
builder: (context) => NoteEditor.newNote(notesFolder));
Navigator.of(context).push(route);
}
}
@ -146,11 +143,9 @@ class NoteSearch extends SearchDelegate<Note> {
Widget journalList = JournalList(
notes: filteredNotes,
noteSelectedFunction: (noteIndex) {
var note = filteredNotes[noteIndex];
var route = MaterialPageRoute(
builder: (context) => JournalBrowsingScreen(
notes: filteredNotes,
noteIndex: noteIndex,
),
builder: (context) => NoteEditor.fromNote(note),
);
Navigator.of(context).push(route);
},

View File

@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:fimber/fimber.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes_folder.dart';
import 'package:gitjournal/editors/markdown_editor.dart';
import 'package:gitjournal/editors/raw_editor.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/core/note_data_serializers.dart';
import 'package:gitjournal/utils.dart';
import 'package:gitjournal/widgets/rename_dialog.dart';
enum NoteEditorDropDownChoices { Discard, SwitchEditor }
class NoteEditor extends StatefulWidget {
final Note note;
final NotesFolder notesFolder;
NoteEditor.fromNote(this.note) : notesFolder = null;
NoteEditor.newNote(this.notesFolder) : note = null;
@override
NoteEditorState createState() {
if (note == null) {
return NoteEditorState.newNote(notesFolder);
} else {
return NoteEditorState.fromNote(note);
}
}
}
enum EditorType { Markdown, Raw }
class NoteEditorState extends State<NoteEditor> {
Note note;
EditorType editorType = EditorType.Markdown;
String noteSerialized = "";
final _rawEditorKey = GlobalKey<RawEditorState>();
final _markdownEditorKey = GlobalKey<MarkdownEditorState>();
bool get _isNewNote {
return widget.note == null;
}
NoteEditorState.newNote(NotesFolder folder) {
note = Note.newNote(folder);
}
NoteEditorState.fromNote(this.note) {
var serializer = MarkdownYAMLSerializer();
noteSerialized = serializer.encode(note.data);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
_saveNote(_getNoteFromEditor());
return true;
},
child: _getEditor(),
);
}
Widget _getEditor() {
switch (editorType) {
case EditorType.Markdown:
return MarkdownEditor(
key: _markdownEditorKey,
note: note,
noteDeletionSelected: _noteDeletionSelected,
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
autofocusOnEditor: _isNewNote,
);
case EditorType.Raw:
return RawEditor(
key: _rawEditorKey,
note: note,
noteDeletionSelected: _noteDeletionSelected,
noteEditorChooserSelected: _noteEditorChooserSelected,
exitEditorSelected: _exitEditorSelected,
renameNoteSelected: _renameNoteSelected,
);
}
return null;
}
void _noteEditorChooserSelected(Note _note) async {
var newEditorType = await showDialog<EditorType>(
context: context,
builder: (BuildContext context) {
var children = <Widget>[
RadioListTile<EditorType>(
title: const Text("Markdown Editor"),
value: EditorType.Markdown,
groupValue: editorType,
onChanged: (EditorType et) => Navigator.of(context).pop(et),
),
RadioListTile<EditorType>(
title: const Text("Raw Editor"),
value: EditorType.Raw,
groupValue: editorType,
onChanged: (EditorType et) => Navigator.of(context).pop(et),
),
];
return AlertDialog(
title: const Text("Choose Editor"),
content: Column(
children: children,
mainAxisSize: MainAxisSize.min,
),
);
},
);
if (newEditorType != null) {
setState(() {
note = _note;
editorType = newEditorType;
});
}
}
void _exitEditorSelected(Note note) {
_saveNote(note);
Navigator.pop(context);
}
void _renameNoteSelected(Note _note) async {
var fileName = await showDialog(
context: context,
builder: (_) => RenameDialog(
oldPath: note.filePath,
inputDecoration: 'File Name',
dialogTitle: "Rename File",
),
);
if (fileName is String) {
if (_isNewNote) {
setState(() {
note = _note;
note.rename(fileName);
});
}
final container = StateContainer.of(context);
container.renameNote(note, fileName);
}
}
void _noteDeletionSelected(Note note) {
if (_isNewNote && !_noteModified(note)) {
Navigator.pop(context);
return;
}
showDialog(context: context, builder: _buildAlertDialog);
}
void _deleteNote(Note note) {
if (_isNewNote) {
return;
}
final stateContainer = StateContainer.of(context);
stateContainer.removeNote(note);
}
Widget _buildAlertDialog(BuildContext context) {
var title = "Do you want to delete this note?";
var editText = "Keep Writing";
var discardText = "Discard";
return AlertDialog(
title: Text(title),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(editText),
),
FlatButton(
onPressed: () {
_deleteNote(note);
Navigator.pop(context); // Alert box
Navigator.pop(context); // Note Editor
if (!_isNewNote) {
Fimber.d("Showing an undo snackbar");
final stateContainer = StateContainer.of(context);
showUndoDeleteSnackbar(context, stateContainer, note);
}
},
child: Text(discardText),
),
],
);
}
bool _noteModified(Note note) {
if (_isNewNote) {
return note.title.isNotEmpty || note.body.isNotEmpty;
}
var serializer = MarkdownYAMLSerializer();
var finalNoteSerialized = serializer.encode(note.data);
return finalNoteSerialized != noteSerialized;
}
void _saveNote(Note note) {
if (!_noteModified(note)) return;
print("Note modified - saving");
final stateContainer = StateContainer.of(context);
_isNewNote ? stateContainer.addNote(note) : stateContainer.updateNote(note);
}
Note _getNoteFromEditor() {
switch (editorType) {
case EditorType.Markdown:
return _markdownEditorKey.currentState.getNote();
case EditorType.Raw:
return _rawEditorKey.currentState.getNote();
}
return null;
}
}

View File

@ -151,11 +151,6 @@ class StateContainerState extends State<StateContainer> {
}
void renameNote(Note note, String newFileName) async {
// Do not let the user rename it to a non-markdown file
if (!newFileName.toLowerCase().endsWith('.md')) {
newFileName += '.md';
}
var oldNotePath = note.filePath;
note.rename(newFileName);
@ -181,8 +176,8 @@ class StateContainerState extends State<StateContainer> {
});
}
void undoRemoveNote(Note note, int index) {
note.parent.insert(index, note);
void undoRemoveNote(Note note) {
note.parent.insert(0, note);
_gitRepo.resetLastCommit().then((NoteRepoResult _) {
syncNotes();
});

View File

@ -26,7 +26,6 @@ void showUndoDeleteSnackbar(
BuildContext context,
StateContainerState stateContainer,
Note deletedNote,
int deletedNoteIndex,
) {
var theme = Theme.of(context);
@ -40,7 +39,7 @@ void showUndoDeleteSnackbar(
),
onPressed: () {
Fimber.d("Undoing delete");
stateContainer.undoRemoveNote(deletedNote, deletedNoteIndex);
stateContainer.undoRemoveNote(deletedNote);
},
),
).show(context);

View File

@ -64,7 +64,7 @@ class JournalList extends StatelessWidget {
final stateContainer = StateContainer.of(context);
stateContainer.removeNote(note);
showUndoDeleteSnackbar(context, stateContainer, note, i);
showUndoDeleteSnackbar(context, stateContainer, note);
},
);
},

View File

@ -47,7 +47,7 @@ class _RenameDialogState extends State<RenameDialog> {
FileSystemEntityType.notFound) {
return 'Already Exists';
}
return "";
return null;
},
autofocus: true,
keyboardType: TextInputType.text,