Files
GitJournal/lib/editors/journal_editor.dart
Vishesh Handa 657721adc6 Update dart-git and stop using the Result class
Instead we're going to move back to standard exceptions.

Using a custom Result class has created far far more problems
- The Stacktraces aren't always right
- Sometimes one forgets to check the Result error
- All other exception throwing code needing to be converted to Results
- Non idiomatic Dart code

I think it's better to just go back to exceptions. They have their
problems, but overall, I think it's a better approach.
2023-11-24 14:03:30 +01:00

256 lines
6.2 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:gitjournal/core/image.dart' as core;
import 'package:gitjournal/core/image.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/notes/note.dart';
import 'package:gitjournal/editors/common.dart';
import 'package:gitjournal/editors/editor_scroll_view.dart';
import 'package:gitjournal/editors/heuristics.dart';
import 'package:gitjournal/editors/note_body_editor.dart';
import 'package:gitjournal/editors/undo_redo.dart';
import 'package:gitjournal/editors/utils/disposable_change_notifier.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/logger/logger.dart';
import 'package:gitjournal/utils/utils.dart';
import 'package:gitjournal/widgets/journal_editor_header.dart';
import 'controllers/rich_text_controller.dart';
class JournalEditor extends StatefulWidget implements Editor {
final Note note;
final bool noteModified;
final bool editMode;
final String? highlightString;
final ThemeData theme;
@override
final EditorCommon common;
const JournalEditor({
super.key,
required this.note,
required this.noteModified,
required this.editMode,
required this.highlightString,
required this.theme,
required this.common,
});
@override
JournalEditorState createState() {
return JournalEditorState();
}
}
class JournalEditorState extends State<JournalEditor>
with DisposableChangeNotifier
implements EditorState {
late Note _note;
late TextEditingController _textController;
late UndoRedoStack _undoRedoStack;
late bool _noteModified;
late EditorHeuristics _heuristics;
final _editorKey = GlobalKey();
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_note = widget.note;
_noteModified = widget.noteModified;
_textController = buildController(
text: _note.body,
highlightText: widget.highlightString,
theme: widget.theme,
);
_heuristics = EditorHeuristics(text: _note.body);
_scrollController = ScrollController(keepScrollOffset: false);
_undoRedoStack = UndoRedoStack();
}
@override
void didUpdateWidget(JournalEditor oldWidget) {
if (oldWidget.noteModified != widget.noteModified) {
_noteModified = widget.noteModified;
}
if (oldWidget.note != widget.note) {
_note = widget.note;
_textController.text = _note.body;
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
_scrollController.dispose();
_textController.dispose();
super.disposeListenables();
super.dispose();
}
@override
Widget build(BuildContext context) {
var editor = EditorScrollView(
scrollController: _scrollController,
child: Column(
children: <Widget>[
JournalEditorHeader(
_note.created,
onChange: (dt) {
setState(() {
_note = _note.copyWith(created: dt);
});
},
),
NoteBodyEditor(
key: _editorKey,
textController: _textController,
autofocus: widget.editMode,
onChanged: _noteTextChanged,
),
],
),
);
return EditorScaffold(
startingNote: widget.note,
editor: widget,
editorState: this,
noteModified: _noteModified,
editMode: widget.editMode,
parentFolder: _note.parent,
body: editor,
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: _undoRedoStack.undoPossible,
redoAllowed: _undoRedoStack.redoPossible,
findAllowed: true,
);
}
@override
Note getNote() {
return _note.copyWith(
body: _textController.text.trim(),
type: NoteType.Journal,
);
}
void _noteTextChanged() {
try {
_applyHeuristics();
} catch (e, stackTrace) {
Log.e("EditorHeuristics: $e");
logExceptionWarning(e, stackTrace);
}
if (_noteModified && !widget.editMode) {
notifyListeners();
return;
}
var newState = !(widget.editMode && _textController.text.trim().isEmpty);
if (newState != _noteModified) {
setState(() {
_noteModified = newState;
});
}
notifyListeners();
}
void _applyHeuristics() {
var editState = TextEditorState.fromValue(_textController.value);
var es = _heuristics.textChanged(editState);
if (es != null) {
_textController.value = es.toValue();
}
var redraw = _undoRedoStack.textChanged(editState);
if (redraw) {
setState(() {});
}
}
@override
Future<void> addImage(String filePath) async {
try {
var image = await core.Image.copyIntoFs(_note.parent, filePath);
var ts = insertImage(
TextEditorState.fromValue(_textController.value),
image,
_note.fileFormat,
);
setState(() {
_textController.value = ts.toValue();
_noteModified = true;
});
} catch (ex) {
showErrorSnackbar(context, ex);
}
}
@override
bool get noteModified => _noteModified;
Future<void> _undo() async {
var es = _undoRedoStack.undo();
setState(() {
_textController.value = es.toValue();
});
}
Future<void> _redo() async {
var es = _undoRedoStack.redo();
setState(() {
_textController.value = es.toValue();
});
}
@override
SearchInfo search(String? text) {
setState(() {
_textController = buildController(
text: _textController.text,
highlightText: text,
theme: widget.theme,
);
});
return SearchInfo.compute(body: _textController.text, text: text);
}
@override
void scrollToResult(String text, int num) {
setState(() {
_textController = buildController(
text: _textController.text,
highlightText: text,
theme: widget.theme,
currentPos: num,
);
});
scrollToSearchResult(
scrollController: _scrollController,
textController: _textController,
textEditorKey: _editorKey,
textStyle: NoteBodyEditor.textStyle(context),
searchText: text,
resultNum: num,
);
}
}