diff --git a/lib/core/note.dart b/lib/core/note.dart index 238ee032..a4d2f545 100644 --- a/lib/core/note.dart +++ b/lib/core/note.dart @@ -156,9 +156,17 @@ class Note with ChangeNotifier implements Comparable { } 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(); diff --git a/lib/editors/markdown_editor.dart b/lib/editors/markdown_editor.dart new file mode 100644 index 00000000..2e1092d8 --- /dev/null +++ b/lib/editors/markdown_editor.dart @@ -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 { + 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: [ + _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: [ + 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( + onSelected: (DropDownChoices choice) { + _updateNote(); + widget.renameNoteSelected(note); + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + 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, + ); + } +} diff --git a/lib/editors/raw_editor.dart b/lib/editors/raw_editor.dart new file mode 100644 index 00000000..967483b6 --- /dev/null +++ b/lib/editors/raw_editor.dart @@ -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 { + 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: [ + IconButton( + icon: const Icon(Icons.library_books), + onPressed: () { + _updateNote(); + widget.noteEditorChooserSelected(note); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + _updateNote(); + widget.noteDeletionSelected(note); + }, + ), + PopupMenuButton( + onSelected: (DropDownChoices choice) { + _updateNote(); + widget.renameNoteSelected(note); + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + 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), + ); + } +} diff --git a/lib/screens/journal_browsing.dart b/lib/screens/journal_browsing.dart deleted file mode 100644 index 64d13d96..00000000 --- a/lib/screens/journal_browsing.dart +++ /dev/null @@ -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 notes; - final int noteIndex; - - const JournalBrowsingScreen({ - @required this.notes, - @required this.noteIndex, - }); - - @override - JournalBrowsingScreenState createState() { - return JournalBrowsingScreenState(noteIndex: noteIndex); - } -} - -class JournalBrowsingScreenState extends State { - 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: [ - 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( - 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) => - >[ - const PopupMenuItem( - 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: [ - FlatButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - FlatButton( - onPressed: () { - Navigator.pop(context); // Alert box - _deleteNote(context); - }, - child: const Text('Delete'), - ), - ], - ); - } -} diff --git a/lib/screens/journal_editor.dart b/lib/screens/journal_editor.dart deleted file mode 100644 index d11c157f..00000000 --- a/lib/screens/journal_editor.dart +++ /dev/null @@ -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 { - 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: [ - 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: [ - !rawEditor - ? IconButton( - icon: editingMode - ? Icon(Icons.remove_red_eye) - : Icon(Icons.edit), - onPressed: () { - setState(() { - editingMode = !editingMode; - }); - }, - ) - : Container(), - PopupMenuButton( - 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) => - >[ - const PopupMenuItem( - value: NoteEditorDropDownChoices.Discard, - child: Text('Discard'), - ), - PopupMenuItem( - 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: [ - 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, - ); - } -} diff --git a/lib/screens/journal_listing.dart b/lib/screens/journal_listing.dart index 01b700a4..4106f85b 100644 --- a/lib/screens/journal_listing.dart +++ b/lib/screens/journal_listing.dart @@ -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 { 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); }, diff --git a/lib/screens/note_editor.dart b/lib/screens/note_editor.dart new file mode 100644 index 00000000..0a5e5c07 --- /dev/null +++ b/lib/screens/note_editor.dart @@ -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 { + Note note; + EditorType editorType = EditorType.Markdown; + String noteSerialized = ""; + + final _rawEditorKey = GlobalKey(); + final _markdownEditorKey = GlobalKey(); + + 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( + context: context, + builder: (BuildContext context) { + var children = [ + RadioListTile( + title: const Text("Markdown Editor"), + value: EditorType.Markdown, + groupValue: editorType, + onChanged: (EditorType et) => Navigator.of(context).pop(et), + ), + RadioListTile( + 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: [ + 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; + } +} diff --git a/lib/state_container.dart b/lib/state_container.dart index cf185366..5f799d9a 100644 --- a/lib/state_container.dart +++ b/lib/state_container.dart @@ -151,11 +151,6 @@ class StateContainerState extends State { } 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 { }); } - 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(); }); diff --git a/lib/utils.dart b/lib/utils.dart index 7704d9a2..f407162c 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -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); diff --git a/lib/widgets/journal_list.dart b/lib/widgets/journal_list.dart index 1e3449f2..7e7454a1 100644 --- a/lib/widgets/journal_list.dart +++ b/lib/widgets/journal_list.dart @@ -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); }, ); }, diff --git a/lib/widgets/rename_dialog.dart b/lib/widgets/rename_dialog.dart index a2b4cab9..266c9c47 100644 --- a/lib/widgets/rename_dialog.dart +++ b/lib/widgets/rename_dialog.dart @@ -47,7 +47,7 @@ class _RenameDialogState extends State { FileSystemEntityType.notFound) { return 'Already Exists'; } - return ""; + return null; }, autofocus: true, keyboardType: TextInputType.text,