Skip to content

Commit a9bc45a

Browse files
committed
Test editorFocusNode and stuff
1 parent 15fc7e2 commit a9bc45a

10 files changed

Lines changed: 262 additions & 187 deletions

File tree

lib/common/constants/constants.dart

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

31-
/// Focus node of the note content text editor.
32-
final editorFocusNode = FocusNode(debugLabel: 'Editor focus node');
33-
3431
/// Utilities for the Storage Access Framework (SAF) APIs.
3532
final safUtil = SafUtil();
3633

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

Lines changed: 91 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -155,100 +155,105 @@ class _BackAppBarState extends ConsumerState<EditorAppBar> {
155155
return EmptyPlaceholder();
156156
}
157157

158-
final editorController = fleatherControllerNotifier.value;
159158
final showEditorModeButton = PreferenceKey.editorModeButton.getPreferenceOrDefault();
160159
final enableLabels = PreferenceKey.enableLabels.getPreferenceOrDefault();
161160

162161
return ValueListenableBuilder(
163-
valueListenable: editorHasFocusNotifier,
164-
builder: (context, editorHasFocus, child) {
162+
valueListenable: fleatherControllerNotifier,
163+
builder: (context, editorController, child) {
165164
return ValueListenableBuilder(
166-
valueListenable: isEditorInEditModeNotifier,
167-
builder: (context, isEditMode, child) {
168-
return AppBar(
169-
leading: BackButton(),
170-
actions: [
171-
if (note.status == NoteStatus.available) ...[
172-
if (note.type == NoteType.richText) ...[
173-
ValueListenableBuilder(
174-
valueListenable: fleatherControllerCanUndoNotifier,
175-
builder: (context, canUndo, child) {
176-
final enableUndo = editorHasFocus &&
177-
canUndo &&
178-
editorController != null &&
179-
editorController.canUndo &&
180-
isEditMode;
165+
valueListenable: editorHasFocusNotifier,
166+
builder: (context, editorHasFocus, child) {
167+
return ValueListenableBuilder(
168+
valueListenable: isEditorInEditModeNotifier,
169+
builder: (context, isEditMode, child) {
170+
return AppBar(
171+
leading: BackButton(),
172+
actions: [
173+
if (note.status == NoteStatus.available) ...[
174+
if (note.type == NoteType.richText) ...[
175+
ValueListenableBuilder(
176+
valueListenable: fleatherControllerCanUndoNotifier,
177+
builder: (context, canUndo, child) {
178+
final enableUndo = editorHasFocus &&
179+
canUndo &&
180+
editorController != null &&
181+
editorController.canUndo &&
182+
isEditMode;
181183

182-
return IconButton(
183-
icon: const Icon(Icons.undo),
184-
tooltip: l.tooltip_undo,
185-
onPressed: enableUndo ? undo : null,
186-
);
187-
},
188-
),
189-
ValueListenableBuilder(
190-
valueListenable: fleatherControllerCanRedoNotifier,
191-
builder: (context, canRedo, child) {
192-
final enableRedo = editorHasFocus && canRedo && isEditMode;
184+
return IconButton(
185+
icon: const Icon(Icons.undo),
186+
tooltip: l.tooltip_undo,
187+
onPressed: enableUndo ? undo : null,
188+
);
189+
},
190+
),
191+
ValueListenableBuilder(
192+
valueListenable: fleatherControllerCanRedoNotifier,
193+
builder: (context, canRedo, child) {
194+
final enableRedo = editorHasFocus && canRedo && isEditMode;
193195

194-
return IconButton(
195-
icon: const Icon(Icons.redo),
196-
tooltip: l.tooltip_redo,
197-
onPressed: enableRedo ? redo : null,
198-
);
199-
},
200-
),
201-
],
202-
if (showEditorModeButton)
203-
ValueListenableBuilder(
204-
valueListenable: isEditorInEditModeNotifier,
205-
builder: (context, isEditMode, child) => IconButton(
206-
icon: Icon(isEditMode ? Icons.visibility : Icons.edit),
207-
tooltip:
208-
isEditMode ? l.tooltip_fab_toggle_editor_mode_read : l.tooltip_fab_toggle_editor_mode_edit,
209-
onPressed: switchMode,
196+
return IconButton(
197+
icon: const Icon(Icons.redo),
198+
tooltip: l.tooltip_redo,
199+
onPressed: enableRedo ? redo : null,
200+
);
201+
},
202+
),
203+
],
204+
if (showEditorModeButton)
205+
ValueListenableBuilder(
206+
valueListenable: isEditorInEditModeNotifier,
207+
builder: (context, isEditMode, child) => IconButton(
208+
icon: Icon(isEditMode ? Icons.visibility : Icons.edit),
209+
tooltip: isEditMode
210+
? l.tooltip_fab_toggle_editor_mode_read
211+
: l.tooltip_fab_toggle_editor_mode_edit,
212+
onPressed: switchMode,
213+
),
214+
),
215+
PopupMenuButton<EditorAvailableMenuOption>(
216+
itemBuilder: (context) => ([
217+
EditorAvailableMenuOption.copy.popupMenuItem(context),
218+
EditorAvailableMenuOption.share.popupMenuItem(context),
219+
const PopupMenuDivider(),
220+
EditorAvailableMenuOption.togglePin.popupMenuItem(context, alternative: note.pinned),
221+
if (enableLabels) EditorAvailableMenuOption.selectLabels.popupMenuItem(context),
222+
const PopupMenuDivider(),
223+
EditorAvailableMenuOption.archive.popupMenuItem(context),
224+
EditorAvailableMenuOption.delete.popupMenuItem(context),
225+
const PopupMenuDivider(),
226+
EditorAvailableMenuOption.about.popupMenuItem(context),
227+
]),
228+
onSelected: onAvailableMenuOptionSelected,
229+
),
230+
],
231+
if (note.status == NoteStatus.archived)
232+
PopupMenuButton<EditorArchivedMenuOption>(
233+
itemBuilder: (context) => ([
234+
EditorArchivedMenuOption.copy.popupMenuItem(context),
235+
EditorArchivedMenuOption.share.popupMenuItem(context),
236+
const PopupMenuDivider(),
237+
EditorArchivedMenuOption.unarchive.popupMenuItem(context),
238+
const PopupMenuDivider(),
239+
EditorArchivedMenuOption.about.popupMenuItem(context),
240+
]),
241+
onSelected: onArchivedMenuOptionSelected,
210242
),
211-
),
212-
PopupMenuButton<EditorAvailableMenuOption>(
213-
itemBuilder: (context) => ([
214-
EditorAvailableMenuOption.copy.popupMenuItem(context),
215-
EditorAvailableMenuOption.share.popupMenuItem(context),
216-
const PopupMenuDivider(),
217-
EditorAvailableMenuOption.togglePin.popupMenuItem(context, alternative: note.pinned),
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-
],
243+
if (note.status == NoteStatus.deleted)
244+
PopupMenuButton<EditorDeletedMenuOption>(
245+
itemBuilder: (context) => ([
246+
EditorDeletedMenuOption.restore.popupMenuItem(context),
247+
EditorDeletedMenuOption.deletePermanently.popupMenuItem(context),
248+
const PopupMenuDivider(),
249+
EditorDeletedMenuOption.about.popupMenuItem(context),
250+
]),
251+
onSelected: onDeletedMenuOptionSelected,
252+
),
253+
Padding(padding: Paddings.appBarActionsEnd),
254+
],
255+
);
256+
},
252257
);
253258
},
254259
);

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.getPreferenceOrDefault();
32+
// If this is a new note, force the editing mode
33+
isEditorInEditModeNotifier.value = isNewNote || !PreferenceKey.openEditorReadingMode.getPreferenceOrDefault();
3334

3435
push(
3536
context,

lib/pages/editor/editor_page.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'widgets/markdown/markdown_editor.dart';
1919
import 'widgets/plain_text/plain_text_editor.dart';
2020
import 'widgets/rich_text/rich_text_editor.dart';
2121
import 'widgets/rich_text/rich_text_editor_toolbar.dart';
22+
import 'widgets/text_editor.dart';
2223
import 'widgets/title_editor.dart';
2324

2425
/// Editor page.
@@ -41,8 +42,15 @@ class NotesEditorPage extends ConsumerStatefulWidget {
4142
}
4243

4344
class _EditorState extends ConsumerState<NotesEditorPage> {
45+
/// Focus node of the note content text editor currently in use.
46+
FocusNode? _editorFocusNode;
47+
48+
void setupEditorFocus(FocusNode? editorFocusNode) {
49+
_editorFocusNode = editorFocusNode;
50+
}
51+
4452
void requestEditorFocus() {
45-
editorFocusNode.requestFocus();
53+
_editorFocusNode?.requestFocus();
4654
}
4755

4856
@override
@@ -55,6 +63,12 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
5563
return ValueListenableBuilder(
5664
valueListenable: currentNoteNotifier,
5765
builder: (context, currentNote, child) {
66+
// Moved up fleatherController here so that it survives isEditorInEditMode changes
67+
// otherwise the cursor will reset to the front when switching between edit/reading mode
68+
final fleatherController = currentNote is RichTextNote
69+
? FleatherController(document: currentNote.document) : null;
70+
fleatherControllerNotifier.value = fleatherController;
71+
5872
return ValueListenableBuilder(
5973
valueListenable: isEditorInEditModeNotifier,
6074
builder: (context, isEditorInEditMode, child) {
@@ -67,7 +81,7 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
6781
final showLabelsList =
6882
enableLabels && showLabelsListInEditorPage && currentNote.labelsVisibleSorted.isNotEmpty;
6983

70-
Widget contentEditor;
84+
TextEditor contentEditor;
7185
Widget? toolbar;
7286
switch (currentNote) {
7387
case PlainTextNote note:
@@ -76,32 +90,34 @@ class _EditorState extends ConsumerState<NotesEditorPage> {
7690
isNewNote: widget.isNewNote,
7791
readOnly: readOnly,
7892
autofocus: autofocus,
93+
setupFocusNode: setupEditorFocus,
7994
);
8095
case RichTextNote note:
81-
final fleatherController = FleatherController(document: note.document);
82-
fleatherControllerNotifier.value = fleatherController;
8396
contentEditor = RichTextEditor(
8497
note: note,
85-
fleatherController: fleatherController,
98+
fleatherController: fleatherController!,
8699
isNewNote: widget.isNewNote,
87100
readOnly: readOnly,
88101
autofocus: autofocus,
102+
setupFocusNode: setupEditorFocus,
89103
);
90104
toolbar = RichTextEditorToolbar(
91-
fleatherController: fleatherController,
105+
fleatherController: fleatherController!,
92106
);
93107
case MarkdownNote note:
94108
contentEditor = MarkdownEditor(
95109
note: note,
96110
isNewNote: widget.isNewNote,
97111
readOnly: readOnly,
98112
autofocus: autofocus,
113+
setupFocusNode: setupEditorFocus,
99114
);
100115
case ChecklistNote note:
101116
contentEditor = ChecklistEditor(
102117
note: note,
103118
isNewNote: widget.isNewNote,
104119
readOnly: readOnly,
120+
setupFocusNode: setupEditorFocus,
105121
);
106122
}
107123

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

Lines changed: 17 additions & 9 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
@@ -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
}

lib/pages/editor/widgets/markdown/markdown_editor.dart

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,28 @@ import '../../../../models/note/note.dart';
1010
import '../../../../models/note/note_status.dart';
1111
import '../../../../providers/notes/notes_provider.dart';
1212
import '../../../../providers/notifiers/notifiers.dart';
13+
import '../text_editor.dart';
1314

1415
/// Markdown editor.
15-
class MarkdownEditor extends ConsumerStatefulWidget {
16+
class MarkdownEditor extends TextEditor {
1617
/// Markdown allowing to edit the markdown text content of a [MarkdownNote].
1718
const MarkdownEditor({
1819
super.key,
1920
required this.note,
20-
required this.isNewNote,
21-
required this.readOnly,
22-
required this.autofocus,
21+
required super.isNewNote,
22+
required super.readOnly,
23+
required super.autofocus,
24+
super.setupFocusNode,
2325
});
2426

2527
/// The note to display.
2628
final MarkdownNote note;
2729

28-
/// Whether the note was just created.
29-
final bool isNewNote;
30-
31-
/// Whether the text fields are read only.
32-
final bool readOnly;
33-
34-
/// Whether the text field should request focus.
35-
final bool autofocus;
36-
3730
@override
3831
ConsumerState<MarkdownEditor> createState() => _MarkdownEditorState();
3932
}
4033

41-
class _MarkdownEditorState extends ConsumerState<MarkdownEditor> {
34+
class _MarkdownEditorState extends TextEditorState<MarkdownEditor> {
4235
late final TextEditingController contentTextController;
4336

4437
@override

0 commit comments

Comments
 (0)