todo-app/lib/ui/note_tile.dart
Krzysztof kuhy Rudnicki 9d2aa1c178 Enforce the 250-line file cap, and split the first four files
Wires the shared gate (~/utils/scripts/check_file_length.sh) into a new
.pre-commit-config.yaml as a local hook, checking only the files in the
commit and failing rather than warning. No baseline, no allowlist.

The repo had a hand-written .git/hooks/pre-commit running two checks;
both are ported verbatim into the config rather than dropped, with the
version bump ordered last so it cannot rewrite pubspec.yaml for a commit
a gate is still about to reject. scripts/install_hooks.sh now installs
pre-commit (and removes the legacy hook) instead of writing its own.

.github/workflows/file-length.yml runs the same check on push, kept
separate from release-apk.yml so an over-long file fails lint rather than
blocking an APK release.

Splits the four cleanest violations, all behaviour-preserving:

  note_template.dart      370 -> 203 + parser (186)
  firebase_backend.dart   308 -> barrel (24) + project/store/client
  port_guard.dart         315 -> 232 + linux_system_probe (100)
  notes_list_screen.dart  698 -> 225 + 5 widget/nav files

Where a file had external callers, the original path stays as a barrel
that re-exports the split parts, so no call site changed.

Verified: 313 tests pass, coverage identical at 99.9% / 1922 lines
(per-file too -- notes_list's 281 covered lines are now 81+75+51+41+22+11),
flutter analyze clean, and a staged 260-line file makes git commit fail.

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

70 lines
2.0 KiB
Dart

/// One row of the notes list.
///
/// Split out of `notes_list_screen.dart` for file size; public only because
/// Dart privacy is library-scoped.
library;
import 'package:flutter/material.dart';
import 'package:todo/data/note.dart';
import 'package:todo/data/note_template.dart';
/// One row in the notes list: title, metadata line, and an actions button.
class NoteTile extends StatelessWidget {
/// Creates a tile for [note].
const NoteTile({
required this.note,
required this.onTap,
required this.onActions,
super.key,
});
/// The note this row displays.
final Note note;
/// Open the full note for reading/editing.
/// Called when the row body is tapped (opens the note).
final VoidCallback onTap;
/// Open the quick-actions sheet (priority/status/delete).
/// Called when the trailing actions button is tapped.
final VoidCallback onActions;
@override
Widget build(BuildContext context) {
final firstLine = noteTitle(note.text);
// Every note has a status and a priority now, so both are always shown.
final meta = <String>[
note.status.label,
note.priority.label,
'edited ${_relative(note.updatedAt)}',
].join(' · ');
return ListTile(
title: Text(
firstLine.isEmpty ? '(empty)' : firstLine,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(meta),
trailing: IconButton(
icon: const Icon(Icons.more_vert),
tooltip: 'Quick actions',
onPressed: onActions,
),
onTap: onTap,
onLongPress: onActions,
);
}
/// Compact relative time like "2m ago" for the list subtitle.
String _relative(DateTime t) {
final d = DateTime.now().difference(t);
if (d.inMinutes < 1) return 'just now';
if (d.inHours < 1) return '${d.inMinutes}m ago';
if (d.inDays < 1) return '${d.inHours}h ago';
return '${d.inDays}d ago';
}
}
/// Bottom sheet for editing one note's priority/status or deleting it.