feat: Update color handling in map drawings and enhance message sync functionality

This commit is contained in:
Janez T
2025-10-15 22:13:15 +02:00
parent 3651f53ef2
commit 6ee1c9ae79
8 changed files with 241 additions and 117 deletions

View File

@@ -32,6 +32,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
int _currentIndex = 0;
bool _isMapFullscreen = false;
@override
void initState() {
@@ -40,6 +41,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
_tabController.addListener(() {
setState(() {
_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
Widget build(BuildContext context) {
// Determine if we should hide the UI (only in fullscreen on map tab)
final shouldHideUI = _isMapFullscreen && _currentIndex == 2;
return Scaffold(
appBar: AppBar(
appBar: shouldHideUI ? null : AppBar(
title: _buildCompactStatusBar(),
actions: [
PopupMenuButton(
@@ -368,10 +376,16 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
children: [
MessagesTab(onNavigateToMap: () => _tabController.animateTo(2)),
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) {
final unreadCount = messagesProvider.unreadCount;
final newContactsCount = contactsProvider.newContactsCount;
@@ -512,47 +526,58 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
),
);
},
child: Row(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// RX indicator
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: provider.rxActivity
? Colors.green
: Colors.grey.withOpacity(0.3),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
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),
Text(
'RX:${provider.rxPacketCount}',
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
),
const SizedBox(width: 12),
const SizedBox(height: 4),
// TX indicator
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: provider.txActivity
? Colors.blue
: Colors.grey.withOpacity(0.3),
),
),
const SizedBox(width: 4),
Text(
'TX:${provider.txPacketCount}',
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: provider.txActivity
? Colors.blue
: Colors.grey.withOpacity(0.3),
),
),
const SizedBox(width: 4),
Text(
'TX:${provider.txPacketCount}',
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
),
],
),
],
),

View File

@@ -31,7 +31,12 @@ import '../widgets/messages/sar_update_sheet.dart';
import 'map_management_screen.dart';
class MapTab extends StatefulWidget {
const MapTab({super.key});
final Function(bool)? onFullscreenChanged;
const MapTab({
super.key,
this.onFullscreenChanged,
});
@override
State<MapTab> createState() => _MapTabState();
@@ -49,6 +54,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
bool _rotateMarkerWithHeading = false; // Toggle for rotation
bool _showLegend = false;
bool _showMapDebugInfo = false; // Toggle for debug info
bool _isFullscreen = false; // Toggle for fullscreen mode
double _gpsUpdateDistance = 3.0; // meters
bool _backgroundTrackingEnabled = false; // Toggle for background tracking
StreamSubscription<CompassEvent>? _compassStreamSubscription;
@@ -180,6 +186,12 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
_showLegend = prefs.getBool('map_show_legend') ?? false;
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? 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;
_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_rotate_with_heading', _rotateMarkerWithHeading);
await prefs.setBool('map_show_debug_info', _showMapDebugInfo);
await prefs.setBool('map_fullscreen', _isFullscreen);
await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance);
await prefs.setBool('background_tracking_enabled', _backgroundTrackingEnabled);
await prefs.setInt('map_last_layer', MapLayer.allLayers.indexOf(_currentLayer));
@@ -499,6 +512,23 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
_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(
drawings: drawingProvider.drawings,
showDeleteButtons: drawingProvider.isDrawing,
onDeleteDrawing: (drawingId) {
drawingProvider.removeDrawing(drawingId);
},
@@ -1083,26 +1114,46 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
],
),
),
// Compass widget - top right (always visible)
Positioned(
top: 16,
right: 16,
child: GestureDetector(
onTap: () => _showDetailedCompass(
context,
contactsProvider.contactsWithLocation,
messagesProvider.sarMarkers,
),
child: CompassWidget(
heading: _currentHeading ?? 0,
hasHeading: _currentHeading != null,
// Exit fullscreen button - top left (only shown in fullscreen mode)
if (_isFullscreen)
Positioned(
top: 16,
left: 16,
child: FloatingActionButton.small(
heroTag: 'exit_fullscreen',
onPressed: () {
setState(() {
_isFullscreen = false;
});
_saveSettings();
// 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),
),
),
),
// Map legend overlay
if (_showLegend)
// Compass widget - top right (hidden in fullscreen mode)
if (!_isFullscreen)
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,
child: MapLegend(
teamMemberCount: contactsWithLocation.length,
@@ -1112,19 +1163,17 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
objectCount: messagesProvider.objectMarkers.length,
),
),
// Drawing toolbar - bottom left
Positioned(
bottom: 16,
left: 16,
child: const DrawingToolbar(),
),
// Map controls - right side
Positioned(
bottom: 16,
right: 16,
child: Column(
children: [
FloatingActionButton.small(
// Map controls - right side (hidden in fullscreen mode)
if (!_isFullscreen)
Positioned(
bottom: 16,
right: 16,
child: Column(
children: [
// Drawing toolbar
const DrawingToolbar(),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'center_map',
onPressed: !_isMapReady ? null : () async {
// Force update GPS location and jump to it
@@ -1162,16 +1211,16 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
child: const Icon(Icons.layers),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'options_menu',
onPressed: () => _showOptionsMenu(context),
child: const Icon(Icons.more_vert),
),
],
FloatingActionButton.small(
heroTag: 'options_menu',
onPressed: () => _showOptionsMenu(context),
child: const Icon(Icons.more_vert),
),
],
),
),
),
// Map debug info - bottom left
if (_showMapDebugInfo && _isMapReady)
// Map debug info - bottom left (hidden in fullscreen mode)
if (_showMapDebugInfo && _isMapReady && !_isFullscreen)
Positioned(
bottom: 16,
left: 16,

View File

@@ -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) {
// Show ALL messages regardless of recipient selection
@@ -243,33 +270,43 @@ class _MessagesTabState extends State<MessagesTab> {
return Column(
children: [
// Messages list
// Messages list with pull-to-refresh
Expanded(
child: messages.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.message_outlined,
size: 64,
color: Theme.of(context).disabledColor,
child: RefreshIndicator(
onRefresh: _handleRefresh,
child: messages.isEmpty
? LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
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',
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(
),
)
: ListView.builder(
reverse: true,
padding: const EdgeInsets.all(8),
itemCount: messages.length,
@@ -298,6 +335,7 @@ class _MessagesTabState extends State<MessagesTab> {
);
},
),
),
),
// Message input area