mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 21:27:01 +01:00
Editing an existing note looked nothing like capturing a new one: it always showed the template dropdown, the View/Guided/Raw pills and the priority/status row. The cause was one missing argument -- both screens already embedded the same NoteEditor, but NoteDetailScreen never passed advancedMode, which defaults to true in the widget while the app-wide AppSettings.advancedMode defaults to false. Extract the shared form as three single-purpose components rather than one widget owning several jobs: - note_meta_row.dart renders the priority/status row (was capture_meta_row.dart; drops the private _MetaDropdown duplicate the detail screen carried, which used DropdownButtonFormField where the shared one uses DropdownButton). - note_form_chrome_gate.dart owns chrome visibility and nothing else, replacing the _chromeVisible flag both screens copy-pasted. - note_form.dart composes those two with NoteEditor. appSettings is now a required parameter through NotesListScreen and NoteDetailScreen. Required, not defaulted: a silent default is exactly what caused this, so it must not be reintroducible. Also in the capture app bar: drop the decorative Icons.list_alt_outlined beside the note count, since the adjacent Icons.list button already names what is counted, and move the count to the right of that button. The finalize-and-clear action becomes Icons.check instead of Icons.add -- it never saved anything (every keystroke is already persisted), so a plus sign misdescribed it. 314 tests, 100% line coverage. Verified in a running web build that edit and capture render identically with advanced mode both off and on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
3.2 KiB
Dart
122 lines
3.2 KiB
Dart
/// A note's metadata controls: the priority/status row and the generic
|
|
/// dropdown behind it.
|
|
///
|
|
/// Shared by both editing surfaces -- capture and the detail screen -- so the
|
|
/// two cannot drift apart. Public only because Dart privacy is library-scoped.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:todo/data/note.dart';
|
|
|
|
/// A compact labelled dropdown for picking an enum value (priority/status).
|
|
///
|
|
/// Generic over the enum type [T] so the same control drives both pickers
|
|
/// without duplication; [labelOf] maps a value to its display string.
|
|
class MetaDropdown<T> extends StatelessWidget {
|
|
/// Creates a dropdown showing [value] out of [values].
|
|
const MetaDropdown({
|
|
required this.label,
|
|
required this.value,
|
|
required this.values,
|
|
required this.labelOf,
|
|
required this.onChanged,
|
|
super.key,
|
|
});
|
|
|
|
/// Label shown above the control.
|
|
final String label;
|
|
|
|
/// Currently selected value.
|
|
final T value;
|
|
|
|
/// Every selectable value.
|
|
final List<T> values;
|
|
|
|
/// Maps a value to its display string.
|
|
final String Function(T) labelOf;
|
|
|
|
/// Called with the newly picked value.
|
|
final ValueChanged<T> onChanged;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InputDecorator(
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
isDense: true,
|
|
border: const OutlineInputBorder(),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<T>(
|
|
value: value,
|
|
isDense: true,
|
|
isExpanded: true,
|
|
items: [
|
|
for (final v in values)
|
|
DropdownMenuItem<T>(value: v, child: Text(labelOf(v))),
|
|
],
|
|
onChanged: (v) {
|
|
if (v != null) onChanged(v);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A note's priority and status pickers, side by side.
|
|
///
|
|
/// Lays the row out and nothing else: whether it is visible at all is
|
|
/// [NoteFormChromeGate]'s job, not this widget's.
|
|
class NoteMetaRow extends StatelessWidget {
|
|
/// Creates the metadata row for the current values.
|
|
const NoteMetaRow({
|
|
required this.priority,
|
|
required this.status,
|
|
required this.onPriorityChanged,
|
|
required this.onStatusChanged,
|
|
super.key,
|
|
});
|
|
|
|
/// The note's current priority.
|
|
final Priority priority;
|
|
|
|
/// The note's current status.
|
|
final Status status;
|
|
|
|
/// Called with a newly picked priority.
|
|
final ValueChanged<Priority> onPriorityChanged;
|
|
|
|
/// Called with a newly picked status.
|
|
final ValueChanged<Status> onStatusChanged;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: MetaDropdown<Priority>(
|
|
label: 'Priority',
|
|
value: priority,
|
|
values: Priority.values,
|
|
labelOf: (p) => p.label,
|
|
onChanged: onPriorityChanged,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: MetaDropdown<Status>(
|
|
label: 'Status',
|
|
value: status,
|
|
values: Status.values,
|
|
labelOf: (s) => s.label,
|
|
onChanged: onStatusChanged,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|