Set simple mode default

This commit is contained in:
Janez T
2026-03-08 09:19:13 +01:00
parent 2e15ccb3f3
commit 1f826ae4c2
8 changed files with 1133 additions and 1703 deletions

View File

@@ -43,8 +43,7 @@ class AppProvider with ChangeNotifier {
bool _isInitialized = false; bool _isInitialized = false;
bool get isInitialized => _isInitialized; bool get isInitialized => _isInitialized;
bool _isSimpleMode = true; bool get isSimpleMode => true;
bool get isSimpleMode => _isSimpleMode;
bool _isMapEnabled = true; bool _isMapEnabled = true;
bool get isMapEnabled => _isMapEnabled; bool get isMapEnabled => _isMapEnabled;
@@ -94,7 +93,6 @@ class AppProvider with ChangeNotifier {
}) { }) {
_setupCallbacks(); _setupCallbacks();
_initializeLocationTracking(); _initializeLocationTracking();
_loadSimpleMode();
_loadMapEnabled(); _loadMapEnabled();
_loadContactsEnabled(); _loadContactsEnabled();
_loadSensorsEnabled(); _loadSensorsEnabled();
@@ -223,29 +221,6 @@ class AppProvider with ChangeNotifier {
return contact?.advName; return contact?.advName;
} }
/// Load simple mode setting from shared preferences
Future<void> _loadSimpleMode() async {
try {
final prefs = await SharedPreferences.getInstance();
_isSimpleMode = prefs.getBool('simple_mode') ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading simple mode setting: $e');
}
}
/// Toggle simple mode on/off
Future<void> toggleSimpleMode(bool enabled) async {
try {
_isSimpleMode = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('simple_mode', enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving simple mode setting: $e');
}
}
/// Load map enabled setting from shared preferences /// Load map enabled setting from shared preferences
Future<void> _loadMapEnabled() async { Future<void> _loadMapEnabled() async {
try { try {
@@ -1214,10 +1189,8 @@ class AppProvider with ChangeNotifier {
// Sync channels to get channel names // Sync channels to get channel names
// In simple mode: only sync first 5 channels for faster startup // In simple mode: only sync first 5 channels for faster startup
// In normal mode: sync all channels (up to device max) // In normal mode: sync all channels (up to device max)
final channelsToSync = _isSimpleMode ? 5 : null; const channelsToSync = 5;
debugPrint( debugPrint('📻 [AppProvider] Syncing channels (simple mode: max 5)...');
'📻 [AppProvider] Syncing channels${_isSimpleMode ? ' (simple mode: max 5)' : ''}...',
);
await connectionProvider.syncChannels(maxChannels: channelsToSync); await connectionProvider.syncChannels(maxChannels: channelsToSync);
debugPrint('✅ [AppProvider] Channel sync complete'); debugPrint('✅ [AppProvider] Channel sync complete');
@@ -2220,7 +2193,7 @@ class AppProvider with ChangeNotifier {
await connectionProvider.getContacts(); await connectionProvider.getContacts();
// Sync channels (respect simple mode settings) // Sync channels (respect simple mode settings)
final channelsToSync = _isSimpleMode ? 5 : null; const channelsToSync = 5;
await connectionProvider.syncChannels(maxChannels: channelsToSync); await connectionProvider.syncChannels(maxChannels: channelsToSync);
// Messages are automatically synced via PUSH_CODE_MSG_WAITING events // Messages are automatically synced via PUSH_CODE_MSG_WAITING events

View File

@@ -27,7 +27,6 @@ import '../widgets/map_debug_info.dart';
import '../widgets/map/compass_widget.dart'; import '../widgets/map/compass_widget.dart';
import '../widgets/map/detailed_compass_dialog.dart'; import '../widgets/map/detailed_compass_dialog.dart';
import '../widgets/map/drawing_layer.dart'; import '../widgets/map/drawing_layer.dart';
import '../widgets/map/drawing_toolbar.dart';
import '../widgets/map/location_trail_layer.dart'; import '../widgets/map/location_trail_layer.dart';
import '../widgets/map/trail_controls.dart'; import '../widgets/map/trail_controls.dart';
import '../widgets/map/map_message_overlay.dart'; import '../widgets/map/map_message_overlay.dart';
@@ -708,78 +707,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
); );
} }
void _showOptionsMenu(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) => Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
children: [
const Icon(Icons.settings),
const SizedBox(width: 12),
Text(
AppLocalizations.of(context)!.mapOptions,
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
],
),
),
const Divider(),
// Map Debug Info toggle
SwitchListTile(
secondary: const Icon(Icons.developer_mode),
title: Text(AppLocalizations.of(context)!.showMapDebugInfo),
subtitle: Text(
AppLocalizations.of(context)!.displayZoomLevelBounds,
),
value: _showMapDebugInfo,
onChanged: (value) {
setState(() {
_showMapDebugInfo = value;
});
setModalState(() {});
_saveSettings();
},
),
const Divider(),
// Fullscreen mode toggle
SwitchListTile(
secondary: const Icon(Icons.fullscreen),
title: Text(AppLocalizations.of(context)!.fullscreenMode),
subtitle: Text(
AppLocalizations.of(context)!.hideUiFullMapView,
),
value: _isFullscreen,
onChanged: (value) {
setState(() {
_isFullscreen = value;
});
setModalState(() {});
_saveSettings();
// Notify parent about fullscreen change
widget.onFullscreenChanged?.call(value);
},
),
],
),
),
),
),
);
}
void _showDetailedCompass( void _showDetailedCompass(
BuildContext context, BuildContext context,
List<Contact> contacts, List<Contact> contacts,
@@ -1190,8 +1117,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); // Required for AutomaticKeepAliveClientMixin super.build(context); // Required for AutomaticKeepAliveClientMixin
final appProvider = context.watch<AppProvider>();
final isSimpleMode = appProvider.isSimpleMode;
return Consumer3<ContactsProvider, MessagesProvider, DrawingProvider>( return Consumer3<ContactsProvider, MessagesProvider, DrawingProvider>(
builder: (context, contactsProvider, messagesProvider, drawingProvider, child) { builder: (context, contactsProvider, messagesProvider, drawingProvider, child) {
@@ -1210,8 +1135,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
onPointerMove: (PointerMoveEvent event) { onPointerMove: (PointerMoveEvent event) {
// Track pointer movement for mobile drag (onPointerHover doesn't work on mobile) // Track pointer movement for mobile drag (onPointerHover doesn't work on mobile)
if (_isDraggingPin) { if (_isDraggingPin) {
final latLng = _mapController.camera final latLng = _mapController.camera.screenOffsetToLatLng(
.screenOffsetToLatLng(event.localPosition); event.localPosition,
);
setState(() { setState(() {
_droppedPinLocation = latLng; _droppedPinLocation = latLng;
}); });
@@ -1226,8 +1152,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
initialCenter: _savedMapCenter ?? center, initialCenter: _savedMapCenter ?? center,
initialZoom: _savedMapZoom ?? _defaultZoom, initialZoom: _savedMapZoom ?? _defaultZoom,
minZoom: 0, // Allow full zoom out to see world view minZoom: 0, // Allow full zoom out to see world view
maxZoom: _currentLayer maxZoom:
.maxZoom, // Respect current layer's maximum _currentLayer.maxZoom, // Respect current layer's maximum
interactionOptions: InteractionOptions( interactionOptions: InteractionOptions(
flags: _isDraggingPin flags: _isDraggingPin
? InteractiveFlag ? InteractiveFlag
@@ -1248,13 +1174,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
}, },
onLongPress: (tapPosition, point) { onLongPress: (tapPosition, point) {
// Handle measurement mode - set measurement points only, no SAR marker // Handle measurement mode - set measurement points only, no SAR marker
if (drawingProvider.drawingMode == if (drawingProvider.drawingMode == DrawingMode.measure) {
DrawingMode.measure) {
if (drawingProvider.measurementPoint1 == null) { if (drawingProvider.measurementPoint1 == null) {
// Set first measurement point // Set first measurement point
drawingProvider.setMeasurementPoint1(point); drawingProvider.setMeasurementPoint1(point);
} else if (drawingProvider.measurementPoint2 == } else if (drawingProvider.measurementPoint2 == null) {
null) {
// Set second measurement point // Set second measurement point
drawingProvider.setMeasurementPoint2(point); drawingProvider.setMeasurementPoint2(point);
} else { } else {
@@ -1295,8 +1219,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
}, },
onPointerHover: (event, point) { onPointerHover: (event, point) {
// Update rectangle preview while dragging // Update rectangle preview while dragging
if (drawingProvider.drawingMode == if (drawingProvider.drawingMode == DrawingMode.rectangle &&
DrawingMode.rectangle &&
drawingProvider.rectangleStartPoint != null) { drawingProvider.rectangleStartPoint != null) {
drawingProvider.updateRectangleEndPoint(point); drawingProvider.updateRectangleEndPoint(point);
return; return;
@@ -1370,8 +1293,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
layers: _currentLayer.wmsLayers ?? [], layers: _currentLayer.wmsLayers ?? [],
styles: _currentLayer.wmsStyles ?? [], styles: _currentLayer.wmsStyles ?? [],
format: _currentLayer.wmsFormat ?? 'image/jpeg', format: _currentLayer.wmsFormat ?? 'image/jpeg',
transparent: transparent: _currentLayer.wmsTransparent ?? false,
_currentLayer.wmsTransparent ?? false,
crs: _currentLayer.crs!, crs: _currentLayer.crs!,
), ),
// Use cached tile provider for offline support // Use cached tile provider for offline support
@@ -1435,8 +1357,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?',
layers: const ['pregledovalnik:gozdne_ceste'], layers: const ['pregledovalnik:gozdne_ceste'],
styles: const ['gozdne_ceste'], styles: const ['gozdne_ceste'],
format: 'image/png', format: 'image/png',
@@ -1466,8 +1387,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?',
layers: const [ layers: const [
'pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G', 'pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G',
], ],
@@ -1495,11 +1415,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?', layers: const ['pregledovalnik:KGI_LINIJE_CESTE_G'],
layers: const [
'pregledovalnik:KGI_LINIJE_CESTE_G',
],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
crs: slovenianCrs, crs: slovenianCrs,
@@ -1524,11 +1441,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?', layers: const ['pregledovalnik:NEP_HISNE_STEVILKE'],
layers: const [
'pregledovalnik:NEP_HISNE_STEVILKE',
],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
crs: slovenianCrs, crs: slovenianCrs,
@@ -1553,11 +1467,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?', layers: const ['pregledovalnik:pozarna_ogrozenost'],
layers: const [
'pregledovalnik:pozarna_ogrozenost',
],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
crs: slovenianCrs, crs: slovenianCrs,
@@ -1582,8 +1493,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?',
layers: const ['pregledovalnik:gozdni_pozari'], layers: const ['pregledovalnik:gozdni_pozari'],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
@@ -1609,11 +1519,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?', layers: const ['pregledovalnik:protipozarne_preseke'],
layers: const [
'pregledovalnik:protipozarne_preseke',
],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
crs: slovenianCrs, crs: slovenianCrs,
@@ -1638,8 +1545,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?',
layers: const ['pregledovalnik:pozarisce_kras'], layers: const ['pregledovalnik:pozarisce_kras'],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
@@ -1665,11 +1571,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?', layers: const ['pregledovalnik:zemljepisna_imena'],
layers: const [
'pregledovalnik:zemljepisna_imena',
],
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
crs: slovenianCrs, crs: slovenianCrs,
@@ -1694,8 +1597,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
} }
return flutter_map.TileLayer( return flutter_map.TileLayer(
wmsOptions: WMSTileLayerOptions( wmsOptions: WMSTileLayerOptions(
baseUrl: baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
'https://prostor.zgs.gov.si/geoserver/wms?',
layers: const ['pregledovalnik:NEP_RPE_OBCINE'], layers: const ['pregledovalnik:NEP_RPE_OBCINE'],
styles: const ['obcine'], styles: const ['obcine'],
format: 'image/png', format: 'image/png',
@@ -1724,18 +1626,13 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
return PolylineLayer( return PolylineLayer(
polylines: [ polylines: [
Polyline( Polyline(
points: points: mapProvider.importedTrail!.latLngPoints,
mapProvider.importedTrail!.latLngPoints,
color: Colors.green.withValues(alpha: 0.7), color: Colors.green.withValues(alpha: 0.7),
strokeWidth: 3.0, strokeWidth: 3.0,
borderColor: Colors.white.withValues( borderColor: Colors.white.withValues(alpha: 0.4),
alpha: 0.4,
),
borderStrokeWidth: 1.0, borderStrokeWidth: 1.0,
// DOTTED pattern to distinguish from other trails // DOTTED pattern to distinguish from other trails
pattern: StrokePattern.dotted( pattern: StrokePattern.dotted(spacingFactor: 2),
spacingFactor: 2,
),
), ),
], ],
); );
@@ -1745,12 +1642,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
Consumer<MapProvider>( Consumer<MapProvider>(
builder: (context, mapProvider, _) { builder: (context, mapProvider, _) {
// Determine which contacts to show trails for // Determine which contacts to show trails for
final contactsToShow = final contactsToShow = mapProvider.showAllContactTrails
mapProvider.showAllContactTrails
? contactsWithLocation // Show all when master toggle is ON ? contactsWithLocation // Show all when master toggle is ON
: contactsWithLocation.where( : contactsWithLocation.where(
(contact) => (contact) => mapProvider.isContactPathVisible(
mapProvider.isContactPathVisible(
contact.publicKeyHex, contact.publicKeyHex,
), ),
); // Individual toggles ); // Individual toggles
@@ -1758,13 +1653,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
return PolylineLayer( return PolylineLayer(
polylines: contactsToShow polylines: contactsToShow
.where( .where(
(contact) => (contact) => contact.advertHistory.length >= 2,
contact.advertHistory.length >= 2,
) )
.map((contact) { .map((contact) {
// Use TrailColorService for consistent, emoji-based colors // Use TrailColorService for consistent, emoji-based colors
final color = final color = TrailColorService.getTrailColor(
TrailColorService.getTrailColor(
contact, contact,
); );
@@ -1782,9 +1675,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
), // Stronger border contrast ), // Stronger border contrast
borderStrokeWidth: 2.0, // Wider border borderStrokeWidth: 2.0, // Wider border
// DASHED pattern to distinguish from solid user trail // DASHED pattern to distinguish from solid user trail
pattern: StrokePattern.dashed( pattern: StrokePattern.dashed(segments: [8, 4]),
segments: [8, 4],
),
); );
}) })
.toList(), .toList(),
@@ -1805,13 +1696,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
], ],
color: Colors.yellow.withValues(alpha: 0.8), color: Colors.yellow.withValues(alpha: 0.8),
strokeWidth: 3.0, strokeWidth: 3.0,
borderColor: Colors.black.withValues( borderColor: Colors.black.withValues(alpha: 0.5),
alpha: 0.5,
),
borderStrokeWidth: 1.0, borderStrokeWidth: 1.0,
pattern: StrokePattern.dashed( pattern: StrokePattern.dashed(segments: [10, 5]),
segments: [10, 5],
),
), ),
], ],
), ),
@@ -1819,7 +1706,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
DrawingLayer( DrawingLayer(
drawings: drawingProvider.drawings, drawings: drawingProvider.drawings,
previewDrawing: drawingProvider.getPreviewDrawing(), previewDrawing: drawingProvider.getPreviewDrawing(),
isSimpleMode: isSimpleMode,
), ),
MarkerLayer( MarkerLayer(
markers: [ markers: [
@@ -2008,9 +1894,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
color: _isDraggingPin color: _isDraggingPin
? Colors.orange ? Colors.orange
: Colors.red, : Colors.red,
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(8),
8,
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues( color: Colors.black.withValues(
@@ -2063,16 +1947,13 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
DrawingMarkersLayer( DrawingMarkersLayer(
drawings: drawingProvider.drawings, drawings: drawingProvider.drawings,
showDeleteButtons: drawingProvider.isDrawing, showDeleteButtons: drawingProvider.isDrawing,
isSimpleMode: isSimpleMode,
onDeleteDrawing: (drawingId) { onDeleteDrawing: (drawingId) {
drawingProvider.removeDrawing(drawingId); drawingProvider.removeDrawing(drawingId);
}, },
onTapDrawing: (drawing) { onTapDrawing: (drawing) {
// Navigate to the corresponding message in Messages tab // Navigate to the corresponding message in Messages tab
if (drawing.messageId != null) { if (drawing.messageId != null) {
messagesProvider.navigateToMessage( messagesProvider.navigateToMessage(drawing.messageId!);
drawing.messageId!,
);
widget.onNavigateToMessages?.call(); widget.onNavigateToMessages?.call();
} }
}, },
@@ -2250,15 +2131,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
right: 16, right: 16,
child: Column( child: Column(
children: [ children: [
// Drawing toolbar (hidden in simple mode)
Consumer<AppProvider>(
builder: (context, appProvider, _) {
if (appProvider.isSimpleMode) {
return const SizedBox.shrink();
}
return const DrawingToolbar();
},
),
// Hide other buttons when in drawing mode // Hide other buttons when in drawing mode
if (!drawingProvider.isDrawing) ...[ if (!drawingProvider.isDrawing) ...[
// Current Location - always center to GPS // Current Location - always center to GPS
@@ -2351,49 +2223,29 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
// In simple mode: show ruler FAB directly
Consumer<AppProvider>(
builder: (context, appProvider, _) {
if (!appProvider.isSimpleMode) {
return const SizedBox.shrink();
}
// Show ruler button
return Column(
children: [
FloatingActionButton.small( FloatingActionButton.small(
heroTag: 'ruler_tool', heroTag: 'ruler_tool',
backgroundColor: backgroundColor:
drawingProvider.drawingMode == drawingProvider.drawingMode == DrawingMode.measure
DrawingMode.measure
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: null, : null,
onPressed: () { onPressed: () {
if (drawingProvider.drawingMode == if (drawingProvider.drawingMode ==
DrawingMode.measure) { DrawingMode.measure) {
// Exit measurement mode
drawingProvider.exitDrawingMode(); drawingProvider.exitDrawingMode();
} else { } else {
// Enter measurement mode drawingProvider.setDrawingMode(DrawingMode.measure);
drawingProvider.setDrawingMode(
DrawingMode.measure,
);
} }
}, },
child: Icon( child: Icon(
Icons.straighten, Icons.straighten,
color: color:
drawingProvider.drawingMode == drawingProvider.drawingMode == DrawingMode.measure
DrawingMode.measure
? Colors.white ? Colors.white
: null, : null,
), ),
), ),
if (!drawingProvider.isDrawing) if (!drawingProvider.isDrawing) const SizedBox(height: 8),
const SizedBox(height: 8),
],
);
},
),
// Continue with other buttons when not in drawing mode // Continue with other buttons when not in drawing mode
if (!drawingProvider.isDrawing) ...[ if (!drawingProvider.isDrawing) ...[
// Trail controls button // Trail controls button
@@ -2405,9 +2257,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
child: const Icon(Icons.layers), child: const Icon(Icons.layers),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// In simple mode: show fullscreen button directly
// In normal mode: show options menu (which includes fullscreen)
if (context.watch<AppProvider>().isSimpleMode)
FloatingActionButton.small( FloatingActionButton.small(
heroTag: 'fullscreen_toggle', heroTag: 'fullscreen_toggle',
onPressed: () { onPressed: () {
@@ -2422,12 +2271,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
? Icons.fullscreen_exit ? Icons.fullscreen_exit
: Icons.fullscreen, : Icons.fullscreen,
), ),
)
else
FloatingActionButton.small(
heroTag: 'options_menu',
onPressed: () => _showOptionsMenu(context),
child: const Icon(Icons.more_vert),
), ),
], ],
], ],

View File

@@ -1436,10 +1436,6 @@ class _MessagesTabState extends State<MessagesTab> {
// Get all recent messages // Get all recent messages
final allMessages = messagesProvider.getRecentMessages(count: 100); final allMessages = messagesProvider.getRecentMessages(count: 100);
// Get simple mode setting from AppProvider
final appProvider = context.read<AppProvider>();
final isSimpleMode = appProvider.isSimpleMode;
List<Message> filteredMessages; List<Message> filteredMessages;
// If channel destination is selected, filter by selected channel. // If channel destination is selected, filter by selected channel.
@@ -1492,16 +1488,11 @@ class _MessagesTabState extends State<MessagesTab> {
filteredMessages = allMessages; filteredMessages = allMessages;
} }
// In simple mode, filter out system messages (toast logs) return filteredMessages
if (isSimpleMode) {
filteredMessages = filteredMessages
.where((message) => !message.isSystemMessage) .where((message) => !message.isSystemMessage)
.toList(); .toList();
} }
return filteredMessages;
}
void _handleMessageTap(Message message) { void _handleMessageTap(Message message) {
if (widget.onNavigateToMap == null) return; if (widget.onNavigateToMap == null) return;

View File

@@ -864,19 +864,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
_buildSectionHeader('Navigation'), _buildSectionHeader('Navigation'),
_buildSettingsCard([ _buildSettingsCard([
Consumer<AppProvider>(
builder: (context, appProvider, child) => SwitchListTile(
secondary: const Icon(Icons.visibility_off),
title: Text(AppLocalizations.of(context)!.simpleMode),
subtitle: Text(
AppLocalizations.of(context)!.simpleModeDescription,
),
value: appProvider.isSimpleMode,
onChanged: (value) async {
await appProvider.toggleSimpleMode(value);
},
),
),
Consumer<AppProvider>( Consumer<AppProvider>(
builder: (context, appProvider, child) => SwitchListTile( builder: (context, appProvider, child) => SwitchListTile(
secondary: const Icon(Icons.map_outlined), secondary: const Icon(Icons.map_outlined),

View File

@@ -15,7 +15,7 @@ class WelcomeWizardScreen extends StatefulWidget {
class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> { class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
final PageController _pageController = PageController(); final PageController _pageController = PageController();
int _currentPage = 0; int _currentPage = 0;
static const int _totalPages = 6; static const int _totalPages = 5;
@override @override
void dispose() { void dispose() {
@@ -99,7 +99,6 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
children: [ children: [
_buildWelcomePage(context, l10n, colorScheme), _buildWelcomePage(context, l10n, colorScheme),
_buildConnectingPage(context, l10n, colorScheme), _buildConnectingPage(context, l10n, colorScheme),
_buildSimpleModePage(context, l10n, colorScheme),
_buildChannelPage(context, l10n, colorScheme), _buildChannelPage(context, l10n, colorScheme),
_buildContactsPage(context, l10n, colorScheme), _buildContactsPage(context, l10n, colorScheme),
_buildMapPage(context, l10n, colorScheme), _buildMapPage(context, l10n, colorScheme),
@@ -182,42 +181,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
title: l10n.wizardConnectingTitle, title: l10n.wizardConnectingTitle,
description: l10n.wizardConnectingDescription, description: l10n.wizardConnectingDescription,
features: [ features: [
_FeatureItem( _FeatureItem(icon: Icons.radio, text: l10n.wizardConnectingFeature1),
icon: Icons.radio, _FeatureItem(icon: Icons.link, text: l10n.wizardConnectingFeature2),
text: l10n.wizardConnectingFeature1, _FeatureItem(icon: Icons.wifi_off, text: l10n.wizardConnectingFeature3),
),
_FeatureItem(
icon: Icons.link,
text: l10n.wizardConnectingFeature2,
),
_FeatureItem(
icon: Icons.wifi_off,
text: l10n.wizardConnectingFeature3,
),
],
colorScheme: colorScheme,
);
}
Widget _buildSimpleModePage(
BuildContext context,
AppLocalizations l10n,
ColorScheme colorScheme,
) {
return _buildPage(
icon: Icons.toggle_on,
iconColor: Colors.green,
title: l10n.wizardSimpleModeTitle,
description: l10n.wizardSimpleModeDescription,
features: [
_FeatureItem(
icon: Icons.check_circle_outline,
text: l10n.wizardSimpleModeFeature1,
),
_FeatureItem(
icon: Icons.settings,
text: l10n.wizardSimpleModeFeature2,
),
], ],
colorScheme: colorScheme, colorScheme: colorScheme,
); );
@@ -234,18 +200,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
title: l10n.wizardChannelTitle, title: l10n.wizardChannelTitle,
description: l10n.wizardChannelDescription, description: l10n.wizardChannelDescription,
features: [ features: [
_FeatureItem( _FeatureItem(icon: Icons.public, text: l10n.wizardChannelFeature1),
icon: Icons.public, _FeatureItem(icon: Icons.groups, text: l10n.wizardChannelFeature2),
text: l10n.wizardChannelFeature1, _FeatureItem(icon: Icons.send, text: l10n.wizardChannelFeature3),
),
_FeatureItem(
icon: Icons.groups,
text: l10n.wizardChannelFeature2,
),
_FeatureItem(
icon: Icons.send,
text: l10n.wizardChannelFeature3,
),
], ],
colorScheme: colorScheme, colorScheme: colorScheme,
); );
@@ -262,14 +219,8 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
title: l10n.wizardContactsTitle, title: l10n.wizardContactsTitle,
description: l10n.wizardContactsDescription, description: l10n.wizardContactsDescription,
features: [ features: [
_FeatureItem( _FeatureItem(icon: Icons.person_add, text: l10n.wizardContactsFeature1),
icon: Icons.person_add, _FeatureItem(icon: Icons.chat, text: l10n.wizardContactsFeature2),
text: l10n.wizardContactsFeature1,
),
_FeatureItem(
icon: Icons.chat,
text: l10n.wizardContactsFeature2,
),
_FeatureItem( _FeatureItem(
icon: Icons.battery_std, icon: Icons.battery_std,
text: l10n.wizardContactsFeature3, text: l10n.wizardContactsFeature3,
@@ -290,22 +241,13 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
title: l10n.wizardMapTitle, title: l10n.wizardMapTitle,
description: l10n.wizardMapDescription, description: l10n.wizardMapDescription,
features: [ features: [
_FeatureItem( _FeatureItem(icon: Icons.location_on, text: l10n.wizardMapFeature1),
icon: Icons.location_on,
text: l10n.wizardMapFeature1,
),
_FeatureItem( _FeatureItem(
icon: Icons.person_pin_circle, icon: Icons.person_pin_circle,
text: l10n.wizardMapFeature2, text: l10n.wizardMapFeature2,
), ),
_FeatureItem( _FeatureItem(icon: Icons.offline_pin, text: l10n.wizardMapFeature3),
icon: Icons.offline_pin, _FeatureItem(icon: Icons.draw, text: l10n.wizardMapFeature4),
text: l10n.wizardMapFeature3,
),
_FeatureItem(
icon: Icons.draw,
text: l10n.wizardMapFeature4,
),
], ],
colorScheme: colorScheme, colorScheme: colorScheme,
); );
@@ -332,11 +274,7 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
color: iconColor.withValues(alpha: 0.1), color: iconColor.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(icon, size: 80, color: iconColor),
icon,
size: 80,
color: iconColor,
),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
// Title // Title
@@ -361,28 +299,25 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
if (features != null && features.isNotEmpty) ...[ if (features != null && features.isNotEmpty) ...[
const SizedBox(height: 32), const SizedBox(height: 32),
// Features list // Features list
...features.map((feature) => Padding( ...features.map(
(feature) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row( child: Row(
children: [ children: [
Icon( Icon(feature.icon, color: colorScheme.primary, size: 24),
feature.icon,
color: colorScheme.primary,
size: 24,
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Text( child: Text(
feature.text, feature.text,
style: style: Theme.of(context).textTheme.bodyMedium?.copyWith(
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurface, color: colorScheme.onSurface,
), ),
), ),
), ),
], ],
), ),
)), ),
),
], ],
const SizedBox(height: 20), const SizedBox(height: 20),
], ],

View File

@@ -8,13 +8,11 @@ import '../../models/room_login_state.dart';
import '../../providers/connection_provider.dart'; import '../../providers/connection_provider.dart';
import '../../providers/contacts_provider.dart'; import '../../providers/contacts_provider.dart';
import '../../providers/map_provider.dart'; import '../../providers/map_provider.dart';
import '../../providers/app_provider.dart';
import 'contact_route_dialog.dart'; import 'contact_route_dialog.dart';
import 'room_login_sheet.dart'; import 'room_login_sheet.dart';
import '../common/contact_avatar.dart'; import '../common/contact_avatar.dart';
import '../../utils/location_formats.dart'; import '../../utils/location_formats.dart';
import '../../utils/toast_logger.dart'; import '../../utils/toast_logger.dart';
import '../../utils/battery_display_helper.dart';
import '../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
class ContactTile extends StatelessWidget { class ContactTile extends StatelessWidget {
@@ -46,12 +44,6 @@ class ContactTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appProvider = context.watch<AppProvider>();
final isSimpleMode = appProvider.isSimpleMode;
final hasTelemetry =
contact.telemetry != null && contact.telemetry!.isRecent;
final battery = contact.displayBattery;
final location = contact.displayLocation; final location = contact.displayLocation;
// Calculate distance if both positions are available // Calculate distance if both positions are available
String? distanceText; String? distanceText;
@@ -102,13 +94,11 @@ class ContactTile extends StatelessWidget {
) )
: null; : null;
void handleTap() { void handleTap() {
if (isSimpleMode && contact.type == ContactType.chat) { if (contact.type == ContactType.chat) {
_showSetRouteDialog(context, contact); _showSetRouteDialog(context, contact);
} else if (isSimpleMode && contact.type == ContactType.repeater) { } else if (contact.type == ContactType.repeater) {
_jumpToMapForRepeater(context, contact); _jumpToMapForRepeater(context, contact);
} else if (isSimpleMode && } else if (contact.type == ContactType.room && !contact.isPublicChannel) {
contact.type == ContactType.room &&
!contact.isPublicChannel) {
_showRoomLoginDialog(context, contact); _showRoomLoginDialog(context, contact);
} else { } else {
_showContactDetails(context, contact); _showContactDetails(context, contact);
@@ -155,8 +145,7 @@ class ContactTile extends StatelessWidget {
: colorScheme.onSurfaceVariant, : colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
final subtitleWidget = isSimpleMode final subtitleWidget = Column(
? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (location != null) ...[ if (location != null) ...[
@@ -182,109 +171,6 @@ class ContactTile extends StatelessWidget {
), ),
), ),
], ],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 2),
if (roomLoginState != null && roomLoginState.isLoggedIn) ...[
Row(
children: [
if (roomLoginState.isAdmin)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.red, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.admin_panel_settings,
size: 10,
color: Colors.red,
),
const SizedBox(width: 2),
Text(
AppLocalizations.of(context)!.admin,
style: Theme.of(context).textTheme.labelSmall
?.copyWith(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
],
),
),
if (roomLoginState.isAdmin) const SizedBox(width: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.green.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.green, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.check_circle,
size: 10,
color: Colors.green,
),
const SizedBox(width: 2),
Text(
AppLocalizations.of(context)!.loggedIn,
style: Theme.of(context).textTheme.labelSmall
?.copyWith(
color: Colors.green,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
],
),
),
],
),
const SizedBox(height: 4),
],
Row(
children: [
if (location != null) ...[
Expanded(
child: _buildLocationLine(
context,
latitude: location.latitude,
longitude: location.longitude,
distanceText: distanceText,
telemetryActive: hasTelemetry,
),
),
] else ...[
const Icon(Icons.sensors_off, size: 12, color: Colors.grey),
const SizedBox(width: 4),
Text(
AppLocalizations.of(context)!.noGpsData,
style: Theme.of(context).textTheme.labelSmall,
),
],
],
),
if (contact.type != ContactType.channel) ...[
const SizedBox(height: 6),
Row(children: [_buildRoutePill(context, contact)]),
],
],
); );
return Container( return Container(
@@ -383,10 +269,6 @@ class ContactTile extends StatelessWidget {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(timeAgoText, style: timeAgoStyle), Text(timeAgoText, style: timeAgoStyle),
if (!isSimpleMode && battery != null) ...[
const SizedBox(width: 6),
_buildBatteryBadge(context, battery),
],
if (isPingInProgress) ...[ if (isPingInProgress) ...[
const SizedBox(width: 6), const SizedBox(width: 6),
SizedBox( SizedBox(
@@ -1439,33 +1321,4 @@ class ContactTile extends StatelessWidget {
), ),
); );
} }
Widget _buildBatteryBadge(BuildContext context, double battery) {
final color = BatteryDisplayHelper.getBatteryColor(battery);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
BatteryDisplayHelper.getBatteryIcon(battery),
size: 12,
color: color,
),
const SizedBox(width: 4),
Text(
'${battery.round()}%',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
} }

View File

@@ -8,14 +8,8 @@ import '../../l10n/app_localizations.dart';
class DrawingLayer extends StatelessWidget { class DrawingLayer extends StatelessWidget {
final List<MapDrawing> drawings; final List<MapDrawing> drawings;
final MapDrawing? previewDrawing; final MapDrawing? previewDrawing;
final bool isSimpleMode;
const DrawingLayer({ const DrawingLayer({super.key, required this.drawings, this.previewDrawing});
super.key,
required this.drawings,
this.previewDrawing,
this.isSimpleMode = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -48,8 +42,7 @@ class DrawingLayer extends StatelessWidget {
strokeWidth = 4.0; strokeWidth = 4.0;
} else if (drawing.isReceived) { } else if (drawing.isReceived) {
// Received drawing from another node // Received drawing from another node
// In simple mode: solid (opacity 1.0), in normal mode: translucent (0.7) opacity = 1.0;
opacity = isSimpleMode ? 1.0 : 0.7;
strokeWidth = 3.0; strokeWidth = 3.0;
} else { } else {
// Local drawing (solid line, normal thickness) // Local drawing (solid line, normal thickness)
@@ -87,7 +80,6 @@ class DrawingMarkersLayer extends StatelessWidget {
final Function(String drawingId)? onDeleteDrawing; final Function(String drawingId)? onDeleteDrawing;
final Function(MapDrawing drawing)? onTapDrawing; final Function(MapDrawing drawing)? onTapDrawing;
final bool showDeleteButtons; final bool showDeleteButtons;
final bool isSimpleMode;
const DrawingMarkersLayer({ const DrawingMarkersLayer({
super.key, super.key,
@@ -95,7 +87,6 @@ class DrawingMarkersLayer extends StatelessWidget {
this.onDeleteDrawing, this.onDeleteDrawing,
this.onTapDrawing, this.onTapDrawing,
this.showDeleteButtons = false, this.showDeleteButtons = false,
this.isSimpleMode = false,
}); });
@override @override
@@ -132,73 +123,7 @@ class DrawingMarkersLayer extends StatelessWidget {
), ),
], ],
), ),
child: const Icon( child: const Icon(Icons.close, color: Colors.white, size: 20),
Icons.close,
color: Colors.white,
size: 20,
),
),
),
),
);
} else if (drawing.isReceived && drawing.senderName != null && !isSimpleMode) {
// Show sender badge for received drawings (when not in drawing mode and not in simple mode)
// Make it tappable if message ID is available
markers.add(
Marker(
point: centerPoint,
width: 120,
height: 30,
child: GestureDetector(
onTap: drawing.messageId != null && onTapDrawing != null
? () => onTapDrawing!(drawing)
: null,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: drawing.color.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white, width: 1.5),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.person,
color: Colors.white,
size: 14,
),
const SizedBox(width: 4),
Flexible(
child: Text(
drawing.senderName!,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
// Add indicator that this is tappable
if (drawing.messageId != null && onTapDrawing != null) ...[
const SizedBox(width: 4),
const Icon(
Icons.arrow_forward_ios,
color: Colors.white,
size: 10,
),
],
],
),
), ),
), ),
), ),
@@ -236,9 +161,7 @@ class DrawingMarkersLayer extends StatelessWidget {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.deleteDrawing), title: Text(AppLocalizations.of(context)!.deleteDrawing),
content: Text( content: Text('Delete this ${drawing.type.name}?'),
'Delete this ${drawing.type.name}?',
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
@@ -249,9 +172,7 @@ class DrawingMarkersLayer extends StatelessWidget {
Navigator.pop(context); Navigator.pop(context);
onDeleteDrawing?.call(drawing.id); onDeleteDrawing?.call(drawing.id);
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(foregroundColor: Colors.red),
foregroundColor: Colors.red,
),
child: Text(AppLocalizations.of(context)!.delete), child: Text(AppLocalizations.of(context)!.delete),
), ),
], ],

View File

@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../providers/map_provider.dart'; import '../../providers/map_provider.dart';
import '../../providers/contacts_provider.dart'; import '../../providers/contacts_provider.dart';
import '../../providers/app_provider.dart';
import '../../services/gpx_service.dart';
import '../../services/trail_color_service.dart'; import '../../services/trail_color_service.dart';
import '../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
@@ -13,10 +11,11 @@ class TrailControls extends StatelessWidget {
void _showTrailMenu(BuildContext context) { void _showTrailMenu(BuildContext context) {
final mapProvider = Provider.of<MapProvider>(context, listen: false); final mapProvider = Provider.of<MapProvider>(context, listen: false);
final contactsProvider = Provider.of<ContactsProvider>(context, listen: false); final contactsProvider = Provider.of<ContactsProvider>(
final appProvider = Provider.of<AppProvider>(context, listen: false); context,
listen: false,
);
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final isSimpleMode = appProvider.isSimpleMode;
// Get contacts with trails (advertHistory >= 2 points) // Get contacts with trails (advertHistory >= 2 points)
final contactsWithTrails = contactsProvider.contactsWithLocation final contactsWithTrails = contactsProvider.contactsWithLocation
@@ -68,13 +67,16 @@ class TrailControls extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
// Trail stats // Trail stats
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty) if (mapProvider.currentTrail != null &&
mapProvider.currentTrail!.points.isNotEmpty)
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1), color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withValues(alpha: 0.3)), border: Border.all(
color: Colors.blue.withValues(alpha: 0.3),
),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -82,7 +84,9 @@ class TrailControls extends StatelessWidget {
_buildStatRow( _buildStatRow(
icon: Icons.straighten, icon: Icons.straighten,
label: l10n.distance, label: l10n.distance,
value: _formatDistance(mapProvider.totalTrailDistance), value: _formatDistance(
mapProvider.totalTrailDistance,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildStatRow( _buildStatRow(
@@ -102,62 +106,9 @@ class TrailControls extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// GPX Export/Import buttons (hidden in simple mode)
if (!isSimpleMode) ...[
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty)
ElevatedButton.icon(
onPressed: () async {
final success = await GpxService.exportTrailToFile(mapProvider.currentTrail!);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? l10n.trailExportedSuccessfully
: l10n.failedToExportTrail),
backgroundColor: success ? Colors.green : Colors.red,
),
);
}
},
icon: const Icon(Icons.upload),
label: Text(l10n.exportTrailToGpx),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () async {
try {
final trail = await GpxService.importTrailFromFile();
if (trail != null && context.mounted) {
_showImportDialog(context, mapProvider, trail, l10n);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.failedToImportTrail(e.toString())),
backgroundColor: Colors.red,
),
);
}
}
},
icon: const Icon(Icons.download),
label: Text(l10n.importTrailFromGpx),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
),
const SizedBox(height: 16),
],
// Clear trail button // Clear trail button
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty) if (mapProvider.currentTrail != null &&
mapProvider.currentTrail!.points.isNotEmpty)
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
_showClearConfirmation(context, mapProvider, l10n); _showClearConfirmation(context, mapProvider, l10n);
@@ -172,13 +123,18 @@ class TrailControls extends StatelessWidget {
), ),
// No trail message // No trail message
if (mapProvider.currentTrail == null || mapProvider.currentTrail!.points.isEmpty) if (mapProvider.currentTrail == null ||
mapProvider.currentTrail!.points.isEmpty)
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
const Icon(Icons.timeline, size: 48, color: Colors.grey), const Icon(
Icons.timeline,
size: 48,
color: Colors.grey,
),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
l10n.noTrailRecorded, l10n.noTrailRecorded,
@@ -225,11 +181,15 @@ class TrailControls extends StatelessWidget {
SwitchListTile( SwitchListTile(
secondary: const Icon(Icons.route), secondary: const Icon(Icons.route),
title: Text(l10n.showAllContactTrails), title: Text(l10n.showAllContactTrails),
subtitle: Text(contactsWithTrails.isEmpty subtitle: Text(
contactsWithTrails.isEmpty
? l10n.noContactsWithLocationHistory ? l10n.noContactsWithLocationHistory
: mapProvider.showAllContactTrails : mapProvider.showAllContactTrails
? l10n.showingTrailsForContacts(contactsWithTrails.length) ? l10n.showingTrailsForContacts(
: l10n.individualContactTrails), contactsWithTrails.length,
)
: l10n.individualContactTrails,
),
value: mapProvider.showAllContactTrails, value: mapProvider.showAllContactTrails,
onChanged: contactsWithTrails.isNotEmpty onChanged: contactsWithTrails.isNotEmpty
? (value) { ? (value) {
@@ -240,13 +200,18 @@ class TrailControls extends StatelessWidget {
), ),
// Individual contact trails (when "show all" is OFF) // Individual contact trails (when "show all" is OFF)
if (!mapProvider.showAllContactTrails && contactsWithTrails.isNotEmpty) if (!mapProvider.showAllContactTrails &&
contactsWithTrails.isNotEmpty)
ExpansionTile( ExpansionTile(
title: Text(l10n.individualContactTrails), title: Text(l10n.individualContactTrails),
initiallyExpanded: false, initiallyExpanded: false,
children: contactsWithTrails.map((contact) { children: contactsWithTrails.map((contact) {
final trailColor = TrailColorService.getTrailColor(contact); final trailColor = TrailColorService.getTrailColor(
final isVisible = mapProvider.isContactPathVisible(contact.publicKeyHex); contact,
);
final isVisible = mapProvider.isContactPathVisible(
contact.publicKeyHex,
);
return SwitchListTile( return SwitchListTile(
// Color indicator with emoji // Color indicator with emoji
@@ -254,21 +219,29 @@ class TrailControls extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (contact.roleEmoji != null) if (contact.roleEmoji != null)
Text(contact.roleEmoji!, style: const TextStyle(fontSize: 18)), Text(
contact.roleEmoji!,
style: const TextStyle(fontSize: 18),
),
const SizedBox(width: 4), const SizedBox(width: 4),
Container( Container(
width: 16, width: 16,
height: 16, height: 16,
decoration: BoxDecoration( decoration: BoxDecoration(
color: trailColor, color: trailColor,
border: Border.all(color: Colors.white, width: 2), border: Border.all(
color: Colors.white,
width: 2,
),
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(3),
), ),
), ),
], ],
), ),
title: Text(contact.displayName), title: Text(contact.displayName),
subtitle: Text('${contact.advertHistory.length} points'), subtitle: Text(
'${contact.advertHistory.length} points',
),
value: isVisible, value: isVisible,
onChanged: (value) { onChanged: (value) {
mapProvider.toggleContactPath(contact.publicKeyHex); mapProvider.toggleContactPath(contact.publicKeyHex);
@@ -293,7 +266,11 @@ class TrailControls extends StatelessWidget {
); );
} }
void _showClearConfirmation(BuildContext context, MapProvider mapProvider, AppLocalizations l10n) { void _showClearConfirmation(
BuildContext context,
MapProvider mapProvider,
AppLocalizations l10n,
) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@@ -318,50 +295,6 @@ class TrailControls extends StatelessWidget {
); );
} }
void _showImportDialog(BuildContext context, MapProvider mapProvider, trail, AppLocalizations l10n) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.importTrail),
content: Text(l10n.importTrailQuestion(trail.points.length)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.cancel),
),
TextButton(
onPressed: () {
mapProvider.setImportedTrail(trail);
Navigator.pop(context); // Close dialog
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.trailImported(trail.points.length)),
backgroundColor: Colors.green,
),
);
},
child: Text(l10n.viewAlongside),
),
TextButton(
onPressed: () {
mapProvider.replaceCurrentTrailWithImport(trail);
Navigator.pop(context); // Close dialog
Navigator.pop(context); // Close bottom sheet
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.trailReplaced(trail.points.length)),
backgroundColor: Colors.green,
),
);
},
style: TextButton.styleFrom(foregroundColor: Colors.blue),
child: Text(l10n.replaceCurrent),
),
],
),
);
}
Widget _buildStatRow({ Widget _buildStatRow({
required IconData icon, required IconData icon,
required String label, required String label,
@@ -373,18 +306,12 @@ class TrailControls extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
label, label,
style: const TextStyle( style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
fontWeight: FontWeight.w500,
fontSize: 14,
),
), ),
const Spacer(), const Spacer(),
Text( Text(
value, value,
style: const TextStyle( style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
fontWeight: FontWeight.bold,
fontSize: 14,
),
), ),
], ],
); );