11import 'dart:async' ;
22
3- import 'package:flutter/foundation.dart' show protected, VoidCallback;
3+ import 'package:flutter/foundation.dart' show VoidCallback;
44
55import '../utils/debug_logger_io.dart' ;
6- import 'status_message_service.dart' ;
7-
8- /// Countdown timer result with message and optional color
9- class CountdownResult {
10- final String message;
11- final StatusColor color;
12-
13- CountdownResult (this .message, [this .color = StatusColor .idle]);
14- }
156
167/// Countdown timer for cooldown, auto-ping, and RX window
178/// Reference: createCountdownTimer() in wardrive.js
189///
1910/// Features:
2011/// - 500ms update interval for responsive countdown display
21- /// - First update respects minimum visibility
22- /// - Subsequent updates are immediate for smooth countdown
2312/// - Auto-stops when countdown reaches zero
2413class CountdownTimerService {
2514 Timer ? _timer;
2615 DateTime ? _endTime;
27- bool _isFirstUpdate = true ;
28- @protected
29- final StatusMessageService statusService;
30- final CountdownResult Function (int remainingSec)? _getStatusMessage;
3116 final VoidCallback ? onUpdate; // Callback for UI refresh on each timer tick
3217
33- CountdownTimerService (
34- this .statusService, {
35- CountdownResult Function (int remainingSec)? getStatusMessage,
36- this .onUpdate,
37- }) : _getStatusMessage = getStatusMessage;
18+ CountdownTimerService ({this .onUpdate});
3819
3920 /// Check if timer is running
4021 bool get isRunning => _timer != null ;
@@ -55,7 +36,6 @@ class CountdownTimerService {
5536 void start (int durationMs) {
5637 stop ();
5738 _endTime = DateTime .now ().add (Duration (milliseconds: durationMs));
58- _isFirstUpdate = true ;
5939
6040 // Start 500ms update timer for responsive countdown
6141 _timer = Timer .periodic (const Duration (milliseconds: 500 ), (_) => _update ());
@@ -76,21 +56,6 @@ class CountdownTimerService {
7656 return ;
7757 }
7858
79- final remainingSec = (remainingMs / 1000 ).ceil ();
80-
81- // Get status message from callback (if provided)
82- if (_getStatusMessage != null ) {
83- final result = _getStatusMessage (remainingSec);
84-
85- // First update respects minimum visibility of previous message
86- // Subsequent updates are immediate for smooth 1-second countdown intervals
87- final immediate = ! _isFirstUpdate;
88- statusService.setDynamicStatus (result.message, result.color, immediate: immediate);
89-
90- // Mark first update as complete
91- _isFirstUpdate = false ;
92- }
93-
9459 // Trigger UI refresh callback after each update
9560 onUpdate? .call ();
9661 }
@@ -100,7 +65,6 @@ class CountdownTimerService {
10065 _timer? .cancel ();
10166 _timer = null ;
10267 _endTime = null ;
103- _isFirstUpdate = true ;
10468 }
10569
10670 /// Dispose resources
@@ -112,25 +76,14 @@ class CountdownTimerService {
11276/// Specialized countdown timer for ping cooldown
11377/// Reference: wardrive.js state.cooldownEndTime and cooldownUpdateTimer
11478class CooldownTimer extends CountdownTimerService {
115- CooldownTimer (StatusMessageService statusService, {VoidCallback ? onUpdate})
116- : super (
117- statusService,
118- onUpdate: onUpdate,
119- getStatusMessage: (remainingSec) => CountdownResult (
120- 'Cooldown ($remainingSec s)' ,
121- StatusColor .idle,
122- ),
123- );
79+ CooldownTimer ({VoidCallback ? onUpdate}) : super (onUpdate: onUpdate);
12480
12581 @override
12682 void stop () {
12783 final wasRunning = isRunning;
12884 super .stop ();
129- // Clear the "Cooldown (Xs)" status message when timer completes naturally
130- // Use clearDynamicStatus to remove our message without forcing a new one
13185 if (wasRunning) {
132- debugLog ('[TIMER] Cooldown timer stopped, clearing countdown status' );
133- statusService.clearDynamicStatus ();
86+ debugLog ('[TIMER] Cooldown timer stopped' );
13487 }
13588 }
13689}
@@ -140,74 +93,13 @@ class CooldownTimer extends CountdownTimerService {
14093class AutoPingTimer extends CountdownTimerService {
14194 String ? _skipReason;
14295
143- AutoPingTimer (StatusMessageService statusService, {VoidCallback ? onUpdate})
144- : super (
145- statusService,
146- getStatusMessage: null , // Custom logic in override
147- onUpdate: onUpdate,
148- );
96+ AutoPingTimer ({VoidCallback ? onUpdate}) : super (onUpdate: onUpdate);
14997
15098 /// Set skip reason (e.g., "too close", "gps too old")
15199 set skipReason (String ? reason) {
152100 _skipReason = reason;
153101 }
154102
155- /// Override update to handle skip reason
156- void _updateWithSkipReason () {
157- if (_endTime == null ) return ;
158-
159- final remainingMs = this .remainingMs;
160-
161- // Stop when countdown reaches zero
162- if (remainingMs == 0 ) {
163- stop ();
164- return ;
165- }
166-
167- final remainingSec = (remainingMs / 1000 ).ceil ();
168-
169- // Build status message
170- CountdownResult result;
171- if (remainingSec == 0 ) {
172- result = CountdownResult ('Sending auto ping' , StatusColor .info);
173- } else if (_skipReason == 'too close' ) {
174- result = CountdownResult (
175- 'Ping skipped, too close to last ping, waiting for next ping (${remainingSec }s)' ,
176- StatusColor .warning,
177- );
178- } else if (_skipReason == 'gps too old' ) {
179- result = CountdownResult (
180- 'Ping skipped, GPS too old, waiting for fresh GPS (${remainingSec }s)' ,
181- StatusColor .warning,
182- );
183- } else {
184- result = CountdownResult ('Next ping in ${remainingSec }s' );
185- }
186-
187- // First update respects minimum visibility, subsequent updates are immediate
188- final immediate = ! _isFirstUpdate;
189- statusService.setDynamicStatus (result.message, result.color, immediate: immediate);
190-
191- // Mark first update as complete
192- _isFirstUpdate = false ;
193-
194- // Trigger UI refresh callback after each update
195- onUpdate? .call ();
196- }
197-
198- @override
199- void start (int durationMs) {
200- stop ();
201- _endTime = DateTime .now ().add (Duration (milliseconds: durationMs));
202- _isFirstUpdate = true ;
203-
204- // Start 500ms update timer with custom update logic
205- _timer = Timer .periodic (const Duration (milliseconds: 500 ), (_) => _updateWithSkipReason ());
206-
207- // Trigger immediate update
208- _updateWithSkipReason ();
209- }
210-
211103 /// Start countdown with optional skip reason
212104 /// Reference: startAutoCountdown() in wardrive.js with state.skipReason
213105 void startWithSkipReason (int durationMs, String ? reason) {
@@ -219,26 +111,10 @@ class AutoPingTimer extends CountdownTimerService {
219111/// Specialized countdown timer for RX listening window
220112/// Reference: wardrive.js state.rxListeningEndTime
221113class RxWindowTimer extends CountdownTimerService {
222- RxWindowTimer (StatusMessageService statusService, {VoidCallback ? onUpdate})
223- : super (
224- statusService,
225- getStatusMessage: (remainingSec) => CountdownResult (
226- 'Listening for responses (${remainingSec }s)' ,
227- StatusColor .info,
228- ),
229- onUpdate: onUpdate,
230- );
114+ RxWindowTimer ({VoidCallback ? onUpdate}) : super (onUpdate: onUpdate);
231115}
232116
233117/// Specialized countdown timer for discovery listening window (Passive Mode)
234118class DiscoveryWindowTimer extends CountdownTimerService {
235- DiscoveryWindowTimer (StatusMessageService statusService, {VoidCallback ? onUpdate})
236- : super (
237- statusService,
238- getStatusMessage: (remainingSec) => CountdownResult (
239- 'Listening for nodes (${remainingSec }s)' ,
240- StatusColor .info,
241- ),
242- onUpdate: onUpdate,
243- );
119+ DiscoveryWindowTimer ({VoidCallback ? onUpdate}) : super (onUpdate: onUpdate);
244120}
0 commit comments