feat: Enhance messaging and SAR marker functionalities

- Integrated SAR marker sending feature in MessagesTab with a dedicated dialog for user input.
- Updated message sending logic to default to the first available room if no contact is selected.
- Improved user feedback with SnackBars for connection issues and successful marker sends.
- Refactored contact selection to remove unused code and streamline the process.
- Added location fetching capabilities using Geolocator for SAR markers.
- Updated sample data with more relevant team names using emojis.
- Enhanced map markers to include rotation handling and improved display of contact information.
- Introduced color coding for location update age in map markers for better visibility.
This commit is contained in:
Janez T
2025-10-14 01:02:20 +02:00
parent 4ed0991193
commit e2fc9bcccf
9 changed files with 1416 additions and 436 deletions

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:latlong2/latlong.dart';
import 'package:flutter/material.dart';
import 'contact_telemetry.dart';
/// MeshCore contact types
@@ -129,6 +130,67 @@ class Contact {
return '${diff.inDays}d ago';
}
/// Get time when location was last updated
DateTime? get locationUpdateTime {
// Prefer telemetry timestamp if available
if (telemetry?.gpsLocation != null) {
return telemetry!.timestamp;
}
// Fall back to lastAdvert time if using advertised location
if (advertLocation != null) {
return lastSeenTime;
}
return null;
}
/// Get friendly time since location was last updated
String get timeSinceLocationUpdate {
final updateTime = locationUpdateTime;
if (updateTime == null) return 'Unknown';
final diff = DateTime.now().difference(updateTime);
if (diff.inMinutes < 1) return 'Now';
if (diff.inMinutes < 60) return '${diff.inMinutes}m';
if (diff.inHours < 24) return '${diff.inHours}h';
return '${diff.inDays}d';
}
/// Extract role emoji from name (e.g., "🧑🏻🚒Janez" → "🧑🏻‍🚒")
/// Returns null if no emoji at start of name
String? get roleEmoji {
if (advName.isEmpty) return null;
// Get the first character/grapheme cluster (which could be a complex emoji)
final firstChar = advName.characters.first;
// Check if it's an emoji (basic check - emojis are typically in certain Unicode ranges)
final firstCodeUnit = firstChar.runes.first;
// Emoji ranges (simplified check):
// 0x1F300-0x1F9FF: Misc Symbols and Pictographs, Emoticons, Transport, etc.
// 0x2600-0x26FF: Misc symbols
// 0x2700-0x27BF: Dingbats
// 0xFE00-0xFE0F: Variation Selectors
// 0x1F900-0x1F9FF: Supplemental Symbols and Pictographs
if ((firstCodeUnit >= 0x1F300 && firstCodeUnit <= 0x1F9FF) ||
(firstCodeUnit >= 0x2600 && firstCodeUnit <= 0x27BF) ||
(firstCodeUnit >= 0x1F600 && firstCodeUnit <= 0x1F64F)) {
return firstChar;
}
return null;
}
/// Get display name without role emoji (e.g., "🧑🏻🚒Janez" → "Janez")
/// If no emoji, returns full advName
String get displayName {
final emoji = roleEmoji;
if (emoji == null) return advName;
// Remove the emoji from the beginning
return advName.substring(emoji.length).trim();
}
Contact copyWith({
Uint8List? publicKey,
ContactType? type,

View File

@@ -6,6 +6,7 @@ enum SarMarkerType {
foundPerson('🧑', 'Found Person'),
fire('🔥', 'Fire'),
stagingArea('🏕️', 'Staging Area'),
object('📦', 'Object'),
unknown('', 'Unknown');
const SarMarkerType(this.emoji, this.displayName);
@@ -22,6 +23,8 @@ enum SarMarkerType {
case '🏕️':
case '':
return SarMarkerType.stagingArea;
case '📦':
return SarMarkerType.object;
default:
return SarMarkerType.unknown;
}
@@ -36,6 +39,8 @@ enum SarMarkerType {
return '#F44336'; // Red
case SarMarkerType.stagingArea:
return '#2196F3'; // Blue
case SarMarkerType.object:
return '#9C27B0'; // Purple
default:
return '#9E9E9E'; // Gray
}