Rounded rectangles and round controls are now square across the app; only status dots, glows, the sun, rings and the breathing circle stay round. Cards, rows, thumbnails, featured cards, category tiles, the standing card and the nav are larger. Home gets a quick To-do list kept on the device (TodoItem in InternalMemory), seeded with sample items in mock mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
217 lines
6.5 KiB
Dart
217 lines
6.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../about/internal/application/TextType.dart';
|
|
import '../utils/Colors.dart';
|
|
import 'text/Text.dart';
|
|
|
|
/// The tick box. Square, hairline border when empty, filled with the accent
|
|
/// and a white check when set — and it animates, because the tick is the one
|
|
/// moment of satisfaction the app allows itself.
|
|
class TickBox extends StatelessWidget {
|
|
final bool checked;
|
|
|
|
final VoidCallback? onTap;
|
|
|
|
/// The accent used when checked. Defaults to the app's positive green;
|
|
/// callers pass the class colour so the row reads as its own weight.
|
|
final Color? accent;
|
|
|
|
final double size;
|
|
|
|
/// Renders muted and ignores taps — used for items that are settled.
|
|
final bool locked;
|
|
|
|
const TickBox({
|
|
super.key,
|
|
required this.checked,
|
|
this.onTap,
|
|
this.accent,
|
|
this.size = 24,
|
|
this.locked = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final Color tone = locked ? colorGrey : (accent ?? colorPositive);
|
|
|
|
return GestureDetector(
|
|
onTap: locked ? null : onTap,
|
|
// A bare box is a small target; the padding brings it up to a
|
|
// comfortable tap area without changing the drawn size.
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 2),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 160),
|
|
curve: Curves.easeOut,
|
|
width: size,
|
|
height: size,
|
|
decoration: BoxDecoration(
|
|
color: checked ? tone : Colors.transparent,
|
|
borderRadius: BorderRadius.zero,
|
|
border: Border.all(
|
|
color: checked ? tone : colorGrey.withValues(alpha: 0.55),
|
|
width: 1.6,
|
|
),
|
|
),
|
|
child: AnimatedOpacity(
|
|
duration: const Duration(milliseconds: 140),
|
|
opacity: checked ? 1 : 0,
|
|
child: Icon(
|
|
Icons.check_rounded,
|
|
size: size * 0.68,
|
|
color: colorWhite,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A task line: tick box, then the title struck through once it is done.
|
|
/// Everything else about the row — window, class, cost — is the caller's,
|
|
/// passed in as [detail] so this stays one shape used everywhere.
|
|
class TickRow extends StatelessWidget {
|
|
final bool checked;
|
|
|
|
final String title;
|
|
|
|
final VoidCallback? onTick;
|
|
|
|
final VoidCallback? onTap;
|
|
|
|
final Color? accent;
|
|
|
|
/// Secondary line beneath the title.
|
|
final Widget? detail;
|
|
|
|
/// Trailing widget — a class pill, a chevron.
|
|
final Widget? trailing;
|
|
|
|
/// Struck through and muted, but not tickable.
|
|
final bool locked;
|
|
|
|
const TickRow({
|
|
super.key,
|
|
required this.checked,
|
|
required this.title,
|
|
this.onTick,
|
|
this.onTap,
|
|
this.accent,
|
|
this.detail,
|
|
this.trailing,
|
|
this.locked = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bool struck = checked || locked;
|
|
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TickBox(
|
|
checked: checked,
|
|
onTap: onTick,
|
|
accent: accent,
|
|
locked: locked,
|
|
),
|
|
const SizedBox(width: 13),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 9),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// The strikethrough is the whole point of the tick: the
|
|
// line stays in place so the day still reads as a record
|
|
// of what was asked, not just what is left.
|
|
AnimatedDefaultTextStyle(
|
|
duration: const Duration(milliseconds: 160),
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontFamily: struck ? "GroundedRegular" : "GroundedMedium",
|
|
color: struck ? colorGrey2 : colorPrimaryDark,
|
|
decoration: struck
|
|
? TextDecoration.lineThrough
|
|
: TextDecoration.none,
|
|
decorationColor: colorGrey2,
|
|
decorationThickness: 1.5,
|
|
height: 1.35,
|
|
),
|
|
child: Text(
|
|
title,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
if (detail != null) ...[
|
|
const SizedBox(height: 6),
|
|
DefaultTextStyle.merge(
|
|
style: TextStyle(
|
|
decoration: TextDecoration.none,
|
|
color: colorGrey2,
|
|
),
|
|
child: detail!,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (trailing != null) ...[
|
|
const SizedBox(width: 10),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
child: trailing!,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// "Add new task" — the centred, low-key affordance beneath a day's list.
|
|
Widget addTaskAffordance(
|
|
VoidCallback onTap, {
|
|
String label = "Add new task",
|
|
bool enabled = true,
|
|
}) {
|
|
final Color tone = enabled ? colorPositive : colorGrey;
|
|
|
|
return GestureDetector(
|
|
onTap: enabled ? onTap : null,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 18),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 46,
|
|
height: 46,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: tone.withValues(alpha: 0.12),
|
|
borderRadius: BorderRadius.zero,
|
|
border: Border.all(color: tone.withValues(alpha: 0.25), width: 1),
|
|
),
|
|
child: Icon(Icons.add_rounded, size: 22, color: tone),
|
|
),
|
|
const SizedBox(height: 9),
|
|
text(label, 12, TextType.Regular, color: colorGrey2),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|