mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 19:07:02 +01:00
Below 600dp the notes list is a compact tile grid (title + priority/status cues). Tile height is derived so whole rows exactly fill the viewport between the status and navigation bars; past a screenful it scrolls vertically (no paging). Body and dates moved into the long-press sheet. Wider windows keep the list. Not using design_system FitGrid: it is square-celled and pages horizontally. Pins: design_system v0.3.0 (newest tag); also bumped crdt_sync_dart 0.11.2, sync_settings_ui 0.4.2, file_picker 13.3.1, idb_shim 2.11.1, sqflite_common_ffi 2.4.5, web 1.1.2 because the freshness gate was already red on them at HEAD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.5 KiB
Dart
116 lines
3.5 KiB
Dart
/// One compact tile of the notes grid: title plus priority and status cues.
|
|
library;
|
|
|
|
import 'package:design_system/design_system.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/data/note_template.dart';
|
|
|
|
/// A note as a grid cell. It fills whatever cell it is given.
|
|
class NoteGridTile extends StatelessWidget {
|
|
/// Creates a tile for [note].
|
|
const NoteGridTile({
|
|
required this.note,
|
|
required this.onTap,
|
|
required this.onActions,
|
|
super.key,
|
|
});
|
|
|
|
/// The note this tile displays.
|
|
final Note note;
|
|
|
|
/// Open the full note.
|
|
final VoidCallback onTap;
|
|
|
|
/// Open the per-note sheet (body, dates, priority/status, delete).
|
|
final VoidCallback onActions;
|
|
|
|
/// Danger for high, the accent for medium, muted for low.
|
|
Color accentFor(ColorScheme scheme) => switch (note.priority) {
|
|
Priority.high => scheme.error,
|
|
Priority.medium => scheme.primary,
|
|
Priority.low => scheme.outline,
|
|
};
|
|
|
|
static IconData _statusIcon(Status s) => switch (s) {
|
|
Status.todo => Icons.radio_button_unchecked,
|
|
Status.inProgress => Icons.timelapse,
|
|
Status.done => Icons.check_circle_outline,
|
|
Status.abandoned => Icons.cancel_outlined,
|
|
};
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final scheme = theme.colorScheme;
|
|
final title = noteTitle(note.text);
|
|
final cue = theme.textTheme.labelSmall;
|
|
final closed =
|
|
note.status == Status.done || note.status == Status.abandoned;
|
|
return Material(
|
|
color: scheme.surfaceContainerHigh,
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
onLongPress: onActions,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
left: BorderSide(color: accentFor(scheme), width: 4),
|
|
),
|
|
),
|
|
padding: const EdgeInsets.fromLTRB(
|
|
AppSpacing.sm,
|
|
AppSpacing.sm,
|
|
AppSpacing.sm,
|
|
AppSpacing.xs,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Expanded: a big text scale shortens the title, never overflows.
|
|
Expanded(
|
|
child: Text(
|
|
title.isEmpty ? '(empty)' : title,
|
|
maxLines: 3,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: closed ? scheme.onSurfaceVariant : null,
|
|
decoration: closed ? TextDecoration.lineThrough : null,
|
|
),
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
_statusIcon(note.status),
|
|
size: 14,
|
|
color: scheme.onSurfaceVariant,
|
|
),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Flexible(
|
|
child: Text(
|
|
note.status.label,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: cue,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Text(
|
|
note.priority.label,
|
|
maxLines: 1,
|
|
style: cue?.copyWith(color: accentFor(scheme)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|