mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
172 lines
4.6 KiB
Dart
172 lines
4.6 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:latlong2/latlong.dart';
|
|
import 'sar_marker.dart';
|
|
|
|
/// Message text types from MeshCore protocol
|
|
enum MessageTextType {
|
|
plain(0),
|
|
cliData(1),
|
|
signedPlain(2);
|
|
|
|
const MessageTextType(this.value);
|
|
final int value;
|
|
|
|
static MessageTextType fromValue(int value) {
|
|
return MessageTextType.values.firstWhere(
|
|
(e) => e.value == value,
|
|
orElse: () => MessageTextType.plain,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Message type (contact or channel)
|
|
enum MessageType {
|
|
contact,
|
|
channel,
|
|
}
|
|
|
|
/// MeshCore message model
|
|
class Message {
|
|
final String id;
|
|
final MessageType messageType;
|
|
final Uint8List? senderPublicKeyPrefix; // 6 bytes for contact messages
|
|
final int? channelIdx; // For channel messages
|
|
final int pathLen;
|
|
final MessageTextType textType;
|
|
final int senderTimestamp; // Unix timestamp
|
|
final String text;
|
|
|
|
// SAR marker data (if this is a SAR message)
|
|
final bool isSarMarker;
|
|
final SarMarkerType? sarMarkerType;
|
|
final LatLng? sarGpsCoordinates;
|
|
|
|
// Display metadata
|
|
final DateTime receivedAt;
|
|
final String? senderName;
|
|
|
|
Message({
|
|
required this.id,
|
|
required this.messageType,
|
|
this.senderPublicKeyPrefix,
|
|
this.channelIdx,
|
|
required this.pathLen,
|
|
required this.textType,
|
|
required this.senderTimestamp,
|
|
required this.text,
|
|
this.isSarMarker = false,
|
|
this.sarMarkerType,
|
|
this.sarGpsCoordinates,
|
|
required this.receivedAt,
|
|
this.senderName,
|
|
});
|
|
|
|
/// Get sender public key as hex string
|
|
String? get senderKeyShort {
|
|
if (senderPublicKeyPrefix == null) return null;
|
|
return senderPublicKeyPrefix!
|
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
|
.join('');
|
|
}
|
|
|
|
/// Get sender timestamp as DateTime
|
|
DateTime get sentAt {
|
|
return DateTime.fromMillisecondsSinceEpoch(senderTimestamp * 1000);
|
|
}
|
|
|
|
/// Check if message is from a channel
|
|
bool get isChannelMessage => messageType == MessageType.channel;
|
|
|
|
/// Check if message is from a contact
|
|
bool get isContactMessage => messageType == MessageType.contact;
|
|
|
|
/// Get friendly time since message was sent
|
|
String get timeAgo {
|
|
final diff = DateTime.now().difference(sentAt);
|
|
if (diff.inMinutes < 1) return 'Just now';
|
|
if (diff.inMinutes < 60) return '${diff.inMinutes}m ago';
|
|
if (diff.inHours < 24) return '${diff.inHours}h ago';
|
|
return '${diff.inDays}d ago';
|
|
}
|
|
|
|
/// Get display name for sender
|
|
String get displaySender {
|
|
if (senderName != null && senderName!.isNotEmpty) {
|
|
return senderName!;
|
|
}
|
|
if (senderKeyShort != null) {
|
|
return senderKeyShort!.substring(0, 8);
|
|
}
|
|
if (isChannelMessage && channelIdx != null) {
|
|
return 'Channel $channelIdx';
|
|
}
|
|
return 'Unknown';
|
|
}
|
|
|
|
/// Convert to SAR marker if applicable
|
|
SarMarker? toSarMarker() {
|
|
if (!isSarMarker || sarMarkerType == null || sarGpsCoordinates == null) {
|
|
return null;
|
|
}
|
|
|
|
return SarMarker(
|
|
id: id,
|
|
type: sarMarkerType!,
|
|
location: sarGpsCoordinates!,
|
|
timestamp: sentAt,
|
|
senderPublicKey: senderPublicKeyPrefix,
|
|
senderName: senderName,
|
|
notes: text,
|
|
);
|
|
}
|
|
|
|
Message copyWith({
|
|
String? id,
|
|
MessageType? messageType,
|
|
Uint8List? senderPublicKeyPrefix,
|
|
int? channelIdx,
|
|
int? pathLen,
|
|
MessageTextType? textType,
|
|
int? senderTimestamp,
|
|
String? text,
|
|
bool? isSarMarker,
|
|
SarMarkerType? sarMarkerType,
|
|
LatLng? sarGpsCoordinates,
|
|
DateTime? receivedAt,
|
|
String? senderName,
|
|
}) {
|
|
return Message(
|
|
id: id ?? this.id,
|
|
messageType: messageType ?? this.messageType,
|
|
senderPublicKeyPrefix: senderPublicKeyPrefix ?? this.senderPublicKeyPrefix,
|
|
channelIdx: channelIdx ?? this.channelIdx,
|
|
pathLen: pathLen ?? this.pathLen,
|
|
textType: textType ?? this.textType,
|
|
senderTimestamp: senderTimestamp ?? this.senderTimestamp,
|
|
text: text ?? this.text,
|
|
isSarMarker: isSarMarker ?? this.isSarMarker,
|
|
sarMarkerType: sarMarkerType ?? this.sarMarkerType,
|
|
sarGpsCoordinates: sarGpsCoordinates ?? this.sarGpsCoordinates,
|
|
receivedAt: receivedAt ?? this.receivedAt,
|
|
senderName: senderName ?? this.senderName,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
if (isSarMarker) {
|
|
return 'Message(SAR: ${sarMarkerType?.displayName}, from: $displaySender)';
|
|
}
|
|
return 'Message(from: $displaySender, text: ${text.length > 30 ? '${text.substring(0, 30)}...' : text})';
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
return other is Message && id == other.id;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => id.hashCode;
|
|
}
|