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.circular(7), 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.circular(14), 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), ], ), ), ); }