mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
Set simple mode default
This commit is contained in:
@@ -43,8 +43,7 @@ class AppProvider with ChangeNotifier {
|
||||
bool _isInitialized = false;
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
bool _isSimpleMode = true;
|
||||
bool get isSimpleMode => _isSimpleMode;
|
||||
bool get isSimpleMode => true;
|
||||
|
||||
bool _isMapEnabled = true;
|
||||
bool get isMapEnabled => _isMapEnabled;
|
||||
@@ -94,7 +93,6 @@ class AppProvider with ChangeNotifier {
|
||||
}) {
|
||||
_setupCallbacks();
|
||||
_initializeLocationTracking();
|
||||
_loadSimpleMode();
|
||||
_loadMapEnabled();
|
||||
_loadContactsEnabled();
|
||||
_loadSensorsEnabled();
|
||||
@@ -223,29 +221,6 @@ class AppProvider with ChangeNotifier {
|
||||
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
|
||||
Future<void> _loadMapEnabled() async {
|
||||
try {
|
||||
@@ -1214,10 +1189,8 @@ class AppProvider with ChangeNotifier {
|
||||
// Sync channels to get channel names
|
||||
// In simple mode: only sync first 5 channels for faster startup
|
||||
// In normal mode: sync all channels (up to device max)
|
||||
final channelsToSync = _isSimpleMode ? 5 : null;
|
||||
debugPrint(
|
||||
'📻 [AppProvider] Syncing channels${_isSimpleMode ? ' (simple mode: max 5)' : ''}...',
|
||||
);
|
||||
const channelsToSync = 5;
|
||||
debugPrint('📻 [AppProvider] Syncing channels (simple mode: max 5)...');
|
||||
await connectionProvider.syncChannels(maxChannels: channelsToSync);
|
||||
debugPrint('✅ [AppProvider] Channel sync complete');
|
||||
|
||||
@@ -2220,7 +2193,7 @@ class AppProvider with ChangeNotifier {
|
||||
await connectionProvider.getContacts();
|
||||
|
||||
// Sync channels (respect simple mode settings)
|
||||
final channelsToSync = _isSimpleMode ? 5 : null;
|
||||
const channelsToSync = 5;
|
||||
await connectionProvider.syncChannels(maxChannels: channelsToSync);
|
||||
|
||||
// Messages are automatically synced via PUSH_CODE_MSG_WAITING events
|
||||
|
||||
@@ -27,7 +27,6 @@ import '../widgets/map_debug_info.dart';
|
||||
import '../widgets/map/compass_widget.dart';
|
||||
import '../widgets/map/detailed_compass_dialog.dart';
|
||||
import '../widgets/map/drawing_layer.dart';
|
||||
import '../widgets/map/drawing_toolbar.dart';
|
||||
import '../widgets/map/location_trail_layer.dart';
|
||||
import '../widgets/map/trail_controls.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(
|
||||
BuildContext context,
|
||||
List<Contact> contacts,
|
||||
@@ -1190,8 +1117,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context); // Required for AutomaticKeepAliveClientMixin
|
||||
final appProvider = context.watch<AppProvider>();
|
||||
final isSimpleMode = appProvider.isSimpleMode;
|
||||
|
||||
return Consumer3<ContactsProvider, MessagesProvider, DrawingProvider>(
|
||||
builder: (context, contactsProvider, messagesProvider, drawingProvider, child) {
|
||||
@@ -1210,8 +1135,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
onPointerMove: (PointerMoveEvent event) {
|
||||
// Track pointer movement for mobile drag (onPointerHover doesn't work on mobile)
|
||||
if (_isDraggingPin) {
|
||||
final latLng = _mapController.camera
|
||||
.screenOffsetToLatLng(event.localPosition);
|
||||
final latLng = _mapController.camera.screenOffsetToLatLng(
|
||||
event.localPosition,
|
||||
);
|
||||
setState(() {
|
||||
_droppedPinLocation = latLng;
|
||||
});
|
||||
@@ -1226,8 +1152,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
initialCenter: _savedMapCenter ?? center,
|
||||
initialZoom: _savedMapZoom ?? _defaultZoom,
|
||||
minZoom: 0, // Allow full zoom out to see world view
|
||||
maxZoom: _currentLayer
|
||||
.maxZoom, // Respect current layer's maximum
|
||||
maxZoom:
|
||||
_currentLayer.maxZoom, // Respect current layer's maximum
|
||||
interactionOptions: InteractionOptions(
|
||||
flags: _isDraggingPin
|
||||
? InteractiveFlag
|
||||
@@ -1248,13 +1174,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
},
|
||||
onLongPress: (tapPosition, point) {
|
||||
// Handle measurement mode - set measurement points only, no SAR marker
|
||||
if (drawingProvider.drawingMode ==
|
||||
DrawingMode.measure) {
|
||||
if (drawingProvider.drawingMode == DrawingMode.measure) {
|
||||
if (drawingProvider.measurementPoint1 == null) {
|
||||
// Set first measurement point
|
||||
drawingProvider.setMeasurementPoint1(point);
|
||||
} else if (drawingProvider.measurementPoint2 ==
|
||||
null) {
|
||||
} else if (drawingProvider.measurementPoint2 == null) {
|
||||
// Set second measurement point
|
||||
drawingProvider.setMeasurementPoint2(point);
|
||||
} else {
|
||||
@@ -1295,8 +1219,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
},
|
||||
onPointerHover: (event, point) {
|
||||
// Update rectangle preview while dragging
|
||||
if (drawingProvider.drawingMode ==
|
||||
DrawingMode.rectangle &&
|
||||
if (drawingProvider.drawingMode == DrawingMode.rectangle &&
|
||||
drawingProvider.rectangleStartPoint != null) {
|
||||
drawingProvider.updateRectangleEndPoint(point);
|
||||
return;
|
||||
@@ -1370,8 +1293,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
layers: _currentLayer.wmsLayers ?? [],
|
||||
styles: _currentLayer.wmsStyles ?? [],
|
||||
format: _currentLayer.wmsFormat ?? 'image/jpeg',
|
||||
transparent:
|
||||
_currentLayer.wmsTransparent ?? false,
|
||||
transparent: _currentLayer.wmsTransparent ?? false,
|
||||
crs: _currentLayer.crs!,
|
||||
),
|
||||
// Use cached tile provider for offline support
|
||||
@@ -1435,8 +1357,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:gozdne_ceste'],
|
||||
styles: const ['gozdne_ceste'],
|
||||
format: 'image/png',
|
||||
@@ -1466,8 +1387,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G',
|
||||
],
|
||||
@@ -1495,11 +1415,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:KGI_LINIJE_CESTE_G',
|
||||
],
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:KGI_LINIJE_CESTE_G'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
@@ -1524,11 +1441,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:NEP_HISNE_STEVILKE',
|
||||
],
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:NEP_HISNE_STEVILKE'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
@@ -1553,11 +1467,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:pozarna_ogrozenost',
|
||||
],
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:pozarna_ogrozenost'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
@@ -1582,8 +1493,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:gozdni_pozari'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
@@ -1609,11 +1519,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:protipozarne_preseke',
|
||||
],
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:protipozarne_preseke'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
@@ -1638,8 +1545,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:pozarisce_kras'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
@@ -1665,11 +1571,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const [
|
||||
'pregledovalnik:zemljepisna_imena',
|
||||
],
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:zemljepisna_imena'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
@@ -1694,8 +1597,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl:
|
||||
'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:NEP_RPE_OBCINE'],
|
||||
styles: const ['obcine'],
|
||||
format: 'image/png',
|
||||
@@ -1724,18 +1626,13 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
return PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(
|
||||
points:
|
||||
mapProvider.importedTrail!.latLngPoints,
|
||||
points: mapProvider.importedTrail!.latLngPoints,
|
||||
color: Colors.green.withValues(alpha: 0.7),
|
||||
strokeWidth: 3.0,
|
||||
borderColor: Colors.white.withValues(
|
||||
alpha: 0.4,
|
||||
),
|
||||
borderColor: Colors.white.withValues(alpha: 0.4),
|
||||
borderStrokeWidth: 1.0,
|
||||
// DOTTED pattern to distinguish from other trails
|
||||
pattern: StrokePattern.dotted(
|
||||
spacingFactor: 2,
|
||||
),
|
||||
pattern: StrokePattern.dotted(spacingFactor: 2),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -1745,12 +1642,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
// Determine which contacts to show trails for
|
||||
final contactsToShow =
|
||||
mapProvider.showAllContactTrails
|
||||
final contactsToShow = mapProvider.showAllContactTrails
|
||||
? contactsWithLocation // Show all when master toggle is ON
|
||||
: contactsWithLocation.where(
|
||||
(contact) =>
|
||||
mapProvider.isContactPathVisible(
|
||||
(contact) => mapProvider.isContactPathVisible(
|
||||
contact.publicKeyHex,
|
||||
),
|
||||
); // Individual toggles
|
||||
@@ -1758,13 +1653,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
return PolylineLayer(
|
||||
polylines: contactsToShow
|
||||
.where(
|
||||
(contact) =>
|
||||
contact.advertHistory.length >= 2,
|
||||
(contact) => contact.advertHistory.length >= 2,
|
||||
)
|
||||
.map((contact) {
|
||||
// Use TrailColorService for consistent, emoji-based colors
|
||||
final color =
|
||||
TrailColorService.getTrailColor(
|
||||
final color = TrailColorService.getTrailColor(
|
||||
contact,
|
||||
);
|
||||
|
||||
@@ -1782,9 +1675,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
), // Stronger border contrast
|
||||
borderStrokeWidth: 2.0, // Wider border
|
||||
// DASHED pattern to distinguish from solid user trail
|
||||
pattern: StrokePattern.dashed(
|
||||
segments: [8, 4],
|
||||
),
|
||||
pattern: StrokePattern.dashed(segments: [8, 4]),
|
||||
);
|
||||
})
|
||||
.toList(),
|
||||
@@ -1805,13 +1696,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
],
|
||||
color: Colors.yellow.withValues(alpha: 0.8),
|
||||
strokeWidth: 3.0,
|
||||
borderColor: Colors.black.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
borderColor: Colors.black.withValues(alpha: 0.5),
|
||||
borderStrokeWidth: 1.0,
|
||||
pattern: StrokePattern.dashed(
|
||||
segments: [10, 5],
|
||||
),
|
||||
pattern: StrokePattern.dashed(segments: [10, 5]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1819,7 +1706,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
DrawingLayer(
|
||||
drawings: drawingProvider.drawings,
|
||||
previewDrawing: drawingProvider.getPreviewDrawing(),
|
||||
isSimpleMode: isSimpleMode,
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
@@ -2008,9 +1894,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
color: _isDraggingPin
|
||||
? Colors.orange
|
||||
: Colors.red,
|
||||
borderRadius: BorderRadius.circular(
|
||||
8,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(
|
||||
@@ -2063,16 +1947,13 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
DrawingMarkersLayer(
|
||||
drawings: drawingProvider.drawings,
|
||||
showDeleteButtons: drawingProvider.isDrawing,
|
||||
isSimpleMode: isSimpleMode,
|
||||
onDeleteDrawing: (drawingId) {
|
||||
drawingProvider.removeDrawing(drawingId);
|
||||
},
|
||||
onTapDrawing: (drawing) {
|
||||
// Navigate to the corresponding message in Messages tab
|
||||
if (drawing.messageId != null) {
|
||||
messagesProvider.navigateToMessage(
|
||||
drawing.messageId!,
|
||||
);
|
||||
messagesProvider.navigateToMessage(drawing.messageId!);
|
||||
widget.onNavigateToMessages?.call();
|
||||
}
|
||||
},
|
||||
@@ -2250,15 +2131,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
right: 16,
|
||||
child: Column(
|
||||
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
|
||||
if (!drawingProvider.isDrawing) ...[
|
||||
// Current Location - always center to GPS
|
||||
@@ -2351,49 +2223,29 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
),
|
||||
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(
|
||||
heroTag: 'ruler_tool',
|
||||
backgroundColor:
|
||||
drawingProvider.drawingMode ==
|
||||
DrawingMode.measure
|
||||
drawingProvider.drawingMode == DrawingMode.measure
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
onPressed: () {
|
||||
if (drawingProvider.drawingMode ==
|
||||
DrawingMode.measure) {
|
||||
// Exit measurement mode
|
||||
drawingProvider.exitDrawingMode();
|
||||
} else {
|
||||
// Enter measurement mode
|
||||
drawingProvider.setDrawingMode(
|
||||
DrawingMode.measure,
|
||||
);
|
||||
drawingProvider.setDrawingMode(DrawingMode.measure);
|
||||
}
|
||||
},
|
||||
child: Icon(
|
||||
Icons.straighten,
|
||||
color:
|
||||
drawingProvider.drawingMode ==
|
||||
DrawingMode.measure
|
||||
drawingProvider.drawingMode == DrawingMode.measure
|
||||
? Colors.white
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (!drawingProvider.isDrawing)
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!drawingProvider.isDrawing) const SizedBox(height: 8),
|
||||
// Continue with other buttons when not in drawing mode
|
||||
if (!drawingProvider.isDrawing) ...[
|
||||
// Trail controls button
|
||||
@@ -2405,9 +2257,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
child: const Icon(Icons.layers),
|
||||
),
|
||||
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(
|
||||
heroTag: 'fullscreen_toggle',
|
||||
onPressed: () {
|
||||
@@ -2422,12 +2271,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
? Icons.fullscreen_exit
|
||||
: Icons.fullscreen,
|
||||
),
|
||||
)
|
||||
else
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'options_menu',
|
||||
onPressed: () => _showOptionsMenu(context),
|
||||
child: const Icon(Icons.more_vert),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1436,10 +1436,6 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
// Get all recent messages
|
||||
final allMessages = messagesProvider.getRecentMessages(count: 100);
|
||||
|
||||
// Get simple mode setting from AppProvider
|
||||
final appProvider = context.read<AppProvider>();
|
||||
final isSimpleMode = appProvider.isSimpleMode;
|
||||
|
||||
List<Message> filteredMessages;
|
||||
|
||||
// If channel destination is selected, filter by selected channel.
|
||||
@@ -1492,16 +1488,11 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
filteredMessages = allMessages;
|
||||
}
|
||||
|
||||
// In simple mode, filter out system messages (toast logs)
|
||||
if (isSimpleMode) {
|
||||
filteredMessages = filteredMessages
|
||||
return filteredMessages
|
||||
.where((message) => !message.isSystemMessage)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return filteredMessages;
|
||||
}
|
||||
|
||||
void _handleMessageTap(Message message) {
|
||||
if (widget.onNavigateToMap == null) return;
|
||||
|
||||
|
||||
@@ -864,19 +864,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
|
||||
_buildSectionHeader('Navigation'),
|
||||
_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>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.map_outlined),
|
||||
|
||||
@@ -15,7 +15,7 @@ class WelcomeWizardScreen extends StatefulWidget {
|
||||
class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
final PageController _pageController = PageController();
|
||||
int _currentPage = 0;
|
||||
static const int _totalPages = 6;
|
||||
static const int _totalPages = 5;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -99,7 +99,6 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
children: [
|
||||
_buildWelcomePage(context, l10n, colorScheme),
|
||||
_buildConnectingPage(context, l10n, colorScheme),
|
||||
_buildSimpleModePage(context, l10n, colorScheme),
|
||||
_buildChannelPage(context, l10n, colorScheme),
|
||||
_buildContactsPage(context, l10n, colorScheme),
|
||||
_buildMapPage(context, l10n, colorScheme),
|
||||
@@ -182,42 +181,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
title: l10n.wizardConnectingTitle,
|
||||
description: l10n.wizardConnectingDescription,
|
||||
features: [
|
||||
_FeatureItem(
|
||||
icon: Icons.radio,
|
||||
text: l10n.wizardConnectingFeature1,
|
||||
),
|
||||
_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,
|
||||
),
|
||||
_FeatureItem(icon: Icons.radio, text: l10n.wizardConnectingFeature1),
|
||||
_FeatureItem(icon: Icons.link, text: l10n.wizardConnectingFeature2),
|
||||
_FeatureItem(icon: Icons.wifi_off, text: l10n.wizardConnectingFeature3),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -234,18 +200,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
title: l10n.wizardChannelTitle,
|
||||
description: l10n.wizardChannelDescription,
|
||||
features: [
|
||||
_FeatureItem(
|
||||
icon: Icons.public,
|
||||
text: l10n.wizardChannelFeature1,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.groups,
|
||||
text: l10n.wizardChannelFeature2,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.send,
|
||||
text: l10n.wizardChannelFeature3,
|
||||
),
|
||||
_FeatureItem(icon: Icons.public, text: l10n.wizardChannelFeature1),
|
||||
_FeatureItem(icon: Icons.groups, text: l10n.wizardChannelFeature2),
|
||||
_FeatureItem(icon: Icons.send, text: l10n.wizardChannelFeature3),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -262,14 +219,8 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
title: l10n.wizardContactsTitle,
|
||||
description: l10n.wizardContactsDescription,
|
||||
features: [
|
||||
_FeatureItem(
|
||||
icon: Icons.person_add,
|
||||
text: l10n.wizardContactsFeature1,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.chat,
|
||||
text: l10n.wizardContactsFeature2,
|
||||
),
|
||||
_FeatureItem(icon: Icons.person_add, text: l10n.wizardContactsFeature1),
|
||||
_FeatureItem(icon: Icons.chat, text: l10n.wizardContactsFeature2),
|
||||
_FeatureItem(
|
||||
icon: Icons.battery_std,
|
||||
text: l10n.wizardContactsFeature3,
|
||||
@@ -290,22 +241,13 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
title: l10n.wizardMapTitle,
|
||||
description: l10n.wizardMapDescription,
|
||||
features: [
|
||||
_FeatureItem(
|
||||
icon: Icons.location_on,
|
||||
text: l10n.wizardMapFeature1,
|
||||
),
|
||||
_FeatureItem(icon: Icons.location_on, text: l10n.wizardMapFeature1),
|
||||
_FeatureItem(
|
||||
icon: Icons.person_pin_circle,
|
||||
text: l10n.wizardMapFeature2,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.offline_pin,
|
||||
text: l10n.wizardMapFeature3,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.draw,
|
||||
text: l10n.wizardMapFeature4,
|
||||
),
|
||||
_FeatureItem(icon: Icons.offline_pin, text: l10n.wizardMapFeature3),
|
||||
_FeatureItem(icon: Icons.draw, text: l10n.wizardMapFeature4),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -332,11 +274,7 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
color: iconColor.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 80,
|
||||
color: iconColor,
|
||||
),
|
||||
child: Icon(icon, size: 80, color: iconColor),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Title
|
||||
@@ -361,28 +299,25 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
if (features != null && features.isNotEmpty) ...[
|
||||
const SizedBox(height: 32),
|
||||
// Features list
|
||||
...features.map((feature) => Padding(
|
||||
...features.map(
|
||||
(feature) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
feature.icon,
|
||||
color: colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
Icon(feature.icon, color: colorScheme.primary, size: 24),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
feature.text,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
@@ -8,13 +8,11 @@ import '../../models/room_login_state.dart';
|
||||
import '../../providers/connection_provider.dart';
|
||||
import '../../providers/contacts_provider.dart';
|
||||
import '../../providers/map_provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import 'contact_route_dialog.dart';
|
||||
import 'room_login_sheet.dart';
|
||||
import '../common/contact_avatar.dart';
|
||||
import '../../utils/location_formats.dart';
|
||||
import '../../utils/toast_logger.dart';
|
||||
import '../../utils/battery_display_helper.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
class ContactTile extends StatelessWidget {
|
||||
@@ -46,12 +44,6 @@ class ContactTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
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;
|
||||
// Calculate distance if both positions are available
|
||||
String? distanceText;
|
||||
@@ -102,13 +94,11 @@ class ContactTile extends StatelessWidget {
|
||||
)
|
||||
: null;
|
||||
void handleTap() {
|
||||
if (isSimpleMode && contact.type == ContactType.chat) {
|
||||
if (contact.type == ContactType.chat) {
|
||||
_showSetRouteDialog(context, contact);
|
||||
} else if (isSimpleMode && contact.type == ContactType.repeater) {
|
||||
} else if (contact.type == ContactType.repeater) {
|
||||
_jumpToMapForRepeater(context, contact);
|
||||
} else if (isSimpleMode &&
|
||||
contact.type == ContactType.room &&
|
||||
!contact.isPublicChannel) {
|
||||
} else if (contact.type == ContactType.room && !contact.isPublicChannel) {
|
||||
_showRoomLoginDialog(context, contact);
|
||||
} else {
|
||||
_showContactDetails(context, contact);
|
||||
@@ -155,8 +145,7 @@ class ContactTile extends StatelessWidget {
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
final subtitleWidget = isSimpleMode
|
||||
? Column(
|
||||
final subtitleWidget = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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(
|
||||
@@ -383,10 +269,6 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(timeAgoText, style: timeAgoStyle),
|
||||
if (!isSimpleMode && battery != null) ...[
|
||||
const SizedBox(width: 6),
|
||||
_buildBatteryBadge(context, battery),
|
||||
],
|
||||
if (isPingInProgress) ...[
|
||||
const SizedBox(width: 6),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,8 @@ import '../../l10n/app_localizations.dart';
|
||||
class DrawingLayer extends StatelessWidget {
|
||||
final List<MapDrawing> drawings;
|
||||
final MapDrawing? previewDrawing;
|
||||
final bool isSimpleMode;
|
||||
|
||||
const DrawingLayer({
|
||||
super.key,
|
||||
required this.drawings,
|
||||
this.previewDrawing,
|
||||
this.isSimpleMode = false,
|
||||
});
|
||||
const DrawingLayer({super.key, required this.drawings, this.previewDrawing});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -48,8 +42,7 @@ class DrawingLayer extends StatelessWidget {
|
||||
strokeWidth = 4.0;
|
||||
} else if (drawing.isReceived) {
|
||||
// Received drawing from another node
|
||||
// In simple mode: solid (opacity 1.0), in normal mode: translucent (0.7)
|
||||
opacity = isSimpleMode ? 1.0 : 0.7;
|
||||
opacity = 1.0;
|
||||
strokeWidth = 3.0;
|
||||
} else {
|
||||
// Local drawing (solid line, normal thickness)
|
||||
@@ -87,7 +80,6 @@ class DrawingMarkersLayer extends StatelessWidget {
|
||||
final Function(String drawingId)? onDeleteDrawing;
|
||||
final Function(MapDrawing drawing)? onTapDrawing;
|
||||
final bool showDeleteButtons;
|
||||
final bool isSimpleMode;
|
||||
|
||||
const DrawingMarkersLayer({
|
||||
super.key,
|
||||
@@ -95,7 +87,6 @@ class DrawingMarkersLayer extends StatelessWidget {
|
||||
this.onDeleteDrawing,
|
||||
this.onTapDrawing,
|
||||
this.showDeleteButtons = false,
|
||||
this.isSimpleMode = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -132,73 +123,7 @@ class DrawingMarkersLayer extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
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,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: const Icon(Icons.close, color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -236,9 +161,7 @@ class DrawingMarkersLayer extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.deleteDrawing),
|
||||
content: Text(
|
||||
'Delete this ${drawing.type.name}?',
|
||||
),
|
||||
content: Text('Delete this ${drawing.type.name}?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
@@ -249,9 +172,7 @@ class DrawingMarkersLayer extends StatelessWidget {
|
||||
Navigator.pop(context);
|
||||
onDeleteDrawing?.call(drawing.id);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: Text(AppLocalizations.of(context)!.delete),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/map_provider.dart';
|
||||
import '../../providers/contacts_provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../services/gpx_service.dart';
|
||||
import '../../services/trail_color_service.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
@@ -13,10 +11,11 @@ class TrailControls extends StatelessWidget {
|
||||
|
||||
void _showTrailMenu(BuildContext context) {
|
||||
final mapProvider = Provider.of<MapProvider>(context, listen: false);
|
||||
final contactsProvider = Provider.of<ContactsProvider>(context, listen: false);
|
||||
final appProvider = Provider.of<AppProvider>(context, listen: false);
|
||||
final contactsProvider = Provider.of<ContactsProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final isSimpleMode = appProvider.isSimpleMode;
|
||||
|
||||
// Get contacts with trails (advertHistory >= 2 points)
|
||||
final contactsWithTrails = contactsProvider.contactsWithLocation
|
||||
@@ -68,13 +67,16 @@ class TrailControls extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Trail stats
|
||||
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty)
|
||||
if (mapProvider.currentTrail != null &&
|
||||
mapProvider.currentTrail!.points.isNotEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: 0.1),
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -82,7 +84,9 @@ class TrailControls extends StatelessWidget {
|
||||
_buildStatRow(
|
||||
icon: Icons.straighten,
|
||||
label: l10n.distance,
|
||||
value: _formatDistance(mapProvider.totalTrailDistance),
|
||||
value: _formatDistance(
|
||||
mapProvider.totalTrailDistance,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStatRow(
|
||||
@@ -102,62 +106,9 @@ class TrailControls extends StatelessWidget {
|
||||
|
||||
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
|
||||
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty)
|
||||
if (mapProvider.currentTrail != null &&
|
||||
mapProvider.currentTrail!.points.isNotEmpty)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showClearConfirmation(context, mapProvider, l10n);
|
||||
@@ -172,13 +123,18 @@ class TrailControls extends StatelessWidget {
|
||||
),
|
||||
|
||||
// No trail message
|
||||
if (mapProvider.currentTrail == null || mapProvider.currentTrail!.points.isEmpty)
|
||||
if (mapProvider.currentTrail == null ||
|
||||
mapProvider.currentTrail!.points.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.timeline, size: 48, color: Colors.grey),
|
||||
const Icon(
|
||||
Icons.timeline,
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.noTrailRecorded,
|
||||
@@ -225,11 +181,15 @@ class TrailControls extends StatelessWidget {
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.route),
|
||||
title: Text(l10n.showAllContactTrails),
|
||||
subtitle: Text(contactsWithTrails.isEmpty
|
||||
subtitle: Text(
|
||||
contactsWithTrails.isEmpty
|
||||
? l10n.noContactsWithLocationHistory
|
||||
: mapProvider.showAllContactTrails
|
||||
? l10n.showingTrailsForContacts(contactsWithTrails.length)
|
||||
: l10n.individualContactTrails),
|
||||
? l10n.showingTrailsForContacts(
|
||||
contactsWithTrails.length,
|
||||
)
|
||||
: l10n.individualContactTrails,
|
||||
),
|
||||
value: mapProvider.showAllContactTrails,
|
||||
onChanged: contactsWithTrails.isNotEmpty
|
||||
? (value) {
|
||||
@@ -240,13 +200,18 @@ class TrailControls extends StatelessWidget {
|
||||
),
|
||||
|
||||
// Individual contact trails (when "show all" is OFF)
|
||||
if (!mapProvider.showAllContactTrails && contactsWithTrails.isNotEmpty)
|
||||
if (!mapProvider.showAllContactTrails &&
|
||||
contactsWithTrails.isNotEmpty)
|
||||
ExpansionTile(
|
||||
title: Text(l10n.individualContactTrails),
|
||||
initiallyExpanded: false,
|
||||
children: contactsWithTrails.map((contact) {
|
||||
final trailColor = TrailColorService.getTrailColor(contact);
|
||||
final isVisible = mapProvider.isContactPathVisible(contact.publicKeyHex);
|
||||
final trailColor = TrailColorService.getTrailColor(
|
||||
contact,
|
||||
);
|
||||
final isVisible = mapProvider.isContactPathVisible(
|
||||
contact.publicKeyHex,
|
||||
);
|
||||
|
||||
return SwitchListTile(
|
||||
// Color indicator with emoji
|
||||
@@ -254,21 +219,29 @@ class TrailControls extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contact.roleEmoji != null)
|
||||
Text(contact.roleEmoji!, style: const TextStyle(fontSize: 18)),
|
||||
Text(
|
||||
contact.roleEmoji!,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: trailColor,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text(contact.displayName),
|
||||
subtitle: Text('${contact.advertHistory.length} points'),
|
||||
subtitle: Text(
|
||||
'${contact.advertHistory.length} points',
|
||||
),
|
||||
value: isVisible,
|
||||
onChanged: (value) {
|
||||
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(
|
||||
context: context,
|
||||
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({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
@@ -373,18 +306,12 @@ class TrailControls extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user