import 'package:flutter/material.dart'; import '../../design/number_stepper.dart'; import '../../design/zen_style.dart'; import '../../localization/app_localizations.dart'; import '../models/plan.dart'; import '../plan_labels.dart'; /// Edits a Step, a Superset or a Superset member in a bottom sheet. Every /// change is handed to [onChanged] right away; there is no separate save. Future showStepEditor( BuildContext context, { required String title, required T value, required ValueChanged onChanged, }) => showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (context) => _StepEditorSheet(title: title, initial: value, onChanged: onChanged), ); class _StepEditorSheet extends StatefulWidget { const _StepEditorSheet({required this.title, required this.initial, required this.onChanged}); final String title; final T initial; final ValueChanged onChanged; @override State<_StepEditorSheet> createState() => _StepEditorSheetState(); } class _StepEditorSheetState extends State<_StepEditorSheet> { late T _value = widget.initial; void _set(Object value) { setState(() => _value = value as T); widget.onChanged(_value); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final text = Theme.of(context).textTheme; final value = _value; return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title, style: text.titleLarge), const SizedBox(height: 12), ...switch (value) { WorkoutStep() => [ _setsStepper(l10n, value.sets, (n) => _set(value.copyWith(sets: n))), _TargetEditor( target: value.target, onChanged: (t) => _set(value.copyWith(target: t)), ), _WeightEditor( weightKg: value.weightKg, onChanged: (kg) => _set(value.copyWith(weightKg: () => kg)), ), ], SupersetMember() => [ _TargetEditor( target: value.target, onChanged: (t) => _set(value.copyWith(target: t)), ), _WeightEditor( weightKg: value.weightKg, onChanged: (kg) => _set(value.copyWith(weightKg: () => kg)), ), ], Superset() => [ _setsStepper(l10n, value.sets, (n) => _set(value.copyWith(sets: n))), SwitchListTile( contentPadding: EdgeInsets.zero, title: Text(l10n.restBetweenLabel, style: text.titleSmall), subtitle: Text(l10n.restBetweenHint), value: value.restBetween, onChanged: (on) => _set(value.copyWith(restBetween: on)), ), ], StretchingStep() => [ NumberStepper( label: l10n.stepHold, value: l10n.secondsShort(value.holdSeconds), onDecrement: value.holdSeconds > 5 ? () => _set(value.copyWith(holdSeconds: value.holdSeconds - 5)) : null, onIncrement: value.holdSeconds < 300 ? () => _set(value.copyWith(holdSeconds: value.holdSeconds + 5)) : null, ), SwitchListTile( contentPadding: EdgeInsets.zero, title: Text(l10n.stepBothSides, style: text.titleSmall), subtitle: Text(l10n.stepBothSidesHint), value: value.bothSides, onChanged: (on) => _set(value.copyWith(bothSides: on)), ), NumberStepper( label: l10n.stepRounds, value: '${value.rounds}', onDecrement: value.rounds > 1 ? () => _set(value.copyWith(rounds: value.rounds - 1)) : null, onIncrement: value.rounds < 10 ? () => _set(value.copyWith(rounds: value.rounds + 1)) : null, ), ], _ => const [], }, const SizedBox(height: 16), SizedBox( width: double.infinity, height: 48, child: FilledButton(onPressed: () => Navigator.of(context).pop(), child: Text(l10n.done)), ), ], ), ), ); } Widget _setsStepper(AppLocalizations l10n, int sets, ValueChanged onChanged) => NumberStepper( label: l10n.stepSets, value: '$sets', onDecrement: sets > 1 ? () => onChanged(sets - 1) : null, onIncrement: sets < 20 ? () => onChanged(sets + 1) : null, ); } /// Reps or time per set, switching between the two with sensible starting values. class _TargetEditor extends StatelessWidget { const _TargetEditor({required this.target, required this.onChanged}); final WorkoutTarget target; final ValueChanged onChanged; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), SegmentedButton( showSelectedIcon: false, segments: [ ButtonSegment(value: false, label: Text(l10n.stepReps)), ButtonSegment(value: true, label: Text(l10n.stepTime)), ], selected: {target.isTimed}, onSelectionChanged: (timed) => onChanged(timed.single ? const WorkoutTarget.seconds(30) : const WorkoutTarget.reps(10)), ), if (target.isTimed) NumberStepper( label: l10n.stepTime, value: l10n.secondsShort(target.seconds!), onDecrement: target.seconds! > 5 ? () => onChanged(WorkoutTarget.seconds(target.seconds! - 5)) : null, onIncrement: target.seconds! < 600 ? () => onChanged(WorkoutTarget.seconds(target.seconds! + 5)) : null, ) else NumberStepper( label: l10n.stepReps, value: '${target.reps}', onDecrement: target.reps! > 1 ? () => onChanged(WorkoutTarget.reps(target.reps! - 1)) : null, onIncrement: target.reps! < 100 ? () => onChanged(WorkoutTarget.reps(target.reps! + 1)) : null, ), ], ); } } /// An optional target weight in 2.5 kg steps. class _WeightEditor extends StatelessWidget { const _WeightEditor({required this.weightKg, required this.onChanged}); static const step = 2.5; final double? weightKg; final ValueChanged onChanged; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final s = ZenStyle.of(context); final kg = weightKg; if (kg == null) { return Padding( padding: const EdgeInsets.only(top: 8), child: OutlinedButton.icon( onPressed: () => onChanged(20), icon: const Icon(Icons.add_rounded), label: Text(l10n.addWeight), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ NumberStepper( label: l10n.stepWeight, value: l10n.weightKg(kgNumber(kg)), onDecrement: kg >= step ? () => onChanged(kg - step) : null, onIncrement: kg < 500 ? () => onChanged(kg + step) : null, ), TextButton( onPressed: () => onChanged(null), style: TextButton.styleFrom(foregroundColor: s.inkMuted, padding: EdgeInsets.zero), child: Text(l10n.removeWeight), ), ], ); } }