Skip to content

Commit 7e00deb

Browse files
committed
Test editorFocusNode and stuff
1 parent ae2a819 commit 7e00deb

10 files changed

Lines changed: 270 additions & 195 deletions

File tree

lib/common/constants/constants.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ final logger = AppLogger();
3232
/// Codec to encode and decode Markdown files in fleather.
3333
const parchmentMarkdownCodec = ParchmentMarkdownCodec();
3434

35-
/// Focus node of the note content text editor.
36-
final editorFocusNode = FocusNode(debugLabel: 'Editor focus node');
37-
3835
/// Utilities for the Storage Access Framework (SAF) APIs.
3936
final safUtil = SafUtil();
4037

lib/common/navigation/app_bars/notes/editor_app_bar.dart

Lines changed: 98 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -147,105 +147,110 @@ class _BackAppBarState extends ConsumerState<EditorAppBar> {
147147
return EmptyPlaceholder();
148148
}
149149

150-
final editorController = fleatherControllerNotifier.value;
151150
final showEditorModeButton = PreferenceKey.editorModeButton.preferenceOrDefault;
152151
final enableLabels = PreferenceKey.enableLabels.preferenceOrDefault;
153152

154153
return ValueListenableBuilder(
155-
valueListenable: editorHasFocusNotifier,
156-
builder: (context, editorHasFocus, child) {
154+
valueListenable: fleatherControllerNotifier,
155+
builder: (context, editorController, child) {
157156
return ValueListenableBuilder(
158-
valueListenable: isEditorInEditModeNotifier,
159-
builder: (context, isEditMode, child) {
160-
return AppBar(
161-
leading: BackButton(
162-
onPressed: () => Navigator.of(rootNavigatorKey.currentContext!).pop(),
163-
),
164-
actions: [
165-
if (note.status == NoteStatus.available) ...[
166-
if (note.type == NoteType.richText) ...[
167-
ValueListenableBuilder(
168-
valueListenable: fleatherControllerCanUndoNotifier,
169-
builder: (context, canUndo, child) {
170-
final enableUndo = editorHasFocus &&
171-
canUndo &&
172-
editorController != null &&
173-
editorController.canUndo &&
174-
isEditMode;
175-
176-
return IconButton(
177-
icon: const Icon(Icons.undo),
178-
tooltip: l.tooltip_undo,
179-
onPressed: enableUndo ? undo : null,
180-
);
181-
},
182-
),
183-
ValueListenableBuilder(
184-
valueListenable: fleatherControllerCanRedoNotifier,
185-
builder: (context, canRedo, child) {
186-
final enableRedo = editorHasFocus && canRedo && isEditMode;
187-
188-
return IconButton(
189-
icon: const Icon(Icons.redo),
190-
tooltip: l.tooltip_redo,
191-
onPressed: enableRedo ? redo : null,
192-
);
193-
},
194-
),
195-
],
196-
if (showEditorModeButton)
197-
ValueListenableBuilder(
198-
valueListenable: isEditorInEditModeNotifier,
199-
builder: (context, isEditMode, child) => IconButton(
200-
icon: Icon(isEditMode ? Icons.visibility : Icons.edit),
201-
tooltip:
202-
isEditMode ? l.tooltip_fab_toggle_editor_mode_read : l.tooltip_fab_toggle_editor_mode_edit,
203-
onPressed: switchMode,
204-
),
205-
),
206-
PopupMenuButton<EditorAvailableMenuOption>(
207-
itemBuilder: (context) => ([
208-
EditorAvailableMenuOption.copy.popupMenuItem(context),
209-
EditorAvailableMenuOption.share.popupMenuItem(context),
210-
const PopupMenuDivider(),
211-
if (note.pinned) EditorAvailableMenuOption.unpin.popupMenuItem(context),
212-
if (!note.pinned) EditorAvailableMenuOption.pin.popupMenuItem(context),
213-
if (note.locked) EditorAvailableMenuOption.unlock.popupMenuItem(context),
214-
if (!note.locked) EditorAvailableMenuOption.lock.popupMenuItem(context),
215-
if (enableLabels) EditorAvailableMenuOption.selectLabels.popupMenuItem(context),
216-
const PopupMenuDivider(),
217-
EditorAvailableMenuOption.archive.popupMenuItem(context),
218-
EditorAvailableMenuOption.delete.popupMenuItem(context),
219-
const PopupMenuDivider(),
220-
EditorAvailableMenuOption.about.popupMenuItem(context),
221-
]),
222-
onSelected: onAvailableMenuOptionSelected,
157+
valueListenable: editorHasFocusNotifier,
158+
builder: (context, editorHasFocus, child) {
159+
return ValueListenableBuilder(
160+
valueListenable: isEditorInEditModeNotifier,
161+
builder: (context, isEditMode, child) {
162+
return AppBar(
163+
leading: BackButton(
164+
onPressed: () => Navigator.of(rootNavigatorKey.currentContext!).pop(),
223165
),
224-
],
225-
if (note.status == NoteStatus.archived)
226-
PopupMenuButton<EditorArchivedMenuOption>(
227-
itemBuilder: (context) => ([
228-
EditorArchivedMenuOption.copy.popupMenuItem(context),
229-
EditorArchivedMenuOption.share.popupMenuItem(context),
230-
const PopupMenuDivider(),
231-
EditorArchivedMenuOption.unarchive.popupMenuItem(context),
232-
const PopupMenuDivider(),
233-
EditorArchivedMenuOption.about.popupMenuItem(context),
234-
]),
235-
onSelected: onArchivedMenuOptionSelected,
236-
),
237-
if (note.status == NoteStatus.deleted)
238-
PopupMenuButton<EditorDeletedMenuOption>(
239-
itemBuilder: (context) => ([
240-
EditorDeletedMenuOption.restore.popupMenuItem(context),
241-
EditorDeletedMenuOption.deletePermanently.popupMenuItem(context),
242-
const PopupMenuDivider(),
243-
EditorDeletedMenuOption.about.popupMenuItem(context),
244-
]),
245-
onSelected: onDeletedMenuOptionSelected,
246-
),
247-
Padding(padding: Paddings.appBarActionsEnd),
248-
],
166+
actions: [
167+
if (note.status == NoteStatus.available) ...[
168+
if (note.type == NoteType.richText) ...[
169+
ValueListenableBuilder(
170+
valueListenable: fleatherControllerCanUndoNotifier,
171+
builder: (context, canUndo, child) {
172+
final enableUndo = editorHasFocus &&
173+
canUndo &&
174+
editorController != null &&
175+
editorController.canUndo &&
176+
isEditMode;
177+
178+
return IconButton(
179+
icon: const Icon(Icons.undo),
180+
tooltip: l.tooltip_undo,
181+
onPressed: enableUndo ? undo : null,
182+
);
183+
},
184+
),
185+
ValueListenableBuilder(
186+
valueListenable: fleatherControllerCanRedoNotifier,
187+
builder: (context, canRedo, child) {
188+
final enableRedo = editorHasFocus && canRedo && isEditMode;
189+
190+
return IconButton(
191+
icon: const Icon(Icons.redo),
192+
tooltip: l.tooltip_redo,
193+
onPressed: enableRedo ? redo : null,
194+
);
195+
},
196+
),
197+
],
198+
if (showEditorModeButton)
199+
ValueListenableBuilder(
200+
valueListenable: isEditorInEditModeNotifier,
201+
builder: (context, isEditMode, child) => IconButton(
202+
icon: Icon(isEditMode ? Icons.visibility : Icons.edit),
203+
tooltip: isEditMode
204+
? l.tooltip_fab_toggle_editor_mode_read
205+
: l.tooltip_fab_toggle_editor_mode_edit,
206+
onPressed: switchMode,
207+
),
208+
),
209+
PopupMenuButton<EditorAvailableMenuOption>(
210+
itemBuilder: (context) => ([
211+
EditorAvailableMenuOption.copy.popupMenuItem(context),
212+
EditorAvailableMenuOption.share.popupMenuItem(context),
213+
const PopupMenuDivider(),
214+
if (note.pinned) EditorAvailableMenuOption.unpin.popupMenuItem(context),
215+
if (!note.pinned) EditorAvailableMenuOption.pin.popupMenuItem(context),
216+
if (note.locked) EditorAvailableMenuOption.unlock.popupMenuItem(context),
217+
if (!note.locked) EditorAvailableMenuOption.lock.popupMenuItem(context),
218+
if (enableLabels) EditorAvailableMenuOption.selectLabels.popupMenuItem(context),
219+
const PopupMenuDivider(),
220+
EditorAvailableMenuOption.archive.popupMenuItem(context),
221+
EditorAvailableMenuOption.delete.popupMenuItem(context),
222+
const PopupMenuDivider(),
223+
EditorAvailableMenuOption.about.popupMenuItem(context),
224+
]),
225+
onSelected: onAvailableMenuOptionSelected,
226+
),
227+
],
228+
if (note.status == NoteStatus.archived)
229+
PopupMenuButton<EditorArchivedMenuOption>(
230+
itemBuilder: (context) => ([
231+
EditorArchivedMenuOption.copy.popupMenuItem(context),
232+
EditorArchivedMenuOption.share.popupMenuItem(context),
233+
const PopupMenuDivider(),
234+
EditorArchivedMenuOption.unarchive.popupMenuItem(context),
235+
const PopupMenuDivider(),
236+
EditorArchivedMenuOption.about.popupMenuItem(context),
237+
]),
238+
onSelected: onArchivedMenuOptionSelected,
239+
),
240+
if (note.status == NoteStatus.deleted)
241+
PopupMenuButton<EditorDeletedMenuOption>(
242+
itemBuilder: (context) => ([
243+
EditorDeletedMenuOption.restore.popupMenuItem(context),
244+
EditorDeletedMenuOption.deletePermanently.popupMenuItem(context),
245+
const PopupMenuDivider(),
246+
EditorDeletedMenuOption.about.popupMenuItem(context),
247+
]),
248+
onSelected: onDeletedMenuOptionSelected,
249+
),
250+
Padding(padding: Paddings.appBarActionsEnd),
251+
],
252+
);
253+
},
249254
);
250255
},
251256
);

lib/navigation/navigator_utils.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ class NavigatorUtils {
2929

3030
/// Pushes the notes editor route with its parameters [readOnly] and a [isNewNote].
3131
static void pushNotesEditor(BuildContext context, bool readOnly, bool isNewNote) {
32-
isEditorInEditModeNotifier.value = !PreferenceKey.openEditorReadingMode.preferenceOrDefault;
32+
// If this is a new note, force the editing mode
33+
isEditorInEditModeNotifier.value = isNewNote || !PreferenceKey.openEditorReadingMode.preferenceOrDefault;
3334

3435
push(
3536
context,

lib/pages/editor/editor_page.dart

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'widgets/markdown/markdown_editor.dart';
2121
import 'widgets/plain_text/plain_text_editor.dart';
2222
import 'widgets/rich_text/rich_text_editor.dart';
2323
import 'widgets/rich_text/rich_text_editor_toolbar.dart';
24+
import 'widgets/text_editor.dart';
2425
import 'widgets/title_editor.dart';
2526

2627
/// Editor page.
@@ -43,6 +44,9 @@ class NotesEditorPage extends ConsumerStatefulWidget {
4344
}
4445

4546
class _EditorState extends ConsumerState<NotesEditorPage> {
47+
/// Focus node of the note content text editor currently in use.
48+
FocusNode? _editorFocusNode;
49+
4650
@override
4751
void dispose() {
4852
super.dispose();
@@ -53,15 +57,25 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
5357
globalRef.read(notesProvider(status: NoteStatus.archived).notifier).removeEmpty();
5458
}
5559

60+
void setupEditorFocus(FocusNode? editorFocusNode) {
61+
_editorFocusNode = editorFocusNode;
62+
}
63+
5664
void requestEditorFocus() {
57-
editorFocusNode.requestFocus();
65+
_editorFocusNode?.requestFocus();
5866
}
5967

6068
@override
6169
Widget build(BuildContext context) {
6270
return ValueListenableBuilder(
6371
valueListenable: currentNoteNotifier,
6472
builder: (context, currentNote, child) {
73+
// Moved up fleatherController here so that it survives isEditorInEditMode changes
74+
// otherwise the cursor will reset to the front when switching between edit/reading mode
75+
final fleatherController =
76+
currentNote is RichTextNote ? FleatherController(document: currentNote.document) : null;
77+
fleatherControllerNotifier.value = fleatherController;
78+
6579
return ValueListenableBuilder(
6680
valueListenable: isEditorInEditModeNotifier,
6781
builder: (context, isEditorInEditMode, child) {
@@ -82,7 +96,7 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
8296
final showLabelsList =
8397
enableLabels && showLabelsListInEditorPage && currentNote.labelsVisibleSorted.isNotEmpty;
8498

85-
Widget contentEditor;
99+
TextEditor contentEditor;
86100
Widget? toolbar;
87101
switch (currentNote) {
88102
case PlainTextNote note:
@@ -91,16 +105,16 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
91105
isNewNote: widget.isNewNote,
92106
readOnly: readOnly,
93107
autofocus: autofocus,
108+
setupFocusNode: setupEditorFocus,
94109
);
95110
case RichTextNote note:
96-
final fleatherController = FleatherController(document: note.document);
97-
fleatherControllerNotifier.value = fleatherController;
98111
contentEditor = RichTextEditor(
99112
note: note,
100-
fleatherController: fleatherController,
113+
fleatherController: fleatherController!,
101114
isNewNote: widget.isNewNote,
102115
readOnly: readOnly,
103116
autofocus: autofocus,
117+
setupFocusNode: setupEditorFocus,
104118
);
105119
toolbar = RichTextEditorToolbar(
106120
fleatherController: fleatherController,
@@ -111,12 +125,14 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
111125
isNewNote: widget.isNewNote,
112126
readOnly: readOnly,
113127
autofocus: autofocus,
128+
setupFocusNode: setupEditorFocus,
114129
);
115130
case ChecklistNote note:
116131
contentEditor = ChecklistEditor(
117132
note: note,
118133
isNewNote: widget.isNewNote,
119134
readOnly: readOnly,
135+
setupFocusNode: setupEditorFocus,
120136
);
121137
}
122138

lib/pages/editor/widgets/checklist/checklist_editor.dart

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,22 @@ import '../../../../models/note/note.dart';
66
import '../../../../models/note/note_status.dart';
77
import '../../../../providers/notes/notes_provider.dart';
88
import '../../../../providers/notifiers/notifiers.dart';
9+
import '../text_editor.dart';
910

1011
/// Checklist editor.
11-
class ChecklistEditor extends ConsumerWidget {
12+
class ChecklistEditor extends TextEditor {
1213
/// Editor allowing to edit the checklist content of a [ChecklistNote].
1314
const ChecklistEditor({
1415
super.key,
1516
required this.note,
16-
required this.isNewNote,
17-
required this.readOnly,
17+
required super.isNewNote,
18+
required super.readOnly,
19+
super.setupFocusNode,
1820
});
1921

2022
/// The note to display.
2123
final ChecklistNote note;
2224

23-
/// Whether the note was just created.
24-
final bool isNewNote;
25-
26-
/// Whether the text fields are read only.
27-
final bool readOnly;
28-
2925
/// Called when an item of the checklist changes with the new [checklistLines].
3026
void onChecklistChanged(WidgetRef ref, List<ChecklistLine> checklistLines) {
3127
ChecklistNote newNote = note
@@ -35,7 +31,7 @@ class ChecklistEditor extends ConsumerWidget {
3531
ref.read(notesProvider(status: NoteStatus.available, label: currentLabelFilter).notifier).edit(newNote);
3632
}
3733

38-
@override
34+
/// Stateless [build].
3935
Widget build(BuildContext context, WidgetRef ref) {
4036
return Column(
4137
children: [
@@ -50,4 +46,16 @@ class ChecklistEditor extends ConsumerWidget {
5046
],
5147
);
5248
}
49+
50+
/// Boilerplate from [ConsumerWidget] from which [ChecklistEditor] originally extends.
51+
@override
52+
ConsumerState<ChecklistEditor> createState() => _ChecklistEditorState();
53+
}
54+
55+
/// Stateless stuff
56+
class _ChecklistEditorState extends TextEditorState<ChecklistEditor> {
57+
@override
58+
Widget build(BuildContext context) {
59+
return widget.build(context, ref);
60+
}
5361
}

0 commit comments

Comments
 (0)