mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Update color handling in map drawings and enhance message sync functionality
This commit is contained in:
@@ -100,7 +100,7 @@ class LineDrawing extends MapDrawing {
|
|||||||
|
|
||||||
return LineDrawing(
|
return LineDrawing(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
color: Color.fromARGB32(json['color'] as int),
|
color: Color(json['color'] as int),
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
points: points,
|
points: points,
|
||||||
);
|
);
|
||||||
@@ -157,7 +157,7 @@ class RectangleDrawing extends MapDrawing {
|
|||||||
|
|
||||||
return RectangleDrawing(
|
return RectangleDrawing(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
color: Color.fromARGB32(json['color'] as int),
|
color: Color(json['color'] as int),
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
topLeft: LatLng(topLeftJson['lat'] as double, topLeftJson['lon'] as double),
|
topLeft: LatLng(topLeftJson['lat'] as double, topLeftJson['lon'] as double),
|
||||||
bottomRight: LatLng(bottomRightJson['lat'] as double, bottomRightJson['lon'] as double),
|
bottomRight: LatLng(bottomRightJson['lat'] as double, bottomRightJson['lon'] as double),
|
||||||
|
|||||||
@@ -158,8 +158,13 @@ class AppProvider with ChangeNotifier {
|
|||||||
// Automatically login to all saved rooms
|
// Automatically login to all saved rooms
|
||||||
await _autoLoginToRooms();
|
await _autoLoginToRooms();
|
||||||
|
|
||||||
// Note: Messages are synced automatically via PUSH_CODE_MSG_WAITING events
|
// FALLBACK: Sync messages once after connection to catch any missed push notifications
|
||||||
// No need to manually sync here - the BLE service handles this via callbacks
|
// This handles the case where messages arrived while the app was disconnected
|
||||||
|
debugPrint('🔄 [AppProvider] Performing initial message sync (fallback for missed pushes)');
|
||||||
|
final initialMessageCount = await connectionProvider.syncAllMessages();
|
||||||
|
debugPrint('📥 [AppProvider] Initial sync retrieved $initialMessageCount message(s)');
|
||||||
|
|
||||||
|
// Note: Future messages are synced automatically via PUSH_CODE_MSG_WAITING events
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
|
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
bool _isMapFullscreen = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -40,6 +41,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
_tabController.addListener(() {
|
_tabController.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentIndex = _tabController.index;
|
_currentIndex = _tabController.index;
|
||||||
|
// Exit fullscreen when switching away from map tab
|
||||||
|
if (_currentIndex != 2) {
|
||||||
|
_isMapFullscreen = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -308,8 +313,11 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Determine if we should hide the UI (only in fullscreen on map tab)
|
||||||
|
final shouldHideUI = _isMapFullscreen && _currentIndex == 2;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: shouldHideUI ? null : AppBar(
|
||||||
title: _buildCompactStatusBar(),
|
title: _buildCompactStatusBar(),
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
@@ -368,10 +376,16 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
children: [
|
children: [
|
||||||
MessagesTab(onNavigateToMap: () => _tabController.animateTo(2)),
|
MessagesTab(onNavigateToMap: () => _tabController.animateTo(2)),
|
||||||
const ContactsTab(),
|
const ContactsTab(),
|
||||||
const MapTab(),
|
MapTab(
|
||||||
|
onFullscreenChanged: (isFullscreen) {
|
||||||
|
setState(() {
|
||||||
|
_isMapFullscreen = isFullscreen;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: Consumer2<MessagesProvider, ContactsProvider>(
|
bottomNavigationBar: shouldHideUI ? null : Consumer2<MessagesProvider, ContactsProvider>(
|
||||||
builder: (context, messagesProvider, contactsProvider, child) {
|
builder: (context, messagesProvider, contactsProvider, child) {
|
||||||
final unreadCount = messagesProvider.unreadCount;
|
final unreadCount = messagesProvider.unreadCount;
|
||||||
final newContactsCount = contactsProvider.newContactsCount;
|
final newContactsCount = contactsProvider.newContactsCount;
|
||||||
@@ -512,47 +526,58 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// RX indicator
|
// RX indicator
|
||||||
Container(
|
Row(
|
||||||
width: 8,
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 8,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
shape: BoxShape.circle,
|
width: 8,
|
||||||
color: provider.rxActivity
|
height: 8,
|
||||||
? Colors.green
|
decoration: BoxDecoration(
|
||||||
: Colors.grey.withOpacity(0.3),
|
shape: BoxShape.circle,
|
||||||
),
|
color: provider.rxActivity
|
||||||
|
? Colors.green
|
||||||
|
: Colors.grey.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'RX:${provider.rxPacketCount}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
|
||||||
'RX:${provider.rxPacketCount}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
// TX indicator
|
// TX indicator
|
||||||
Container(
|
Row(
|
||||||
width: 8,
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 8,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
shape: BoxShape.circle,
|
width: 8,
|
||||||
color: provider.txActivity
|
height: 8,
|
||||||
? Colors.blue
|
decoration: BoxDecoration(
|
||||||
: Colors.grey.withOpacity(0.3),
|
shape: BoxShape.circle,
|
||||||
),
|
color: provider.txActivity
|
||||||
),
|
? Colors.blue
|
||||||
const SizedBox(width: 4),
|
: Colors.grey.withOpacity(0.3),
|
||||||
Text(
|
),
|
||||||
'TX:${provider.txPacketCount}',
|
),
|
||||||
style: const TextStyle(
|
const SizedBox(width: 4),
|
||||||
fontSize: 11,
|
Text(
|
||||||
color: Colors.grey,
|
'TX:${provider.txPacketCount}',
|
||||||
),
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ import '../widgets/messages/sar_update_sheet.dart';
|
|||||||
import 'map_management_screen.dart';
|
import 'map_management_screen.dart';
|
||||||
|
|
||||||
class MapTab extends StatefulWidget {
|
class MapTab extends StatefulWidget {
|
||||||
const MapTab({super.key});
|
final Function(bool)? onFullscreenChanged;
|
||||||
|
|
||||||
|
const MapTab({
|
||||||
|
super.key,
|
||||||
|
this.onFullscreenChanged,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MapTab> createState() => _MapTabState();
|
State<MapTab> createState() => _MapTabState();
|
||||||
@@ -49,6 +54,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
bool _rotateMarkerWithHeading = false; // Toggle for rotation
|
bool _rotateMarkerWithHeading = false; // Toggle for rotation
|
||||||
bool _showLegend = false;
|
bool _showLegend = false;
|
||||||
bool _showMapDebugInfo = false; // Toggle for debug info
|
bool _showMapDebugInfo = false; // Toggle for debug info
|
||||||
|
bool _isFullscreen = false; // Toggle for fullscreen mode
|
||||||
double _gpsUpdateDistance = 3.0; // meters
|
double _gpsUpdateDistance = 3.0; // meters
|
||||||
bool _backgroundTrackingEnabled = false; // Toggle for background tracking
|
bool _backgroundTrackingEnabled = false; // Toggle for background tracking
|
||||||
StreamSubscription<CompassEvent>? _compassStreamSubscription;
|
StreamSubscription<CompassEvent>? _compassStreamSubscription;
|
||||||
@@ -180,6 +186,12 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
_showLegend = prefs.getBool('map_show_legend') ?? false;
|
_showLegend = prefs.getBool('map_show_legend') ?? false;
|
||||||
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
||||||
_showMapDebugInfo = prefs.getBool('map_show_debug_info') ?? false;
|
_showMapDebugInfo = prefs.getBool('map_show_debug_info') ?? false;
|
||||||
|
_isFullscreen = prefs.getBool('map_fullscreen') ?? false;
|
||||||
|
|
||||||
|
// Notify parent about initial fullscreen state
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
widget.onFullscreenChanged?.call(_isFullscreen);
|
||||||
|
});
|
||||||
_gpsUpdateDistance = prefs.getDouble('map_gps_update_distance') ?? 3.0;
|
_gpsUpdateDistance = prefs.getDouble('map_gps_update_distance') ?? 3.0;
|
||||||
_backgroundTrackingEnabled = prefs.getBool('background_tracking_enabled') ?? false;
|
_backgroundTrackingEnabled = prefs.getBool('background_tracking_enabled') ?? false;
|
||||||
|
|
||||||
@@ -202,6 +214,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
await prefs.setBool('map_show_legend', _showLegend);
|
await prefs.setBool('map_show_legend', _showLegend);
|
||||||
await prefs.setBool('map_rotate_with_heading', _rotateMarkerWithHeading);
|
await prefs.setBool('map_rotate_with_heading', _rotateMarkerWithHeading);
|
||||||
await prefs.setBool('map_show_debug_info', _showMapDebugInfo);
|
await prefs.setBool('map_show_debug_info', _showMapDebugInfo);
|
||||||
|
await prefs.setBool('map_fullscreen', _isFullscreen);
|
||||||
await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance);
|
await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance);
|
||||||
await prefs.setBool('background_tracking_enabled', _backgroundTrackingEnabled);
|
await prefs.setBool('background_tracking_enabled', _backgroundTrackingEnabled);
|
||||||
await prefs.setInt('map_last_layer', MapLayer.allLayers.indexOf(_currentLayer));
|
await prefs.setInt('map_last_layer', MapLayer.allLayers.indexOf(_currentLayer));
|
||||||
@@ -499,6 +512,23 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
_saveSettings();
|
_saveSettings();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const Divider(),
|
||||||
|
// Fullscreen mode toggle
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(Icons.fullscreen),
|
||||||
|
title: const Text('Fullscreen Mode'),
|
||||||
|
subtitle: const Text('Hide all UI controls for full map view'),
|
||||||
|
value: _isFullscreen,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isFullscreen = value;
|
||||||
|
});
|
||||||
|
setModalState(() {});
|
||||||
|
_saveSettings();
|
||||||
|
// Notify parent about fullscreen change
|
||||||
|
widget.onFullscreenChanged?.call(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1060,9 +1090,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Drawing markers layer (delete buttons on drawings)
|
// Drawing markers layer (delete buttons on drawings, only shown when in drawing mode)
|
||||||
DrawingMarkersLayer(
|
DrawingMarkersLayer(
|
||||||
drawings: drawingProvider.drawings,
|
drawings: drawingProvider.drawings,
|
||||||
|
showDeleteButtons: drawingProvider.isDrawing,
|
||||||
onDeleteDrawing: (drawingId) {
|
onDeleteDrawing: (drawingId) {
|
||||||
drawingProvider.removeDrawing(drawingId);
|
drawingProvider.removeDrawing(drawingId);
|
||||||
},
|
},
|
||||||
@@ -1083,26 +1114,46 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Compass widget - top right (always visible)
|
// Exit fullscreen button - top left (only shown in fullscreen mode)
|
||||||
Positioned(
|
if (_isFullscreen)
|
||||||
top: 16,
|
Positioned(
|
||||||
right: 16,
|
top: 16,
|
||||||
child: GestureDetector(
|
left: 16,
|
||||||
onTap: () => _showDetailedCompass(
|
child: FloatingActionButton.small(
|
||||||
context,
|
heroTag: 'exit_fullscreen',
|
||||||
contactsProvider.contactsWithLocation,
|
onPressed: () {
|
||||||
messagesProvider.sarMarkers,
|
setState(() {
|
||||||
),
|
_isFullscreen = false;
|
||||||
child: CompassWidget(
|
});
|
||||||
heading: _currentHeading ?? 0,
|
_saveSettings();
|
||||||
hasHeading: _currentHeading != null,
|
// Notify parent about fullscreen change
|
||||||
|
widget.onFullscreenChanged?.call(false);
|
||||||
|
},
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.9),
|
||||||
|
child: const Icon(Icons.fullscreen_exit),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// Compass widget - top right (hidden in fullscreen mode)
|
||||||
// Map legend overlay
|
if (!_isFullscreen)
|
||||||
if (_showLegend)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 80, // Position below compass (which is always visible now)
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _showDetailedCompass(
|
||||||
|
context,
|
||||||
|
contactsProvider.contactsWithLocation,
|
||||||
|
messagesProvider.sarMarkers,
|
||||||
|
),
|
||||||
|
child: CompassWidget(
|
||||||
|
heading: _currentHeading ?? 0,
|
||||||
|
hasHeading: _currentHeading != null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Map legend overlay (hidden in fullscreen mode)
|
||||||
|
if (_showLegend && !_isFullscreen)
|
||||||
|
Positioned(
|
||||||
|
top: 80, // Position below compass
|
||||||
left: 16,
|
left: 16,
|
||||||
child: MapLegend(
|
child: MapLegend(
|
||||||
teamMemberCount: contactsWithLocation.length,
|
teamMemberCount: contactsWithLocation.length,
|
||||||
@@ -1112,19 +1163,17 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
objectCount: messagesProvider.objectMarkers.length,
|
objectCount: messagesProvider.objectMarkers.length,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Drawing toolbar - bottom left
|
// Map controls - right side (hidden in fullscreen mode)
|
||||||
Positioned(
|
if (!_isFullscreen)
|
||||||
bottom: 16,
|
Positioned(
|
||||||
left: 16,
|
bottom: 16,
|
||||||
child: const DrawingToolbar(),
|
right: 16,
|
||||||
),
|
child: Column(
|
||||||
// Map controls - right side
|
children: [
|
||||||
Positioned(
|
// Drawing toolbar
|
||||||
bottom: 16,
|
const DrawingToolbar(),
|
||||||
right: 16,
|
const SizedBox(height: 8),
|
||||||
child: Column(
|
FloatingActionButton.small(
|
||||||
children: [
|
|
||||||
FloatingActionButton.small(
|
|
||||||
heroTag: 'center_map',
|
heroTag: 'center_map',
|
||||||
onPressed: !_isMapReady ? null : () async {
|
onPressed: !_isMapReady ? null : () async {
|
||||||
// Force update GPS location and jump to it
|
// Force update GPS location and jump to it
|
||||||
@@ -1162,16 +1211,16 @@ 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),
|
||||||
FloatingActionButton.small(
|
FloatingActionButton.small(
|
||||||
heroTag: 'options_menu',
|
heroTag: 'options_menu',
|
||||||
onPressed: () => _showOptionsMenu(context),
|
onPressed: () => _showOptionsMenu(context),
|
||||||
child: const Icon(Icons.more_vert),
|
child: const Icon(Icons.more_vert),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
// Map debug info - bottom left (hidden in fullscreen mode)
|
||||||
// Map debug info - bottom left
|
if (_showMapDebugInfo && _isMapReady && !_isFullscreen)
|
||||||
if (_showMapDebugInfo && _isMapReady)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
left: 16,
|
left: 16,
|
||||||
|
|||||||
@@ -227,7 +227,34 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Removed _handleRefresh() - messages are synced automatically via PUSH_CODE_MSG_WAITING events
|
/// Handle pull-to-refresh for manual message sync
|
||||||
|
/// This is a FALLBACK mechanism - messages are normally synced automatically via PUSH_CODE_MSG_WAITING
|
||||||
|
Future<void> _handleRefresh() async {
|
||||||
|
final connectionProvider = context.read<ConnectionProvider>();
|
||||||
|
|
||||||
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ToastLogger.warning(context, 'Not connected - cannot sync messages');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
print('🔄 [MessagesTab] Manual refresh triggered - syncing messages');
|
||||||
|
final messageCount = await connectionProvider.syncAllMessages();
|
||||||
|
print('✅ [MessagesTab] Synced $messageCount message(s)');
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
if (messageCount > 0) {
|
||||||
|
ToastLogger.success(context, 'Synced $messageCount message(s)');
|
||||||
|
} else {
|
||||||
|
ToastLogger.info(context, 'No new messages');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('❌ [MessagesTab] Sync error: $e');
|
||||||
|
if (!mounted) return;
|
||||||
|
ToastLogger.error(context, 'Sync failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Message> _getFilteredMessages(MessagesProvider messagesProvider) {
|
List<Message> _getFilteredMessages(MessagesProvider messagesProvider) {
|
||||||
// Show ALL messages regardless of recipient selection
|
// Show ALL messages regardless of recipient selection
|
||||||
@@ -243,33 +270,43 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// Messages list
|
// Messages list with pull-to-refresh
|
||||||
Expanded(
|
Expanded(
|
||||||
child: messages.isEmpty
|
child: RefreshIndicator(
|
||||||
? Center(
|
onRefresh: _handleRefresh,
|
||||||
child: Column(
|
child: messages.isEmpty
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
? LayoutBuilder(
|
||||||
children: [
|
builder: (context, constraints) => SingleChildScrollView(
|
||||||
Icon(
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
Icons.message_outlined,
|
child: ConstrainedBox(
|
||||||
size: 64,
|
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||||
color: Theme.of(context).disabledColor,
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.message_outlined,
|
||||||
|
size: 64,
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'No messages yet',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Pull down to sync messages',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
)
|
||||||
'No messages yet',
|
: ListView.builder(
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Connect to a device to start receiving messages',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
reverse: true,
|
reverse: true,
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
itemCount: messages.length,
|
itemCount: messages.length,
|
||||||
@@ -298,6 +335,7 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Message input area
|
// Message input area
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
import '../../models/map_drawing.dart';
|
import '../../models/map_drawing.dart';
|
||||||
|
|
||||||
/// Widget that renders map drawings as polylines
|
/// Widget that renders map drawings as polylines
|
||||||
@@ -37,9 +38,9 @@ class DrawingLayer extends StatelessWidget {
|
|||||||
|
|
||||||
return Polyline(
|
return Polyline(
|
||||||
points: points,
|
points: points,
|
||||||
color: drawing.color.withOpacity(opacity),
|
color: drawing.color.withValues(alpha: opacity),
|
||||||
strokeWidth: 4.0,
|
strokeWidth: 4.0,
|
||||||
borderColor: Colors.white.withOpacity(opacity * 0.8),
|
borderColor: Colors.white.withValues(alpha: opacity * 0.8),
|
||||||
borderStrokeWidth: 1.0,
|
borderStrokeWidth: 1.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -59,15 +60,22 @@ class DrawingLayer extends StatelessWidget {
|
|||||||
class DrawingMarkersLayer extends StatelessWidget {
|
class DrawingMarkersLayer extends StatelessWidget {
|
||||||
final List<MapDrawing> drawings;
|
final List<MapDrawing> drawings;
|
||||||
final Function(String drawingId)? onDeleteDrawing;
|
final Function(String drawingId)? onDeleteDrawing;
|
||||||
|
final bool showDeleteButtons;
|
||||||
|
|
||||||
const DrawingMarkersLayer({
|
const DrawingMarkersLayer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.drawings,
|
required this.drawings,
|
||||||
this.onDeleteDrawing,
|
this.onDeleteDrawing,
|
||||||
|
this.showDeleteButtons = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Only show delete buttons when showDeleteButtons is true
|
||||||
|
if (!showDeleteButtons) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
final List<Marker> markers = [];
|
final List<Marker> markers = [];
|
||||||
|
|
||||||
// Add delete markers for each drawing (at the center point)
|
// Add delete markers for each drawing (at the center point)
|
||||||
@@ -87,12 +95,12 @@ class DrawingMarkersLayer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: drawing.color.withOpacity(0.9),
|
color: drawing.color.withValues(alpha: 0.9),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.3),
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.2),
|
color: Colors.black.withValues(alpha: 0.2),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -83,7 +83,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
boxShadow: [
|
boxShadow: [
|
||||||
if (isSelected)
|
if (isSelected)
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: color.withOpacity(0.5),
|
color: color.withValues(alpha: 0.5),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user