Skip to content

Commit 02d07ac

Browse files
committed
Enhance home screen with help button, add help modal for controls, and improve status bar with info popups
1 parent 5fa0c36 commit 02d07ac

File tree

5 files changed

+340
-83
lines changed

5 files changed

+340
-83
lines changed

lib/screens/home_screen.dart

Lines changed: 163 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,34 +133,190 @@ class _HomeScreenState extends State<HomeScreen> {
133133
child: Column(
134134
mainAxisSize: MainAxisSize.min,
135135
children: [
136-
// Header with close button
136+
// Header with help and close buttons
137137
ListTile(
138138
title: const Text('Controls', style: TextStyle(fontWeight: FontWeight.bold)),
139-
trailing: IconButton(
140-
icon: const Icon(Icons.close),
141-
onPressed: () => setState(() => _showControlPanel = false),
139+
trailing: Row(
140+
mainAxisSize: MainAxisSize.min,
141+
children: [
142+
IconButton(
143+
icon: const Icon(Icons.help_outline),
144+
onPressed: () => _showControlsHelp(context),
145+
tooltip: 'Help',
146+
),
147+
IconButton(
148+
icon: const Icon(Icons.close),
149+
onPressed: () => setState(() => _showControlPanel = false),
150+
),
151+
],
142152
),
143153
),
144154
const Divider(height: 1),
145-
155+
146156
// Compact connection info
147157
const Padding(
148158
padding: EdgeInsets.all(12),
149159
child: ConnectionPanel(compact: true),
150160
),
151-
161+
152162
// Ping controls
153163
const Padding(
154164
padding: EdgeInsets.all(12),
155165
child: PingControls(),
156166
),
157-
167+
158168
const SizedBox(height: 8),
159169
],
160170
),
161171
);
162172
}
163173

174+
/// Show help bottom sheet explaining each control
175+
void _showControlsHelp(BuildContext context) {
176+
showModalBottomSheet(
177+
context: context,
178+
isScrollControlled: true,
179+
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
180+
shape: const RoundedRectangleBorder(
181+
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
182+
),
183+
builder: (context) => DraggableScrollableSheet(
184+
initialChildSize: 0.6,
185+
minChildSize: 0.3,
186+
maxChildSize: 0.9,
187+
expand: false,
188+
builder: (context, scrollController) => SingleChildScrollView(
189+
controller: scrollController,
190+
padding: const EdgeInsets.all(20),
191+
child: Column(
192+
crossAxisAlignment: CrossAxisAlignment.start,
193+
children: [
194+
// Header
195+
Row(
196+
children: [
197+
Container(
198+
padding: const EdgeInsets.all(8),
199+
decoration: BoxDecoration(
200+
color: Colors.blue.withValues(alpha: 0.15),
201+
borderRadius: BorderRadius.circular(8),
202+
),
203+
child: const Icon(Icons.help_outline, color: Colors.blue),
204+
),
205+
const SizedBox(width: 12),
206+
const Expanded(
207+
child: Text(
208+
'Controls Help',
209+
style: TextStyle(
210+
fontSize: 18,
211+
fontWeight: FontWeight.bold,
212+
),
213+
),
214+
),
215+
IconButton(
216+
icon: const Icon(Icons.close),
217+
onPressed: () => Navigator.pop(context),
218+
),
219+
],
220+
),
221+
const Divider(height: 24),
222+
223+
// External Antenna
224+
_buildHelpItem(
225+
icon: Icons.settings_input_antenna,
226+
color: Colors.orange,
227+
title: 'External Antenna',
228+
description: 'Enable if using an external antenna (ex: mag mount on roof of car). We store this along with pings as external antennas can make a big difference in reception.',
229+
),
230+
231+
// Send Ping button
232+
_buildHelpItem(
233+
icon: Icons.cell_tower,
234+
color: const Color(0xFF0EA5E9),
235+
title: 'Send Ping',
236+
description: 'Send a single ping to #wardriving and track which repeaters heard it.',
237+
),
238+
239+
// Active Mode button
240+
_buildHelpItem(
241+
icon: Icons.sensors,
242+
color: const Color(0xFF6366F1),
243+
title: 'Active Mode',
244+
description: 'Auto-pings #wardriving at your set interval, tracks repeaters from pings and received mesh traffic.',
245+
),
246+
247+
// Passive Mode button
248+
_buildHelpItem(
249+
icon: Icons.hearing,
250+
color: const Color(0xFF6366F1),
251+
title: 'Passive Mode',
252+
description: 'Sends zero-hop discovery pings every 30s, tracks nearby repeaters and received mesh traffic.',
253+
),
254+
255+
// Offline mode toggle
256+
_buildHelpItem(
257+
icon: Icons.cloud_off,
258+
color: Colors.orange,
259+
title: 'Offline Mode',
260+
description: 'Save pings locally instead of uploading immediately. Useful when you have poor connectivity. Upload saved sessions later from the Settings tab.',
261+
),
262+
263+
const SizedBox(height: 8),
264+
],
265+
),
266+
),
267+
),
268+
);
269+
}
270+
271+
/// Build a single help item
272+
Widget _buildHelpItem({
273+
required IconData icon,
274+
required Color color,
275+
required String title,
276+
required String description,
277+
}) {
278+
return Padding(
279+
padding: const EdgeInsets.only(bottom: 16),
280+
child: Row(
281+
crossAxisAlignment: CrossAxisAlignment.start,
282+
children: [
283+
Container(
284+
padding: const EdgeInsets.all(8),
285+
decoration: BoxDecoration(
286+
color: color.withValues(alpha: 0.15),
287+
borderRadius: BorderRadius.circular(8),
288+
),
289+
child: Icon(icon, size: 20, color: color),
290+
),
291+
const SizedBox(width: 12),
292+
Expanded(
293+
child: Column(
294+
crossAxisAlignment: CrossAxisAlignment.start,
295+
children: [
296+
Text(
297+
title,
298+
style: const TextStyle(
299+
fontSize: 14,
300+
fontWeight: FontWeight.w600,
301+
),
302+
),
303+
const SizedBox(height: 4),
304+
Text(
305+
description,
306+
style: TextStyle(
307+
fontSize: 13,
308+
color: Colors.grey.shade600,
309+
height: 1.4,
310+
),
311+
),
312+
],
313+
),
314+
),
315+
],
316+
),
317+
);
318+
}
319+
164320
/// Build subtitle with device name only
165321
String _buildSubtitle(AppStateProvider appState) {
166322
return appState.connectedDeviceName!.replaceFirst('MeshCore-', '');

lib/screens/settings_screen.dart

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
161161

162162
const Divider(),
163163

164-
// Queue section
165-
_buildSectionHeader(context, 'API Queue'),
164+
// Data Management section
165+
_buildSectionHeader(context, 'Data Management'),
166166
ListTile(
167167
leading: const Icon(Icons.cloud_queue),
168168
title: const Text('Queued Pings'),
@@ -188,6 +188,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
188188
),
189189
),
190190

191+
// Clear Map Markers
192+
ListTile(
193+
leading: const Icon(Icons.delete_sweep),
194+
title: const Text('Clear Map Markers'),
195+
subtitle: const Text('Remove all TX/RX markers from map'),
196+
onTap: () => _confirmClearPings(context, appState),
197+
),
198+
191199
// Offline Sessions
192200
if (appState.offlineSessions.isNotEmpty) ...[
193201
_buildSectionHeader(context, 'Offline Sessions'),
@@ -199,48 +207,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
199207
)),
200208
],
201209

202-
const Divider(),
203-
204-
// Statistics section
205-
_buildSectionHeader(context, 'Statistics'),
206-
ListTile(
207-
leading: const Icon(Icons.send),
208-
title: const Text('TX Pings'),
209-
trailing: Text(
210-
'${appState.pingStats.txCount}',
211-
style: Theme.of(context).textTheme.titleMedium,
212-
),
213-
),
214-
ListTile(
215-
leading: const Icon(Icons.call_received),
216-
title: const Text('RX Pings'),
217-
trailing: Text(
218-
'${appState.pingStats.rxCount}',
219-
style: Theme.of(context).textTheme.titleMedium,
220-
),
221-
),
222-
ListTile(
223-
leading: const Icon(Icons.radar),
224-
title: const Text('DISC Pings'),
225-
trailing: Text(
226-
'${appState.pingStats.discCount}',
227-
style: Theme.of(context).textTheme.titleMedium,
228-
),
229-
),
230-
ListTile(
231-
leading: const Icon(Icons.check_circle),
232-
title: const Text('Successful Uploads'),
233-
trailing: Text(
234-
'${appState.pingStats.successfulUploads}',
235-
style: Theme.of(context).textTheme.titleMedium,
236-
),
237-
),
238-
ListTile(
239-
leading: const Icon(Icons.delete_forever),
240-
title: const Text('Clear Map Markers'),
241-
onTap: () => _confirmClearPings(context, appState),
242-
),
243-
244210
const Divider(),
245211

246212
// Device Info section
@@ -273,6 +239,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
273239
subtitle: const Text('Report bugs or request features'),
274240
onTap: () => _launchUrl('https://github.com/MeshMapper/MeshMapper_Project'),
275241
),
242+
ListTile(
243+
leading: const Icon(Icons.groups),
244+
title: const Text('Community'),
245+
subtitle: const Text('Built with contributions from the Greater Ottawa Mesh Radio Enthusiasts community'),
246+
onTap: () => _launchUrl('https://ottawamesh.ca/'),
247+
),
248+
ListTile(
249+
leading: const Icon(Icons.coffee),
250+
title: const Text('Buy us a coffee ☕'),
251+
subtitle: const Text('Support MeshMapper development'),
252+
onTap: () => _launchUrl('https://buymeacoffee.com/meshmapper'),
253+
),
276254

277255
// Developer Tools section - only visible when developer mode is enabled
278256
if (appState.developerModeEnabled) ...[
@@ -590,8 +568,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
590568

591569
Future<void> _launchUrl(String url) async {
592570
final uri = Uri.parse(url);
593-
if (await canLaunchUrl(uri)) {
571+
try {
594572
await launchUrl(uri, mode: LaunchMode.externalApplication);
573+
} catch (e) {
574+
debugPrint('[SETTINGS] Failed to launch URL: $url - $e');
595575
}
596576
}
597577

lib/widgets/map_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ class _MapWidgetState extends State<MapWidget> with TickerProviderStateMixin {
899899
crossAxisAlignment: CrossAxisAlignment.start,
900900
children: [
901901
Text(
902-
'TX Ping',
902+
'Send Ping',
903903
style: Theme.of(context).textTheme.titleLarge?.copyWith(
904904
fontWeight: FontWeight.w600,
905905
),

lib/widgets/ping_controls.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ class PingControls extends StatelessWidget {
7777
// Action buttons row
7878
Row(
7979
children: [
80-
// TX Ping button - disabled when offline mode is active
80+
// Send Ping button - disabled when offline mode is active
8181
Expanded(
8282
child: _ActionButton(
8383
icon: Icons.cell_tower,
8484
label: txBlockedByOffline
8585
? 'TX Disabled'
86-
: (cooldownActive ? '$cooldownRemaining s' : 'TX Ping'),
86+
: (cooldownActive ? '$cooldownRemaining s' : 'Send Ping'),
8787
color: const Color(0xFF0EA5E9), // sky-500
8888
enabled: canPing && !isTxRxAutoRunning && !isRxAutoRunning && !cooldownActive && !txBlockedByOffline,
8989
onPressed: () => _sendPing(context, appState),
@@ -94,7 +94,7 @@ class PingControls extends StatelessWidget {
9494
),
9595
const SizedBox(width: 10),
9696

97-
// TX/RX Auto button - disabled when offline mode is active
97+
// Active Mode button - disabled when offline mode is active
9898
// Can start even when tooCloseToLastPing - ping will be skipped until user moves
9999
Expanded(
100100
child: _ActionButton(
@@ -103,7 +103,7 @@ class PingControls extends StatelessWidget {
103103
? 'TX Disabled'
104104
: (cooldownActive && !isTxRxAutoRunning
105105
? '$cooldownRemaining s'
106-
: 'TX/RX Auto'),
106+
: 'Active Mode'),
107107
color: isTxRxAutoRunning
108108
? const Color(0xFF22C55E) // green-500
109109
: const Color(0xFF6366F1), // indigo-500
@@ -116,12 +116,12 @@ class PingControls extends StatelessWidget {
116116
),
117117
const SizedBox(width: 10),
118118

119-
// RX Auto button
120-
// RX Auto is passive listening - needs connection + antenna + power config, no cooldown/GPS/distance checks
119+
// Passive Mode button
120+
// Passive Mode is passive listening - needs connection + antenna + power config, no cooldown/GPS/distance checks
121121
Expanded(
122122
child: _ActionButton(
123123
icon: Icons.hearing,
124-
label: 'RX Auto',
124+
label: 'Passive Mode',
125125
color: isRxAutoRunning
126126
? const Color(0xFF22C55E) // green-500
127127
: const Color(0xFF6366F1), // indigo-500

0 commit comments

Comments
 (0)