mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 21:27:01 +01:00
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>
70 lines
2.0 KiB
Dart
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.
|