Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
2026-06-27 12:10:21 +02:00
|
|
|
import '../data/note.dart';
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
import '../data/note_template.dart';
|
|
|
|
|
import 'markdown_view.dart';
|
|
|
|
|
|
|
|
|
|
/// Which view of the note the editor is currently showing.
|
|
|
|
|
enum NoteEditorMode {
|
|
|
|
|
/// Read-only rendered Markdown (headings, guidance, bullets).
|
|
|
|
|
preview,
|
|
|
|
|
|
2026-06-27 12:15:19 +02:00
|
|
|
/// Full-screen per-step view, one step per template section.
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
guided,
|
|
|
|
|
|
|
|
|
|
/// A single text field showing the assembled Markdown verbatim.
|
|
|
|
|
raw,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A guided editor for a note's text, shared by the capture and detail
|
|
|
|
|
/// screens.
|
|
|
|
|
///
|
|
|
|
|
/// It is a *view* over plain text: it parses [initialText] into template
|
|
|
|
|
/// sections and reports the re-assembled text through [onChanged] on every
|
|
|
|
|
/// edit. Storage stays plain text, so sync and markdown export are unaffected.
|
|
|
|
|
///
|
|
|
|
|
/// Modes (see [NoteEditorMode]):
|
|
|
|
|
/// * **Preview** — the note rendered as Markdown, read-only.
|
2026-06-27 12:15:19 +02:00
|
|
|
/// * **Guided** — a full-screen per-step view, one step per template
|
|
|
|
|
/// section, with guidance on what to write and why the LLM needs it.
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
/// * **Raw** — a single text field showing the assembled text verbatim.
|
|
|
|
|
///
|
|
|
|
|
/// Non-conforming or freeform text never enters the guided stepper (we never
|
|
|
|
|
/// force it into the template), so for such text Guided is unavailable and the
|
|
|
|
|
/// editable source stays the raw body, preserving the user's content.
|
2026-06-27 12:10:21 +02:00
|
|
|
///
|
|
|
|
|
/// Entering Guided on an empty draft first runs a two-step wizard (priority,
|
|
|
|
|
/// then template) via [onPriorityChanged], since those choices only make
|
|
|
|
|
/// sense once, before there's anything to guide. Guided itself — wizard or
|
2026-06-27 12:15:19 +02:00
|
|
|
/// bare step page — hides the template/mode chrome entirely (just a back arrow
|
2026-06-27 12:10:21 +02:00
|
|
|
/// to return to Raw); [onChromeVisibleChanged] tells the parent screen to
|
|
|
|
|
/// hide its own priority/status row in sync.
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
class NoteEditor extends StatefulWidget {
|
|
|
|
|
const NoteEditor({
|
|
|
|
|
required this.onChanged,
|
2026-06-27 12:10:21 +02:00
|
|
|
required this.priority,
|
|
|
|
|
required this.onPriorityChanged,
|
|
|
|
|
required this.onChromeVisibleChanged,
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
this.initialText = '',
|
|
|
|
|
this.initialTemplate,
|
|
|
|
|
this.initialMode = NoteEditorMode.guided,
|
|
|
|
|
this.autofocus = false,
|
|
|
|
|
super.key,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/// Called with the freshly assembled note text on every edit.
|
|
|
|
|
final ValueChanged<String> onChanged;
|
|
|
|
|
|
2026-06-27 12:10:21 +02:00
|
|
|
/// The note's current priority, shown as the wizard's starting selection.
|
|
|
|
|
final Priority priority;
|
|
|
|
|
|
|
|
|
|
/// Called when the priority wizard step is confirmed (Guided "Start").
|
|
|
|
|
final ValueChanged<Priority> onPriorityChanged;
|
|
|
|
|
|
|
|
|
|
/// Called whenever the editor's own chrome (template dropdown, mode
|
|
|
|
|
/// selector) is shown/hidden, so the parent screen can hide its
|
|
|
|
|
/// priority/status row in sync while Guided (wizard or bare stepper) is
|
|
|
|
|
/// active.
|
|
|
|
|
final ValueChanged<bool> onChromeVisibleChanged;
|
|
|
|
|
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
/// Existing note text to load. Empty for a fresh draft.
|
|
|
|
|
final String initialText;
|
|
|
|
|
|
|
|
|
|
/// Template to author with. When null the template is detected from
|
|
|
|
|
/// [initialText] (used when opening an existing note).
|
|
|
|
|
final NoteTemplate? initialTemplate;
|
|
|
|
|
|
|
|
|
|
/// Preferred mode to open in. Falls back to [NoteEditorMode.raw] when
|
|
|
|
|
/// [NoteEditorMode.guided] is requested for text that can't be guided
|
|
|
|
|
/// (freeform template or non-conforming content).
|
|
|
|
|
final NoteEditorMode initialMode;
|
|
|
|
|
|
|
|
|
|
/// Autofocus the first field, so a fresh capture needs zero taps before
|
|
|
|
|
/// typing — preserving the app's instant-capture invariant.
|
|
|
|
|
final bool autofocus;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<NoteEditor> createState() => _NoteEditorState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _NoteEditorState extends State<NoteEditor> {
|
|
|
|
|
late NoteTemplate _template;
|
|
|
|
|
late NoteEditorMode _mode;
|
|
|
|
|
|
|
|
|
|
/// Whether the editable content currently lives in [_body] (raw source)
|
|
|
|
|
/// rather than the per-section controllers (guided source). Preview keeps
|
|
|
|
|
/// whichever source was last active so [_currentText] stays correct.
|
|
|
|
|
late bool _rawSource;
|
|
|
|
|
|
|
|
|
|
int _currentStep = 0;
|
|
|
|
|
|
2026-06-27 12:10:21 +02:00
|
|
|
/// Whether the priority+template entry wizard is showing in place of the
|
|
|
|
|
/// normal chrome. True only between tapping Guided on an empty draft and
|
|
|
|
|
/// either "Start" (which flips to bare Guided) or "Cancel" (back to Raw).
|
|
|
|
|
bool _enteringGuided = false;
|
|
|
|
|
|
|
|
|
|
/// Which wizard step (0: priority, 1: template) is showing.
|
|
|
|
|
int _wizardStep = 0;
|
|
|
|
|
|
|
|
|
|
/// Working copies of the wizard's two choices, committed on "Start".
|
|
|
|
|
Priority _wizardPriority = Priority.defaultValue;
|
|
|
|
|
NoteTemplate _wizardTemplate = NoteTemplate.defaultTemplate;
|
|
|
|
|
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
/// One controller per structured section (keyed by section key).
|
|
|
|
|
final Map<String, TextEditingController> _section = {};
|
|
|
|
|
|
|
|
|
|
/// Single field used for the freeform [NoteTemplate.blank] body and for raw
|
|
|
|
|
/// mode of a structured template.
|
|
|
|
|
final TextEditingController _body = TextEditingController();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
final initial = widget.initialTemplate;
|
|
|
|
|
if (initial != null) {
|
|
|
|
|
_template = initial;
|
2026-06-27 12:10:21 +02:00
|
|
|
// Only prefer the guided (sections) source for an empty draft when the
|
|
|
|
|
// caller actually wants to open in Guided — an empty Raw draft (the new
|
|
|
|
|
// default) must keep the raw body as its source, or typing into the Raw
|
|
|
|
|
// field would silently update the (hidden, unused) section controllers
|
|
|
|
|
// instead of what's emitted via onChanged.
|
|
|
|
|
_loadSource(
|
|
|
|
|
initial,
|
|
|
|
|
widget.initialText,
|
|
|
|
|
preferGuided: widget.initialMode != NoteEditorMode.raw,
|
|
|
|
|
);
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
} else {
|
|
|
|
|
// Detect: does the text cleanly fit the design-spec template?
|
|
|
|
|
final parsed = parse(NoteTemplate.llmDesignSpec, widget.initialText);
|
|
|
|
|
if (parsed.conforms) {
|
|
|
|
|
_template = NoteTemplate.llmDesignSpec;
|
2026-06-27 12:10:21 +02:00
|
|
|
// Same Raw/source consistency rule as above: an explicit Raw request
|
|
|
|
|
// must keep the raw body as the source even for conforming text.
|
|
|
|
|
if (widget.initialMode == NoteEditorMode.raw) {
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
_body.text = widget.initialText;
|
|
|
|
|
} else {
|
|
|
|
|
_rawSource = false;
|
|
|
|
|
_fillSections(parsed.values);
|
|
|
|
|
}
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
} else {
|
|
|
|
|
// Freeform / legacy / hand-mangled — keep it as a raw body, untouched.
|
|
|
|
|
_template = NoteTemplate.blank;
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
_body.text = widget.initialText;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_mode = _resolveMode(widget.initialMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
for (final c in _section.values) {
|
|
|
|
|
c.dispose();
|
|
|
|
|
}
|
|
|
|
|
_body.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether the guided stepper can be *opened* right now: a structured
|
|
|
|
|
/// template whose current source still fits the template. Used to decide the
|
|
|
|
|
/// initial mode; the Guided segment itself is offered for any structured
|
|
|
|
|
/// template (a switch that no longer conforms is blocked at switch time).
|
|
|
|
|
bool get _canOpenGuided => !_template.isFreeform && !_rawSource;
|
|
|
|
|
|
|
|
|
|
/// Picks the mode to actually display: honours [desired] unless Guided was
|
|
|
|
|
/// asked for when it can't be opened, in which case fall back to Raw.
|
|
|
|
|
NoteEditorMode _resolveMode(NoteEditorMode desired) {
|
|
|
|
|
if (desired == NoteEditorMode.guided && !_canOpenGuided) {
|
|
|
|
|
return NoteEditorMode.raw;
|
|
|
|
|
}
|
|
|
|
|
return desired;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 12:15:19 +02:00
|
|
|
/// Ensures a controller exists for every section of [template].
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
void _ensureControllers(NoteTemplate template) {
|
|
|
|
|
for (final s in template.sections) {
|
|
|
|
|
_section.putIfAbsent(s.key, () => TextEditingController());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 12:15:19 +02:00
|
|
|
void _goToStep(int index) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_currentStep = index.clamp(0, _template.sections.length - 1);
|
|
|
|
|
});
|
2026-06-19 11:48:23 +02:00
|
|
|
}
|
|
|
|
|
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
void _fillSections(Map<String, String> values) {
|
|
|
|
|
_ensureControllers(_template);
|
|
|
|
|
for (final s in _template.sections) {
|
|
|
|
|
_section[s.key]!.text = values[s.key] ?? '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Loads [text] into [template], choosing the guided source when it conforms
|
|
|
|
|
/// (or when [preferGuided] and the text is empty) and the raw body otherwise.
|
|
|
|
|
void _loadSource(
|
|
|
|
|
NoteTemplate template,
|
|
|
|
|
String text, {
|
|
|
|
|
required bool preferGuided,
|
|
|
|
|
}) {
|
|
|
|
|
if (template.isFreeform) {
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
_body.text = text;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final parsed = parse(template, text);
|
|
|
|
|
if (parsed.conforms || (preferGuided && text.trim().isEmpty)) {
|
|
|
|
|
_rawSource = false;
|
|
|
|
|
_fillSections(parsed.values);
|
|
|
|
|
} else {
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
_body.text = text;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The current note text, assembled from whichever source is active.
|
|
|
|
|
String _currentText() {
|
|
|
|
|
if (_rawSource) return _body.text;
|
|
|
|
|
final values = {
|
|
|
|
|
for (final s in _template.sections) s.key: _section[s.key]?.text ?? '',
|
|
|
|
|
};
|
|
|
|
|
return assemble(_template, values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _emit() => widget.onChanged(_currentText());
|
|
|
|
|
|
|
|
|
|
void _switchTemplate(NoteTemplate next) {
|
|
|
|
|
if (next.id == _template.id) return;
|
|
|
|
|
final text = _currentText();
|
|
|
|
|
setState(() {
|
|
|
|
|
_template = next;
|
|
|
|
|
_currentStep = 0;
|
|
|
|
|
_loadSource(next, text, preferGuided: true);
|
|
|
|
|
_mode = _resolveMode(_mode);
|
|
|
|
|
});
|
|
|
|
|
_emit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Switches the displayed mode, converting the editable source as needed.
|
|
|
|
|
void _setMode(NoteEditorMode next) {
|
|
|
|
|
if (next == _mode) return;
|
|
|
|
|
setState(() {
|
|
|
|
|
switch (next) {
|
|
|
|
|
case NoteEditorMode.guided:
|
|
|
|
|
if (_rawSource) {
|
|
|
|
|
// raw -> guided: only if the edited text still fits the template.
|
|
|
|
|
final parsed = parse(_template, _body.text);
|
|
|
|
|
if (!parsed.conforms && _body.text.trim().isNotEmpty) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
const SnackBar(
|
|
|
|
|
content: Text(
|
|
|
|
|
"Text doesn't match the template — staying in raw",
|
|
|
|
|
),
|
|
|
|
|
duration: Duration(seconds: 2),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_fillSections(parsed.values);
|
|
|
|
|
_rawSource = false;
|
|
|
|
|
}
|
|
|
|
|
_currentStep = 0;
|
2026-06-27 12:10:21 +02:00
|
|
|
if (_currentText().trim().isEmpty) {
|
|
|
|
|
// Fresh, empty draft: priority and template are meaningful
|
|
|
|
|
// choices only once, so ask for them before showing the stepper.
|
|
|
|
|
_enteringGuided = true;
|
|
|
|
|
_wizardStep = 0;
|
|
|
|
|
_wizardPriority = widget.priority;
|
|
|
|
|
_wizardTemplate = NoteTemplate.defaultTemplate;
|
|
|
|
|
} else {
|
|
|
|
|
// Existing content: priority/template are already settled, so
|
|
|
|
|
// skip straight to the bare stepper rather than re-asking.
|
|
|
|
|
_mode = NoteEditorMode.guided;
|
|
|
|
|
}
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
case NoteEditorMode.raw:
|
|
|
|
|
if (!_rawSource) {
|
|
|
|
|
// guided -> raw: materialise the assembled text into the body.
|
|
|
|
|
_body.text = _currentText();
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
}
|
|
|
|
|
_mode = NoteEditorMode.raw;
|
|
|
|
|
case NoteEditorMode.preview:
|
|
|
|
|
// Read-only render of the current source; nothing to convert.
|
|
|
|
|
_mode = NoteEditorMode.preview;
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-06-27 12:10:21 +02:00
|
|
|
widget.onChromeVisibleChanged(
|
|
|
|
|
!_enteringGuided && _mode != NoteEditorMode.guided,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Commits the wizard's template choice and enters the bare stepper.
|
|
|
|
|
/// Distinct from [_switchTemplate]: that short-circuits when the template
|
|
|
|
|
/// is unchanged, which would skip flipping out of the wizard here.
|
|
|
|
|
void _enterGuidedWithTemplate(NoteTemplate template) {
|
|
|
|
|
final text = _currentText();
|
|
|
|
|
setState(() {
|
|
|
|
|
_template = template;
|
|
|
|
|
_currentStep = 0;
|
|
|
|
|
_loadSource(template, text, preferGuided: true);
|
|
|
|
|
_mode = NoteEditorMode.guided;
|
|
|
|
|
_enteringGuided = false;
|
|
|
|
|
});
|
|
|
|
|
_emit();
|
|
|
|
|
widget.onChromeVisibleChanged(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Aborts the wizard, returning to Raw with the chrome restored.
|
|
|
|
|
void _cancelWizard() {
|
|
|
|
|
setState(() => _enteringGuided = false);
|
|
|
|
|
widget.onChromeVisibleChanged(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Exits the bare stepper back to Raw, restoring the chrome.
|
|
|
|
|
void _exitGuided() {
|
|
|
|
|
setState(() {
|
|
|
|
|
if (!_rawSource) {
|
|
|
|
|
_body.text = _currentText();
|
|
|
|
|
_rawSource = true;
|
|
|
|
|
}
|
|
|
|
|
_mode = NoteEditorMode.raw;
|
|
|
|
|
});
|
|
|
|
|
widget.onChromeVisibleChanged(true);
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final theme = Theme.of(context);
|
2026-06-27 12:10:21 +02:00
|
|
|
if (_enteringGuided) return _buildGuidedEntryWizard(theme);
|
|
|
|
|
|
2026-06-27 17:32:50 +02:00
|
|
|
// Guided hides the template/mode chrome entirely. Return the flat step
|
|
|
|
|
// page directly so the TextField sits in THIS column's Expanded — same
|
|
|
|
|
// depth as _buildRaw, avoiding the nested-Column/Expanded layout issue
|
|
|
|
|
// that silently collapses the inner flex space in release builds.
|
|
|
|
|
if (_mode == NoteEditorMode.guided) return _buildStepPage(theme);
|
|
|
|
|
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
return Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
children: [
|
2026-06-27 17:32:50 +02:00
|
|
|
DropdownButtonFormField<String>(
|
|
|
|
|
initialValue: _template.id,
|
|
|
|
|
isDense: true,
|
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
|
labelText: 'Template',
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
isDense: true,
|
2026-06-27 17:32:50 +02:00
|
|
|
border: OutlineInputBorder(),
|
|
|
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
items: [
|
|
|
|
|
for (final t in NoteTemplate.all)
|
|
|
|
|
DropdownMenuItem(value: t.id, child: Text(t.label)),
|
|
|
|
|
],
|
|
|
|
|
onChanged: (id) {
|
|
|
|
|
if (id == null) return;
|
|
|
|
|
_switchTemplate(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 (see _setMode), and is
|
|
|
|
|
// blocked at switch time if the raw text no longer conforms.
|
|
|
|
|
if (!_template.isFreeform)
|
2026-06-27 12:10:21 +02:00
|
|
|
const ButtonSegment(
|
2026-06-27 17:32:50 +02:00
|
|
|
value: NoteEditorMode.guided,
|
|
|
|
|
icon: Icon(Icons.checklist),
|
|
|
|
|
label: Text('Guided'),
|
2026-06-27 12:10:21 +02:00
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
const ButtonSegment(
|
|
|
|
|
value: NoteEditorMode.raw,
|
|
|
|
|
icon: Icon(Icons.notes),
|
|
|
|
|
label: Text('Raw'),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
selected: {_mode},
|
|
|
|
|
onSelectionChanged: (s) => _setMode(s.first),
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
),
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
Expanded(child: _buildBody(theme)),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 12:10:21 +02:00
|
|
|
/// The two-step priority -> template wizard shown before a fresh draft
|
|
|
|
|
/// enters Guided. Replaces the normal chrome entirely (see [build]).
|
|
|
|
|
Widget _buildGuidedEntryWizard(ThemeData theme) {
|
|
|
|
|
return Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
IconButton(
|
|
|
|
|
icon: const Icon(Icons.close),
|
|
|
|
|
tooltip: 'Cancel',
|
|
|
|
|
onPressed: _cancelWizard,
|
|
|
|
|
),
|
|
|
|
|
Text(
|
|
|
|
|
'Step ${_wizardStep + 1} of 2',
|
|
|
|
|
style: theme.textTheme.labelLarge,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
if (_wizardStep == 0)
|
|
|
|
|
..._buildWizardPriorityStep()
|
|
|
|
|
else
|
|
|
|
|
..._buildWizardTemplateStep(),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Widget> _buildWizardPriorityStep() {
|
|
|
|
|
return [
|
|
|
|
|
DropdownButtonFormField<Priority>(
|
|
|
|
|
initialValue: _wizardPriority,
|
|
|
|
|
isDense: true,
|
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
|
labelText: 'Priority',
|
|
|
|
|
isDense: true,
|
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
|
|
|
),
|
|
|
|
|
items: [
|
|
|
|
|
for (final p in Priority.values)
|
|
|
|
|
DropdownMenuItem(value: p, child: Text(p.label)),
|
|
|
|
|
],
|
|
|
|
|
onChanged: (p) {
|
|
|
|
|
if (p != null) setState(() => _wizardPriority = p);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
Align(
|
|
|
|
|
alignment: Alignment.centerRight,
|
|
|
|
|
child: FilledButton(
|
|
|
|
|
onPressed: () => setState(() => _wizardStep = 1),
|
|
|
|
|
child: const Text('Next'),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Widget> _buildWizardTemplateStep() {
|
|
|
|
|
final templates = NoteTemplate.all.where((t) => !t.isFreeform).toList();
|
|
|
|
|
return [
|
|
|
|
|
DropdownButtonFormField<String>(
|
|
|
|
|
initialValue: _wizardTemplate.id,
|
|
|
|
|
isDense: true,
|
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
|
labelText: 'Template',
|
|
|
|
|
isDense: true,
|
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
|
|
|
),
|
|
|
|
|
items: [
|
|
|
|
|
for (final t in templates)
|
|
|
|
|
DropdownMenuItem(value: t.id, child: Text(t.label)),
|
|
|
|
|
],
|
|
|
|
|
onChanged: (id) {
|
|
|
|
|
if (id == null) return;
|
|
|
|
|
setState(
|
|
|
|
|
() => _wizardTemplate = templates.firstWhere((t) => t.id == id),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
|
|
|
children: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () => setState(() => _wizardStep = 0),
|
|
|
|
|
child: const Text('Back'),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
FilledButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
widget.onPriorityChanged(_wizardPriority);
|
|
|
|
|
_enterGuidedWithTemplate(_wizardTemplate);
|
|
|
|
|
},
|
|
|
|
|
child: const Text('Start'),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
Widget _buildBody(ThemeData theme) {
|
|
|
|
|
switch (_mode) {
|
|
|
|
|
case NoteEditorMode.preview:
|
|
|
|
|
return MarkdownView(text: _currentText());
|
|
|
|
|
case NoteEditorMode.raw:
|
|
|
|
|
return _buildRaw(theme);
|
|
|
|
|
case NoteEditorMode.guided:
|
2026-06-27 17:32:50 +02:00
|
|
|
// build() short-circuits to _buildStepPage before reaching here.
|
|
|
|
|
return _buildRaw(theme);
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildRaw(ThemeData theme) {
|
|
|
|
|
return TextField(
|
|
|
|
|
controller: _body,
|
|
|
|
|
autofocus: widget.autofocus,
|
|
|
|
|
maxLines: null,
|
|
|
|
|
expands: true,
|
|
|
|
|
textAlignVertical: TextAlignVertical.top,
|
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
|
style: theme.textTheme.bodyLarge,
|
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
|
border: InputBorder.none,
|
|
|
|
|
hintText: 'Write your idea…',
|
|
|
|
|
),
|
|
|
|
|
onChanged: (_) => _emit(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 17:32:50 +02:00
|
|
|
/// Full-screen per-step view returned directly from [build] so the
|
|
|
|
|
/// [TextField] is a first-level [Expanded] child of THIS column — the same
|
|
|
|
|
/// depth as [_buildRaw]. Nesting it inside a second Column broke the
|
|
|
|
|
/// flutter constraint chain in release builds (inner Expanded got 0 height).
|
2026-06-27 12:15:19 +02:00
|
|
|
Widget _buildStepPage(ThemeData theme) {
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
_ensureControllers(_template);
|
|
|
|
|
final sections = _template.sections;
|
2026-06-27 12:15:19 +02:00
|
|
|
final idx = _currentStep.clamp(0, sections.length - 1);
|
|
|
|
|
final section = sections[idx];
|
|
|
|
|
final total = sections.length;
|
|
|
|
|
final controller = _section[section.key]!;
|
|
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
children: [
|
2026-06-27 17:32:50 +02:00
|
|
|
// Back arrow — only exit from guided mode.
|
|
|
|
|
Align(
|
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
|
child: IconButton(
|
|
|
|
|
icon: const Icon(Icons.arrow_back),
|
|
|
|
|
tooltip: 'Exit guided',
|
|
|
|
|
onPressed: _exitGuided,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
// Progress bar + step counter.
|
2026-06-27 12:15:19 +02:00
|
|
|
Padding(
|
2026-06-27 17:32:50 +02:00
|
|
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 4),
|
2026-06-27 12:15:19 +02:00
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Text('${idx + 1} / $total', style: theme.textTheme.labelMedium),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: LinearProgressIndicator(value: (idx + 1) / total),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
// Section label + helper text.
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
section.isTitle ? 'title' : section.label,
|
|
|
|
|
style: theme.textTheme.titleMedium,
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 4),
|
|
|
|
|
Text(
|
|
|
|
|
section.helper,
|
|
|
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
|
|
|
color: theme.colorScheme.onSurfaceVariant,
|
2026-06-27 12:15:19 +02:00
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
// TextField fills all remaining space — directly in this Column's
|
|
|
|
|
// Expanded, same as _buildRaw, so expands:true works correctly.
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
|
|
|
|
child: TextField(
|
|
|
|
|
controller: controller,
|
|
|
|
|
autofocus: widget.autofocus && idx == 0,
|
|
|
|
|
expands: true,
|
|
|
|
|
maxLines: null,
|
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
|
textAlignVertical: TextAlignVertical.top,
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
hintText: section.hint,
|
|
|
|
|
border: const OutlineInputBorder(),
|
|
|
|
|
isDense: true,
|
|
|
|
|
),
|
|
|
|
|
onChanged: (_) {
|
|
|
|
|
setState(() {});
|
|
|
|
|
_emit();
|
|
|
|
|
},
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
),
|
2026-06-27 12:15:19 +02:00
|
|
|
),
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
),
|
2026-06-27 17:32:50 +02:00
|
|
|
// Navigation buttons — sibling of Expanded so keyboard pushes them up.
|
2026-06-27 12:15:19 +02:00
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
if (idx > 0)
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () => _goToStep(idx - 1),
|
|
|
|
|
child: const Text('Back'),
|
|
|
|
|
),
|
|
|
|
|
const Spacer(),
|
|
|
|
|
if (idx < total - 1)
|
|
|
|
|
FilledButton(
|
|
|
|
|
onPressed: () => _goToStep(idx + 1),
|
|
|
|
|
child: const Text('Next'),
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
FilledButton(onPressed: _exitGuided, child: const Text('Done')),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
Add full note view/editor with templates and Markdown render
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:31 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|