todo-app/lib/ui/note_editor_chrome.dart
Krzysztof kuhy Rudnicki 63725e726f Split note_editor.dart into nine files under the cap
702 -> 220 lines, decomposed rather than just moved, because a single
~600-line State class was the actual problem:

  note_editor_document.dart   the text model -- template, controllers, and
                              which of the two sources (raw body vs. section
                              fields) is authoritative. None of it needed a
                              BuildContext or a setState, which is what made
                              it the honest seam. The raw<->guided conversions
                              and the initial-template detection moved here as
                              tryAdoptGuided/materialiseToRaw/forInitialText.
  note_editor_wizard.dart     the two-step priority+template wizard, which now
                              owns its own step state instead of parking three
                              fields on the editor.
  note_editor_step_page.dart  the guided per-step view.
  note_editor_chrome.dart     the template picker + mode toggle.
  note_editor_raw_field.dart  the raw-mode text field.
  note_editor_mode.dart       the mode enum, re-exported so callers are
                              unaffected.
  note_editor_{widget,views}  parts, not libraries: createState returns a
                              private class, so the widget and its State
                              cannot be split across libraries.

The layout comments about Expanded/expands:true are load-bearing (nesting
broke the constraint chain in release builds before), so they travelled with
the code they describe rather than being summarised away.

Verified: 313 tests pass, flutter analyze clean. Covered lines went UP,
1922 -> 1958, because the wizard's state is now exercised in a widget of its
own; every new file reports 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:12:04 +02:00

96 lines
3.1 KiB
Dart

/// The editor's chrome: the template picker and the View/Guided/Raw toggle.
///
/// Split out of `note_editor.dart` for file size. Stateless and callback-only
/// — every choice it offers is applied by `_NoteEditorState`, which owns the
/// template and mode. Guided hides this chrome entirely, so it is only ever
/// built in Preview and Raw.
library;
import 'package:flutter/material.dart';
import 'package:todo/data/note_template.dart';
import 'package:todo/ui/note_editor_mode.dart';
/// Template dropdown above a segmented View / Guided / Raw selector.
class NoteEditorChrome extends StatelessWidget {
/// Creates the chrome for [template] in [mode].
const NoteEditorChrome({
required this.template,
required this.mode,
required this.onTemplateSelected,
required this.onModeSelected,
super.key,
});
/// The template currently selected in the dropdown.
final NoteTemplate template;
/// The mode the segmented button shows as selected.
final NoteEditorMode mode;
/// Called with the template the user picked.
final ValueChanged<NoteTemplate> onTemplateSelected;
/// Called with the mode the user picked.
final ValueChanged<NoteEditorMode> onModeSelected;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
DropdownButtonFormField<String>(
initialValue: template.id,
decoration: const InputDecoration(
labelText: 'Template',
isDense: true,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
items: [
for (final t in NoteTemplate.all)
DropdownMenuItem(value: t.id, child: Text(t.label)),
],
onChanged: (id) {
if (id == null) return;
onTemplateSelected(
NoteTemplate.all.firstWhere((t) => t.id == id),
);
},
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerLeft,
child: SegmentedButton<NoteEditorMode>(
showSelectedIcon: false,
segments: [
const ButtonSegment(
value: NoteEditorMode.preview,
icon: Icon(Icons.visibility_outlined),
label: Text('View'),
),
// Guided is offered for any structured template; tapping it
// on an empty draft opens the wizard, and is blocked at
// switch time if the raw text no longer conforms.
if (!template.isFreeform)
const ButtonSegment(
value: NoteEditorMode.guided,
icon: Icon(Icons.checklist),
label: Text('Guided'),
),
const ButtonSegment(
value: NoteEditorMode.raw,
icon: Icon(Icons.notes),
label: Text('Raw'),
),
],
selected: {mode},
onSelectionChanged: (s) => onModeSelected(s.first),
),
),
],
);
}
}