todo-app/lib/ui/note_sheet_details.dart
Krzysztof kuhy Rudnicki 5ea7ba7b73 Text-free list, editor, sync and image cues
Glyph + spoken label replaces every app-written string the guard
found: empty states, tile meta, sheet dates/fields, date presets,
template/section headings, status footer, snackbars, GitHub and
cloud-image results, upload/download badges, Material tooltips.
Guard tests (text_free_*_test) walk the painted tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 01:17:03 +01:00

64 lines
1.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:todo/data/note.dart';
import 'package:todo/images/attachment_links.dart';
/// [text] without the line that serves as the title.
String noteBodyOf(String text) {
final lines = text.split('\n');
final title = lines.indexWhere(
(l) => l.trim().isNotEmpty && attachmentOfLine(l.trim()) == null,
);
if (title < 0) return text.trim();
return [...lines.take(title), ...lines.skip(title + 1)].join('\n').trim();
}
String _stamp(DateTime t) {
String two(int n) => n.toString().padLeft(2, '0');
final date = '${t.year}-${two(t.month)}-${two(t.day)}';
return '$date ${two(t.hour)}:${two(t.minute)}';
}
/// What a grid tile leaves out: the note body and its two dates.
class NoteSheetDetails extends StatelessWidget {
/// Shows the body and the dates of [note].
const NoteSheetDetails({required this.note, super.key});
/// The note being shown.
final Note note;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context).textTheme;
final body = noteBodyOf(note.text);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (body.isNotEmpty) ...[
const SizedBox(height: 12),
Text(body, style: theme.bodyMedium),
],
const SizedBox(height: 12),
Wrap(
spacing: 12,
children: [
_dated(Icons.history, 'Edited', note.updatedAt, theme.bodySmall),
_dated(Icons.add, 'Created', note.createdAt, theme.bodySmall),
],
),
],
);
}
/// A glyph (edited = history, created = plus) before its timestamp.
Widget _dated(IconData icon, String spoken, DateTime t, TextStyle? style) =>
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, semanticLabel: spoken),
const SizedBox(width: 4),
Text(_stamp(t), style: style),
],
);
}