Files
GitJournal/lib/editors/raw_editor.dart
Vishesh Handa 510b277398 Add Note.apply
Avoid calling each setter as each results in the entire tree being
rebuilt.
2021-09-16 20:07:33 +02:00

270 lines
7.0 KiB
Dart

/*
* SPDX-FileCopyrightText: 2019-2021 Vishesh Handa <me@vhanda.in>
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:provider/provider.dart';
import 'package:gitjournal/core/image.dart' as core;
import 'package:gitjournal/core/md_yaml_doc_codec.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes_folder_fs.dart';
import 'package:gitjournal/core/views/inline_tags_view.dart';
import 'package:gitjournal/editors/autocompletion_widget.dart';
import 'package:gitjournal/editors/common.dart';
import 'package:gitjournal/editors/disposable_change_notifier.dart';
import 'package:gitjournal/editors/editor_scroll_view.dart';
import 'package:gitjournal/editors/undo_redo.dart';
import 'package:gitjournal/generated/locale_keys.g.dart';
import 'package:gitjournal/logger/logger.dart';
import 'package:gitjournal/settings/app_settings.dart';
import 'package:gitjournal/widgets/future_builder_with_progress.dart';
import 'rich_text_controller.dart';
class RawEditor 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 editMode;
final String? highlightString;
final ThemeData theme;
RawEditor({
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.editMode,
required this.highlightString,
required this.theme,
}) : super(key: key);
@override
RawEditorState createState() {
return RawEditorState(note);
}
}
class RawEditorState extends State<RawEditor>
with DisposableChangeNotifier
implements EditorState {
Note note;
late bool _noteModified;
late TextEditingController _textController;
late UndoRedoStack _undoRedoStack;
final serializer = MarkdownYAMLCodec();
RawEditorState(this.note);
@override
void initState() {
super.initState();
_noteModified = widget.noteModified;
_textController = buildController(
text: serializer.encode(note.data),
highlightText: widget.highlightString,
theme: widget.theme,
);
_undoRedoStack = UndoRedoStack();
}
@override
void dispose() {
_textController.dispose();
super.disposeListenables();
super.dispose();
}
@override
void didUpdateWidget(RawEditor oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.noteModified != widget.noteModified) {
_noteModified = widget.noteModified;
}
}
@override
Widget build(BuildContext context) {
var editor = EditorScrollView(
child: _NoteEditor(
textController: _textController,
autofocus: widget.editMode,
onChanged: _noteTextChanged,
),
);
return EditorScaffold(
editor: widget,
editorState: this,
noteModified: _noteModified,
editMode: widget.editMode,
parentFolder: note.parent,
body: editor,
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: _undoRedoStack.undoPossible,
redoAllowed: _undoRedoStack.redoPossible,
);
}
@override
Note getNote() {
note.data = serializer.decode(_textController.text);
return note;
}
void _noteTextChanged() {
notifyListeners();
var editState = TextEditorState.fromValue(_textController.value);
var redraw = _undoRedoStack.textChanged(editState);
if (redraw) {
setState(() {});
}
if (_noteModified) return;
setState(() {
_noteModified = true;
});
}
@override
Future<void> addImage(String filePath) async {
var note = getNote();
var image = await core.Image.copyIntoFs(note.parent, filePath);
note.apply(body: note.body + image.toMarkup(note.fileFormat));
setState(() {
_textController.text = note.body;
_noteModified = true;
});
}
@override
bool get noteModified => _noteModified;
Future<void> _undo() async {
var es = _undoRedoStack.undo();
_textController.value = es.toValue();
setState(() {
// To Redraw the undo/redo button state
});
}
Future<void> _redo() async {
var es = _undoRedoStack.redo();
_textController.value = es.toValue();
setState(() {
// To Redraw the undo/redo button state
});
}
}
class _NoteEditor extends StatefulWidget {
final TextEditingController textController;
final bool autofocus;
final Function onChanged;
_NoteEditor({
required this.textController,
required this.autofocus,
required this.onChanged,
});
@override
State<_NoteEditor> createState() => _NoteEditorState();
}
class _NoteEditorState extends State<_NoteEditor> {
late FocusNode _focusNode;
late GlobalKey _textFieldKey;
@override
void initState() {
_focusNode = FocusNode();
_textFieldKey = GlobalKey();
super.initState();
}
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var style = theme.textTheme.subtitle1!.copyWith(fontFamily: "Roboto Mono");
var textField = TextField(
key: _textFieldKey,
focusNode: _focusNode,
autofocus: widget.autofocus,
keyboardType: TextInputType.multiline,
maxLines: null,
style: style,
decoration: InputDecoration(
hintText: tr(LocaleKeys.editors_common_defaultBodyHint),
border: InputBorder.none,
isDense: true,
fillColor: theme.scaffoldBackgroundColor,
hoverColor: theme.scaffoldBackgroundColor,
isCollapsed: true,
),
controller: widget.textController,
textCapitalization: TextCapitalization.sentences,
scrollPadding: const EdgeInsets.all(0.0),
onChanged: (_) => widget.onChanged(),
);
var appSettings = Provider.of<AppSettings>(context);
if (!appSettings.experimentalTagAutoCompletion) {
return textField;
}
final rootFolder = Provider.of<NotesFolderFS>(context, listen: false);
var inlineTagsView = InlineTagsProvider.of(context);
var futureBuilder = () async {
var allTags = await rootFolder.getNoteTagsRecursively(inlineTagsView);
Log.d("Building autocompleter with $allTags");
return AutoCompletionWidget(
textFieldStyle: style,
textFieldKey: _textFieldKey,
textFieldFocusNode: _focusNode,
textController: widget.textController,
child: textField,
tags: allTags.toList(),
);
};
return FutureBuilderWithProgress(future: futureBuilder());
}
}