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 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

View File

@@ -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),
),
],
],

View File

@@ -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;

View File

@@ -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),

View File

@@ -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),
],

View File

@@ -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,
),
),
],
),
);
}
}

View File

@@ -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),
),
],

View File

@@ -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),
),
],
);