@@ -15,6 +15,7 @@ import '../services/durable_stats.dart';
1515import '../services/milestones.dart' ;
1616import '../services/live_activity.dart' ;
1717import '../services/notifications.dart' ;
18+ import '../services/phase_alarm_gate.dart' ;
1819import 'milestone_screen.dart' ;
1920
2021const _accent = Color (0xFFF97316 );
@@ -44,6 +45,9 @@ class _HomeScreenState extends State<HomeScreen>
4445 int _totalPausedMs = 0 ;
4546 bool _alarmPlaying = false ;
4647 bool _sessionComplete = false ;
48+ bool _waitingForAlarmStop = false ;
49+ DateTime ? _alarmGateStart;
50+ int ? _pendingPhaseIndexAfterAlarm;
4751 int _lastPhaseIndex = - 1 ;
4852 String ? _ringtonePath;
4953 StreamSubscription <void >? _alarmCompleteSub;
@@ -89,7 +93,7 @@ class _HomeScreenState extends State<HomeScreen>
8993 if (! mounted) return ;
9094 if (_sessionComplete) {
9195 _stop ();
92- } else {
96+ } else if ( ! _waitingForAlarmStop) {
9397 setState (() => _alarmPlaying = false );
9498 }
9599 });
@@ -527,6 +531,9 @@ class _HomeScreenState extends State<HomeScreen>
527531 _pauseStart = null ;
528532 _totalPausedMs = 0 ;
529533 _sessionComplete = false ;
534+ _waitingForAlarmStop = false ;
535+ _alarmGateStart = null ;
536+ _pendingPhaseIndexAfterAlarm = null ;
530537 _lastPhaseIndex = - 1 ;
531538 });
532539 await _persistActiveSession ();
@@ -561,9 +568,17 @@ class _HomeScreenState extends State<HomeScreen>
561568 }
562569 }
563570
564- Future <void > _triggerAlarm () async {
565- setState (() => _alarmPlaying = true );
566- await playAlarm ();
571+ Future <void > _triggerAlarm ({required int pendingPhaseIndex}) async {
572+ _ticker? .cancel ();
573+ _ticker = null ;
574+ setState (() {
575+ _waitingForAlarmStop = true ;
576+ _alarmGateStart = DateTime .now ();
577+ _pendingPhaseIndexAfterAlarm = pendingPhaseIndex;
578+ _alarmPlaying = true ;
579+ });
580+ unawaited (_persistActiveSession ());
581+ await playAlarm (loop: true );
567582 }
568583
569584 Future <void > _stopAlarm ({bool nativeOrigin = false }) async {
@@ -573,9 +588,56 @@ class _HomeScreenState extends State<HomeScreen>
573588 }
574589 if (_sessionComplete) {
575590 _stop ();
576- } else {
577- setState (() => _alarmPlaying = false );
591+ return ;
592+ }
593+
594+ final schedule = _schedule;
595+ final gateStart = _alarmGateStart;
596+ final pendingPhaseIndex = _pendingPhaseIndexAfterAlarm;
597+ if (_waitingForAlarmStop &&
598+ schedule != null &&
599+ gateStart != null &&
600+ pendingPhaseIndex != null &&
601+ pendingPhaseIndex < schedule.phases.length) {
602+ final resolved = resolvePhaseAlarmGate (
603+ totalPausedMs: _totalPausedMs,
604+ gateStartedAt: gateStart,
605+ stoppedAt: DateTime .now (),
606+ pendingPhaseIndex: pendingPhaseIndex,
607+ );
608+ _totalPausedMs = resolved.totalPausedMs;
609+ _lastPhaseIndex = resolved.activePhaseIndex;
610+ final now = DateTime .now ().subtract (
611+ Duration (milliseconds: _totalPausedMs),
612+ );
613+ final phase = schedule.phases[resolved.activePhaseIndex];
614+ setState (() {
615+ _waitingForAlarmStop = false ;
616+ _alarmGateStart = null ;
617+ _pendingPhaseIndexAfterAlarm = null ;
618+ _alarmPlaying = false ;
619+ _phaseIndex = resolved.activePhaseIndex;
620+ _remaining = phase.remaining (now);
621+ _phaseProgress = phase.progress (now);
622+ });
623+ unawaited (_persistActiveSession ());
624+ unawaited (scheduleAll (schedule, offsetMs: _totalPausedMs));
625+ unawaited (
626+ startTimerService (
627+ phaseNames: schedule.phases.map ((p) => p.phase.name).toList (),
628+ phaseEndTimes: shiftedPhaseEndTimes (schedule, _totalPausedMs),
629+ ),
630+ );
631+ _ticker = Timer .periodic (const Duration (seconds: 1 ), (_) => _tick ());
632+ return ;
578633 }
634+
635+ setState (() {
636+ _waitingForAlarmStop = false ;
637+ _alarmGateStart = null ;
638+ _pendingPhaseIndexAfterAlarm = null ;
639+ _alarmPlaying = false ;
640+ });
579641 }
580642
581643 void _stop () async {
@@ -599,6 +661,9 @@ class _HomeScreenState extends State<HomeScreen>
599661 _pauseStart = null ;
600662 _totalPausedMs = 0 ;
601663 _alarmPlaying = false ;
664+ _waitingForAlarmStop = false ;
665+ _alarmGateStart = null ;
666+ _pendingPhaseIndexAfterAlarm = null ;
602667 _sessionComplete = false ;
603668 _pendingMilestone = null ;
604669 _phaseIndex = 0 ;
@@ -688,7 +753,7 @@ class _HomeScreenState extends State<HomeScreen>
688753
689754 void _tick () {
690755 final schedule = _schedule;
691- if (schedule == null ) return ;
756+ if (schedule == null || _waitingForAlarmStop ) return ;
692757
693758 final now = DateTime .now ().subtract (Duration (milliseconds: _totalPausedMs));
694759 final idx = schedule.currentPhaseIndex (now);
@@ -699,7 +764,7 @@ class _HomeScreenState extends State<HomeScreen>
699764 unawaited (_onSessionComplete ());
700765 setState (() => _sessionComplete = true );
701766 unawaited (cancelNotification (schedule.phases.length - 1 ));
702- _triggerAlarm ();
767+ _triggerAlarm (pendingPhaseIndex : schedule.phases.length );
703768 return ;
704769 }
705770
@@ -708,18 +773,24 @@ class _HomeScreenState extends State<HomeScreen>
708773 final progress = phase.progress (now);
709774
710775 if (idx != _lastPhaseIndex && _lastPhaseIndex != - 1 ) {
711- // Cancel the notification for the completed phase — app handles alarm in-app
776+ final completedPhase = schedule.phases[_lastPhaseIndex];
712777 unawaited (cancelNotification (_lastPhaseIndex));
713- _triggerAlarm ();
778+ setState (() {
779+ _phaseIndex = _lastPhaseIndex;
780+ _remaining = Duration .zero;
781+ _phaseProgress = 1.0 ;
782+ });
714783 updateLiveActivity (
715- phaseName: phase .phase.name,
716- phaseEndTime: phase .endTime,
717- remainingSeconds: remaining.inSeconds ,
718- totalSeconds: phase .phase.duration.inSeconds,
719- isBreak: phase .phase.isBreak,
784+ phaseName: completedPhase .phase.name,
785+ phaseEndTime: completedPhase .endTime,
786+ remainingSeconds: 0 ,
787+ totalSeconds: completedPhase .phase.duration.inSeconds,
788+ isBreak: completedPhase .phase.isBreak,
720789 isPaused: false ,
721- alarmPlaying: _alarmPlaying ,
790+ alarmPlaying: true ,
722791 );
792+ _triggerAlarm (pendingPhaseIndex: idx);
793+ return ;
723794 }
724795 _lastPhaseIndex = idx;
725796
0 commit comments