import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import '../../about/external/data/Commitment.dart'; import '../../about/external/data/Goal.dart'; import '../../about/internal/application/CommitmentClass.dart'; import '../../about/internal/application/CommitmentStatus.dart'; import '../../about/internal/application/ProofType.dart'; import '../../about/internal/application/TextType.dart'; import '../../configs/Navigator.dart'; import '../../designs/Component.dart'; import '../../designs/Responsive.dart'; import '../../designs/Shell.dart'; import '../../designs/buttons/Buttons.dart'; import '../../designs/text/Text.dart'; import '../../utils/Colors.dart'; import '../../utils/CommonUtils.dart'; import '../../utils/DebtEngine.dart'; import '../commitment/NewCommitment.dart'; import '../live/LiveTask.dart'; import 'ConnectGoalDetail.dart'; import 'GoalDetail.dart'; import 'ViewGoalDetail.dart'; class GoalDetailState extends State implements ConnectGoalDetail { ViewGoalDetail? _model; Goal _goal = Goal(); List _tasks = []; bool _changed = false; @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => ViewGoalDetail(context, this), onViewModelReady: (viewModel) { _model = viewModel; _initiate(); }, builder: (context, viewModel, child) => LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Responsive( mobile: _mobileView(constraints), tablet: _mobileView(constraints), desktop: _mobileView(constraints), ); }, ), ); } void _initiate() { setState(() { _goal = widget.goal; }); _model?.loadTasks(widget.goal); } // ── Handlers ────────────────────────────────────────────────────────────── void _onBack() { Navigator.pop(context, _changed); } void _onStartTask(Commitment task) { _model?.startTask(task); } void _onAddTask() async { final result = await GroundedNavigation() .navigateToPageWithData(const NewCommitment(), context); if (result == true) { _changed = true; _model?.loadTasks(_goal); } } // ── Views ───────────────────────────────────────────────────────────────── Widget _mobileView(BoxConstraints constraints) { final List open = _tasks .where((task) => task.status != CommitmentStatus.Completed && task.status != CommitmentStatus.LateCompleted && task.status != CommitmentStatus.Abandoned) .toList(); final List done = _tasks .where((task) => task.status == CommitmentStatus.Completed || task.status == CommitmentStatus.LateCompleted) .toList(); return Sheet( eyebrow: "Goal", title: _goal.title, onBack: _onBack, action: chromeAction(CupertinoIcons.add, _onAddTask), banner: _progressBanner(), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ if (_goal.description.isNotEmpty) ...[ text(_goal.description, 15, TextType.Regular, color: colorGrey2, height: 1.6), const SizedBox(height: 28), ], sectionBreak("To do", caption: "${open.length} open"), if (open.isEmpty) emptyState( CupertinoIcons.square_list, "Nothing scheduled", "Add the actual sessions — Monday shoulders, Wednesday legs — and they start counting.", ) else ...open.map(_taskRow), if (done.isNotEmpty) ...[ const SizedBox(height: 28), sectionBreak("Done", caption: "${done.length}"), ...done.map(_doneRow), ], const SizedBox(height: 24), roundedCornerButton("Add a task", _onAddTask, icon: CupertinoIcons.add), ], ), ); } Widget _progressBanner() { return Container( padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), decoration: BoxDecoration( color: colorWhite.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ text("PROGRESS", 9, TextType.Bold, color: colorWhite.withValues(alpha: 0.45), letterSpacing: 1.2), text("${(_goal.progress * 100).round()}%", 13, TextType.Bold, color: colorWhite), ], ), const SizedBox(height: 12), meter( _goal.progress, fill: colorWhite, track: colorWhite.withValues(alpha: 0.14), height: 5, ), ], ), ); } /// A task row leads with the action: the point of opening a goal is to start /// something, not to admire the list. Widget _taskRow(Commitment task) { final Color accent = classColor(task.commitmentClass); final bool late = task.windowClosed; return Container( margin: const EdgeInsets.only(bottom: 10), child: card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 3, height: 38, margin: const EdgeInsets.only(right: 14, top: 2), decoration: BoxDecoration( color: accent, borderRadius: BorderRadius.circular(4), ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ text(task.title, 16, TextType.Medium, color: colorPrimaryDark, maxLines: 2, overflow: TextOverflow.ellipsis), const SizedBox(height: 7), Row( children: [ text(formatWindow(task), 11, TextType.Regular, color: colorGrey2), const SizedBox(width: 9), Container( width: 3, height: 3, decoration: BoxDecoration( color: colorGrey, shape: BoxShape.circle), ), const SizedBox(width: 9), text(formatMinutes(task.estMinutes), 11, TextType.Regular, color: colorGrey2), ], ), ], ), ), const SizedBox(width: 8), pill(proofLabel(task.proofType), colorGrey2, colorMuted, textSize: 9), ], ), if (late) ...[ const SizedBox(height: 12), Row( children: [ pill(overdueLabel(task), colorStandingGrounded, colorStandingGroundedBg, textSize: 9), const SizedBox(width: 6), pill("−${formatDebt(DebtEngine.commitmentDebt(task))}", colorGrey2, colorMuted, textSize: 9), ], ), ], const SizedBox(height: 14), roundedCornerButton( "Start", () => _onStartTask(task), icon: CupertinoIcons.play_fill, verticalPadding: 13, ), ], ), ), ); } Widget _doneRow(Commitment task) { final bool late = task.status == CommitmentStatus.LateCompleted; return Container( margin: const EdgeInsets.only(bottom: 8), child: card( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), child: Row( children: [ Icon( late ? CupertinoIcons.checkmark_circle : CupertinoIcons.checkmark_circle_fill, size: 17, color: late ? colorStandingWarned : colorPositive, ), const SizedBox(width: 12), Expanded( child: text(task.title, 13, TextType.Regular, color: colorGrey2, maxLines: 1, overflow: TextOverflow.ellipsis), ), if (late) pill("Late", colorStandingWarned, colorStandingWarnedBg, textSize: 9), ], ), ), ); } // ── ConnectGoalDetail ───────────────────────────────────────────────────── @override void onGoalLoaded(Goal goal, List tasks) { setState(() { _goal = goal; _tasks = tasks; }); } @override void onTaskReady(Commitment task) async { // The runner takes over the whole screen — a task you are running is the // thing you are doing, not a row in a list. final result = await GroundedNavigation().navigateToPageWithData( LiveTask(commitment: task, goalTitle: _goal.title), context, ); if (result == true) { _changed = true; _model?.loadTasks(_goal); } } }