import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:stacked/stacked.dart'; import '../../about/internal/application/ProgressTargets.dart'; import '../../about/internal/application/TextType.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 'ConnectTargetSetup.dart'; import 'TargetSetup.dart'; import 'ViewTargetSetup.dart'; class TargetSetupState extends State implements ConnectTargetSetup { ViewTargetSetup? _model; ProgressTargets _targets = ProgressTargets(); int _scheduled = 0; static const int _routineFloor = 1; static const int _routineCeiling = 28; static const List _minuteOptions = [60, 90, 150, 210, 300]; static const List _leadOptions = [0, 5, 10, 15, 30]; @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => ViewTargetSetup(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() { _model?.initialise(); } // ── Handlers ────────────────────────────────────────────────────────────── void _onBack() { Navigator.pop(context, false); } void _onRoutinesDown() { setState(() { if (_targets.routinesPerWeek > _routineFloor) { _targets.routinesPerWeek = _targets.routinesPerWeek - 1; } }); } void _onRoutinesUp() { setState(() { if (_targets.routinesPerWeek < _routineCeiling) { _targets.routinesPerWeek = _targets.routinesPerWeek + 1; } }); } void _onMatchSchedule() { setState(() { _targets.routinesPerWeek = _scheduled.clamp(_routineFloor, _routineCeiling); }); } void _onMinutesSelected(int minutes) { setState(() { _targets.minutesPerWeek = minutes; }); } void _onToggleReminders() { setState(() { _targets.reminders = !_targets.reminders; }); } void _onLeadSelected(int lead) { setState(() { _targets.reminderLead = lead; }); } void _onSave() { _model?.save(_targets); } // ── Views ───────────────────────────────────────────────────────────────── Widget _mobileView(BoxConstraints constraints) { return Sheet( eyebrow: "Progress", title: "Targets", onBack: _onBack, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ displayTitle("What counts as\na good week?"), const SizedBox(height: 10), text( "Progress measures against these. Debt doesn't: missing a target costs nothing, only missing a commitment does.", 13, TextType.Regular, color: colorInkSoft, height: 1.5, ), const SizedBox(height: 28), sectionBreak("Routines held", caption: "per week"), _routineStepper(), const SizedBox(height: 28), sectionBreak("Active time", caption: "minutes per week, in routines"), segmentedSelector( options: _minuteOptions, selected: _targets.minutesPerWeek, label: _minuteLabel, onSelected: _onMinutesSelected, activeColor: colorNight, ), const SizedBox(height: 28), sectionBreak("Reminders", caption: "before each scheduled routine"), _reminderToggle(), if (_targets.reminders) ...[ const SizedBox(height: 14), segmentedSelector( options: _leadOptions, selected: _targets.reminderLead, label: _leadLabel, onSelected: _onLeadSelected, activeColor: colorNight, ), ], const SizedBox(height: 10), text( kIsWeb ? "Reminders use the phone's notifications, so they only fire in the installed app." : "Uses this phone's notifications. Non-negotiables still get their full-screen alarm when the window closes.", 12, TextType.Regular, color: colorGrey2, height: 1.5, ), const SizedBox(height: 32), roundedCornerButton( "Save targets", _onSave, icon: CupertinoIcons.checkmark, radius: 0, ), const SizedBox(height: 12), ], ), ); } String _minuteLabel(int minutes) => formatMinutes(minutes); String _leadLabel(int lead) => lead == 0 ? "At start" : "${lead}m before"; Widget _routineStepper() { return card( padding: const EdgeInsets.fromLTRB(18, 16, 18, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ _stepButton(CupertinoIcons.minus, _onRoutinesDown, _targets.routinesPerWeek > _routineFloor), Expanded( child: Column( children: [ text("${_targets.routinesPerWeek}", 44, TextType.Light, color: colorInk, height: 1), const SizedBox(height: 4), text( "about ${(_targets.routinesPerWeek / 7).toStringAsFixed(1)} a day", 12, TextType.Regular, color: colorInkSoft, ), ], ), ), _stepButton(CupertinoIcons.add, _onRoutinesUp, _targets.routinesPerWeek < _routineCeiling), ], ), if (_scheduled > 0) ...[ const SizedBox(height: 16), Container(height: 1, color: colorDivider), const SizedBox(height: 12), Row( children: [ Expanded( child: text( "Your schedule asks for $_scheduled a week.", 12, TextType.Regular, color: colorInkSoft, ), ), if (_scheduled != _targets.routinesPerWeek) GestureDetector( onTap: _onMatchSchedule, child: text("Match it", 12, TextType.Medium, color: colorInk), ), ], ), ], ], ), ); } Widget _stepButton(IconData icon, VoidCallback onTap, bool enabled) { return GestureDetector( onTap: enabled ? onTap : null, child: Container( width: 48, height: 48, alignment: Alignment.center, color: enabled ? colorNight : colorMuted, child: Icon(icon, size: 20, color: enabled ? colorLime : colorGrey), ), ); } Widget _reminderToggle() { final bool on = _targets.reminders; return card( onTap: _onToggleReminders, background: on ? colorStandingGoodBg : colorCard, borderColor: on ? colorPositive.withValues(alpha: 0.2) : colorBorder, child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ text(on ? "On" : "Off", 16, TextType.Medium, color: colorPrimaryDark), const SizedBox(height: 6), text( on ? "A heads-up before every scheduled routine. Tap to turn off." : "No heads-up before routines. Tap to turn on.", 12, TextType.Regular, color: colorGrey2, ), ], ), ), Icon( on ? CupertinoIcons.bell_fill : CupertinoIcons.bell_slash, size: 24, color: on ? colorPositive : colorGrey2, ), ], ), ); } // ── ConnectTargetSetup ──────────────────────────────────────────────────── @override void onTargetsLoaded(ProgressTargets targets, int scheduledPerWeek) { setState(() { _targets = targets; _scheduled = scheduledPerWeek; }); } @override void onTargetsSaved() { Navigator.pop(context, true); } }