Skip to content

Commit 6471ebf

Browse files
committed
Add Seeed SenseCAP Indicator device model and enhance ping control UI feedback
1 parent 147fb8a commit 6471ebf

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed

assets/device-models.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,14 @@
211211
"txPower": 20,
212212
"notes": "OLED display variant"
213213
},
214+
{
215+
"manufacturer": "Seeed SenseCAP Indicator",
216+
"shortName": "Seeed SenseCAP Indicator",
217+
"power": 0.3,
218+
"platform": "ESP32S3",
219+
"txPower": 22,
220+
"notes": "Seeed SenseCAP ESP32S3"
221+
},
214222
{
215223
"manufacturer": "Seeed Wio E5 Dev Board",
216224
"shortName": "Wio E5 Dev Board",

lib/providers/app_state_provider.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class AppStateProvider extends ChangeNotifier {
105105
PingStats _pingStats = const PingStats();
106106
bool _autoPingEnabled = false;
107107
AutoMode _autoMode = AutoMode.txRx;
108+
bool _isPingSending = false; // True immediately when ping button clicked
108109
int _queueSize = 0;
109110
int? _currentNoiseFloor;
110111
int? _currentBatteryPercent;
@@ -184,6 +185,7 @@ class AppStateProvider extends ChangeNotifier {
184185
PingStats get pingStats => _pingStats;
185186
bool get autoPingEnabled => _autoPingEnabled;
186187
AutoMode get autoMode => _autoMode;
188+
bool get isPingSending => _isPingSending;
187189
int get queueSize => _queueSize;
188190
int? get currentNoiseFloor => _currentNoiseFloor;
189191
int? get currentBatteryPercent => _currentBatteryPercent;
@@ -1152,8 +1154,19 @@ class AppStateProvider extends ChangeNotifier {
11521154
/// Send a manual TX ping
11531155
Future<bool> sendPing() async {
11541156
if (_pingService == null) return false;
1157+
1158+
// Set sending state immediately for instant UI feedback
1159+
_isPingSending = true;
1160+
notifyListeners();
1161+
11551162
debugLog('[PING] Sending manual TX ping');
1156-
return await _pingService!.sendTxPing(manual: true);
1163+
try {
1164+
return await _pingService!.sendTxPing(manual: true);
1165+
} finally {
1166+
// Clear sending state when done (RX window timer will show listening state)
1167+
_isPingSending = false;
1168+
notifyListeners();
1169+
}
11571170
}
11581171

11591172
/// Toggle auto-ping mode (TX/RX or RX-only)

lib/widgets/ping_controls.dart

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class PingControls extends StatelessWidget {
2222
final isRxAutoRunning = appState.autoPingEnabled && appState.autoMode == AutoMode.rxOnly;
2323
final cooldownActive = appState.cooldownTimer.isRunning; // Shared cooldown for both TX Ping and TX/RX Auto
2424
final cooldownRemaining = appState.cooldownTimer.remainingSec;
25+
final rxWindowActive = appState.rxWindowTimer.isRunning; // RX listening window after ping
26+
final rxWindowRemaining = appState.rxWindowTimer.remainingSec;
27+
final isPingSending = appState.isPingSending; // True immediately when button clicked
2528

2629
// TX is blocked when offline mode is active and connected
2730
final txBlockedByOffline = appState.offlineMode && appState.isConnected;
@@ -78,17 +81,23 @@ class PingControls extends StatelessWidget {
7881
Row(
7982
children: [
8083
// Send Ping button - disabled when offline mode is active, but works during Passive Mode
84+
// Shows active state with pulse animation during sending and RX listening window
8185
Expanded(
8286
child: _ActionButton(
8387
icon: Icons.cell_tower,
8488
label: txBlockedByOffline
8589
? 'TX Disabled'
86-
: (cooldownActive && !isTxRxAutoRunning ? '$cooldownRemaining s' : 'Send Ping'),
90+
: isPingSending
91+
? 'Sending...'
92+
: rxWindowActive
93+
? 'Listening ${rxWindowRemaining}s'
94+
: (cooldownActive && !isTxRxAutoRunning ? '$cooldownRemaining s' : 'Send Ping'),
8795
color: const Color(0xFF0EA5E9), // sky-500
88-
enabled: canPing && !isTxRxAutoRunning && !cooldownActive && !txBlockedByOffline,
96+
enabled: canPing && !isTxRxAutoRunning && !cooldownActive && !txBlockedByOffline && !rxWindowActive && !isPingSending,
97+
isActive: isPingSending || rxWindowActive,
8998
onPressed: () => _sendPing(context, appState),
9099
showCooldown: cooldownActive && !isTxRxAutoRunning && !txBlockedByOffline,
91-
subtitle: txBlockedByOffline ? 'Offline Mode' : moveSubtitle,
100+
subtitle: txBlockedByOffline ? 'Offline Mode' : ((isPingSending || rxWindowActive) ? null : moveSubtitle),
92101
subtitleColor: txBlockedByOffline ? Colors.orange : Colors.orange.shade600,
93102
),
94103
),
@@ -256,7 +265,10 @@ class _ActionButtonState extends State<_ActionButton>
256265

257266
@override
258267
Widget build(BuildContext context) {
259-
final effectiveColor = widget.enabled ? widget.color : Colors.grey;
268+
// Use color when enabled, active (RX listening), or during cooldown
269+
// This prevents the button from going grey during cooldown
270+
final showColor = widget.enabled || widget.isActive || widget.showCooldown;
271+
final effectiveColor = showColor ? widget.color : Colors.grey;
260272
final borderOpacity = widget.isActive ? 0.6 : 0.3;
261273

262274
return AnimatedBuilder(
@@ -294,7 +306,7 @@ class _ActionButtonState extends State<_ActionButton>
294306
Icon(
295307
widget.icon,
296308
size: 26,
297-
color: widget.enabled
309+
color: showColor
298310
? effectiveColor
299311
: Colors.grey.shade400,
300312
),
@@ -326,7 +338,7 @@ class _ActionButtonState extends State<_ActionButton>
326338
style: TextStyle(
327339
fontSize: 11,
328340
fontWeight: widget.isActive ? FontWeight.w600 : FontWeight.w500,
329-
color: widget.enabled
341+
color: showColor
330342
? (widget.isActive ? effectiveColor : Colors.grey.shade700)
331343
: Colors.grey.shade400,
332344
),

0 commit comments

Comments
 (0)