Add routines module and full goal management

Routines: workout, meditation and must-do routines with timed, rep and
tick steps, sets and rest. A library screen, a builder with templates
and drag-to-reorder steps, and a full-screen player that auto-advances
timed phases, counts backgrounding and logs broken chains as partial.

Goals: a goal editor (deadline, colour, category, default class, archive),
routines linked to goals, and a goal field on tasks and routines. Tasks
added from inside a goal are now actually attached to it via parentId.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
alvocool
2026-11-02 08:45:41 +03:00
parent c88887b639
commit 1a5867a98f
49 changed files with 5384 additions and 220 deletions

View File

@@ -0,0 +1,63 @@
import 'package:flutter/cupertino.dart';
import '../../about/internal/application/TextType.dart';
import '../../utils/Colors.dart';
import '../text/Text.dart';
/// A labelled minus / value / plus control for sets, reps and seconds.
/// Numbers are stepped, not typed — nobody wants a keyboard mid-routine-edit.
Widget countStepper(
String label,
String value, {
required VoidCallback onMinus,
required VoidCallback onPlus,
bool canMinus = true,
}) {
return Container(
padding: const EdgeInsets.fromLTRB(14, 10, 8, 10),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colorBorder, width: 1),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text(label.toUpperCase(), 9, TextType.Bold,
color: colorGrey2, letterSpacing: 1.0),
const SizedBox(height: 4),
text(value, 17, TextType.Medium, color: colorPrimaryDark),
],
),
),
_stepButton(CupertinoIcons.minus, canMinus ? onMinus : null),
const SizedBox(width: 6),
_stepButton(CupertinoIcons.plus, onPlus),
],
),
);
}
Widget _stepButton(IconData icon, VoidCallback? onTap) {
final bool enabled = onTap != null;
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
width: 34,
height: 34,
alignment: Alignment.center,
decoration: BoxDecoration(
color: enabled ? colorSecondary : colorInset,
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon,
size: 14, color: enabled ? colorPrimaryDark : colorGrey),
),
);
}