Files
GitJournal/lib/editors/markdown_editor.dart
Vishesh Handa e9cc050866 Implement a basic Zen Mode
This can be enabled in all the editors.
2020-07-27 09:39:31 +02:00

260 lines
6.4 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/editors/common.dart';
import 'package:gitjournal/editors/heuristics.dart';
import 'package:gitjournal/editors/note_title_editor.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/settings.dart';
import 'package:gitjournal/utils/logger.dart';
import 'package:gitjournal/widgets/editor_scroll_view.dart';
import 'package:gitjournal/widgets/note_viewer.dart';
class MarkdownEditor extends StatefulWidget implements Editor {
final Note note;
final bool noteModified;
@override
final NoteCallback noteDeletionSelected;
@override
final NoteCallback noteEditorChooserSelected;
@override
final NoteCallback exitEditorSelected;
@override
final NoteCallback renameNoteSelected;
@override
final NoteCallback editTagsSelected;
@override
final NoteCallback moveNoteToFolderSelected;
@override
final NoteCallback discardChangesSelected;
final bool isNewNote;
MarkdownEditor({
Key key,
@required this.note,
@required this.noteModified,
@required this.noteDeletionSelected,
@required this.noteEditorChooserSelected,
@required this.exitEditorSelected,
@required this.renameNoteSelected,
@required this.editTagsSelected,
@required this.moveNoteToFolderSelected,
@required this.discardChangesSelected,
@required this.isNewNote,
}) : super(key: key);
@override
MarkdownEditorState createState() {
return MarkdownEditorState(note);
}
}
class MarkdownEditorState extends State<MarkdownEditor>
with ChangeNotifier
implements EditorState {
Note note;
TextEditingController _textController = TextEditingController();
TextEditingController _titleTextController = TextEditingController();
String _oldText;
bool editingMode = true;
bool _noteModified;
MarkdownEditorState(this.note) {
_textController = TextEditingController(text: note.body);
_titleTextController = TextEditingController(text: note.title);
_oldText = note.body;
var settings = Settings.instance;
if (settings.markdownDefaultView == SettingsMarkdownDefaultView.LastUsed) {
editingMode =
settings.markdownLastUsedView == SettingsMarkdownDefaultView.Edit;
} else {
editingMode =
settings.markdownDefaultView == SettingsMarkdownDefaultView.Edit;
}
}
@override
void initState() {
super.initState();
_noteModified = widget.noteModified;
if (widget.isNewNote) {
editingMode = true;
}
}
@override
void dispose() {
_textController.dispose();
_titleTextController.dispose();
super.dispose();
}
@override
void didUpdateWidget(MarkdownEditor oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.noteModified != widget.noteModified) {
_noteModified = widget.noteModified;
}
}
@override
Widget build(BuildContext context) {
var editor = EditorScrollView(
child: Column(
children: <Widget>[
if (note.canHaveMetadata)
NoteTitleEditor(
_titleTextController,
_noteTextChanged,
),
_NoteBodyEditor(
textController: _textController,
autofocus: widget.isNewNote,
onChanged: _noteTextChanged,
),
],
),
);
Widget body = editingMode ? editor : NoteViewer(note: note);
var extraButton = IconButton(
icon: editingMode
? const Icon(Icons.remove_red_eye)
: const Icon(Icons.edit),
onPressed: _switchMode,
);
return EditorScaffold(
editor: widget,
editorState: this,
extraButton: extraButton,
noteModified: _noteModified,
parentFolder: note.parent,
allowEdits: editingMode,
body: body,
);
}
void _switchMode() {
setState(() {
editingMode = !editingMode;
switch (editingMode) {
case true:
Settings.instance.markdownLastUsedView =
SettingsMarkdownDefaultView.Edit;
break;
case false:
Settings.instance.markdownLastUsedView =
SettingsMarkdownDefaultView.View;
break;
}
Settings.instance.save();
_updateNote();
});
}
void _updateNote() {
note.title = _titleTextController.text.trim();
note.body = _textController.text.trim();
note.type = NoteType.Unknown;
}
@override
Note getNote() {
_updateNote();
return note;
}
void _noteTextChanged() {
try {
_applyHeuristics();
} catch (e, stackTrace) {
Log.e("EditorHeuristics: $e");
logExceptionWarning(e, stackTrace);
}
if (_noteModified && !widget.isNewNote) return;
var newState = !(widget.isNewNote && _textController.text.trim().isEmpty);
if (newState != _noteModified) {
setState(() {
_noteModified = newState;
});
}
notifyListeners();
}
void _applyHeuristics() {
var selection = _textController.selection;
if (selection.baseOffset != selection.extentOffset) {
_oldText = _textController.text;
return;
}
var r =
autoAddBulletList(_oldText, _textController.text, selection.baseOffset);
_oldText = _textController.text;
if (r == null) {
return;
}
_textController.text = r.text;
_textController.selection = TextSelection.collapsed(offset: r.cursorPos);
}
@override
Future<void> addImage(File file) async {
await getNote().addImage(file);
setState(() {
_textController.text = note.body;
_noteModified = true;
});
}
@override
bool get noteModified => _noteModified;
}
class _NoteBodyEditor extends StatelessWidget {
final TextEditingController textController;
final bool autofocus;
final Function onChanged;
_NoteBodyEditor({
@required this.textController,
@required this.autofocus,
@required this.onChanged,
});
@override
Widget build(BuildContext context) {
var style = Theme.of(context).textTheme.subtitle1;
return TextField(
autofocus: autofocus,
keyboardType: TextInputType.multiline,
maxLines: null,
style: style,
decoration: const InputDecoration(
hintText: 'Write here',
border: InputBorder.none,
isDense: true,
),
controller: textController,
textCapitalization: TextCapitalization.sentences,
scrollPadding: const EdgeInsets.all(0.0),
onChanged: (_) => onChanged(),
);
}
}