mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-08-24 17:31:06 +08:00

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.
256 lines
6.2 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|