mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 20:27:01 +01:00
An app-bar toggle enters selection mode: a tap toggles a note (tint + check on grid tiles, ListTile.selected + check in the list), Ctrl/Cmd- click toggles and Shift-click range-selects on desktop, long-press outside the mode still opens the per-note sheet. While anything is selected an icon-only bottom bar sets status or priority for the whole selection in one bulkUpdate, assigns plain notes to a repo picked from the repos on notes or typed as owner/name, or clears the selection. A refused assign (note already in a repo) flashes a block icon. Labels are semantic only; menus pass an empty tooltip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWXdsB7CQPNXVN829G1
82 lines
2.2 KiB
Dart
82 lines
2.2 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:design_system/design_system.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/ui/note_grid_tile.dart';
|
|
|
|
/// Below this width the notes list is a grid; at or above it, the list.
|
|
const double kNotesGridMaxWidth = 600;
|
|
|
|
const _columns = 2;
|
|
const double _gap = AppSpacing.sm;
|
|
const double _sidePadding = AppSpacing.md - AppSpacing.xs;
|
|
const _minTileHeight = 96.0;
|
|
|
|
double _tileHeightFor(double height) {
|
|
final rows = math.max(1, ((height + _gap) / (_minTileHeight + _gap)).floor());
|
|
return (height + _gap) / rows - _gap;
|
|
}
|
|
|
|
/// Phone notes list: two columns, scrolling only past one screenful.
|
|
class NotesGrid extends StatelessWidget {
|
|
/// Creates the grid; [onOpen] and [onActions] get the tapped note.
|
|
const NotesGrid({
|
|
required this.notes,
|
|
required this.onOpen,
|
|
required this.onActions,
|
|
required this.isSelected,
|
|
super.key,
|
|
});
|
|
|
|
/// Whether the note with this id is in the multi-selection.
|
|
final bool Function(String id) isSelected;
|
|
|
|
/// Notes in display order.
|
|
final List<Note> notes;
|
|
|
|
/// Tap on a tile.
|
|
final ValueChanged<Note> onOpen;
|
|
|
|
/// Long-press on a tile.
|
|
final ValueChanged<Note> onActions;
|
|
|
|
Widget _tile(Note note) {
|
|
return NoteGridTile(
|
|
key: ValueKey(note.id),
|
|
note: note,
|
|
selected: isSelected(note.id),
|
|
onTap: () => onOpen(note),
|
|
onActions: () => onActions(note),
|
|
);
|
|
}
|
|
|
|
SliverGridDelegate _delegate(double viewportHeight) {
|
|
return SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: _columns,
|
|
mainAxisSpacing: _gap,
|
|
crossAxisSpacing: _gap,
|
|
mainAxisExtent: _tileHeightFor(viewportHeight),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: _gap),
|
|
child: SafeArea(
|
|
top: false,
|
|
child: LayoutBuilder(
|
|
builder: (context, box) => GridView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: _sidePadding),
|
|
gridDelegate: _delegate(box.maxHeight),
|
|
itemCount: notes.length,
|
|
itemBuilder: (context, i) => _tile(notes[i]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|