mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: show destination and channel key
ref:
This commit is contained in:
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a reproducible bug in MeshCore SAR App
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: [bug]
|
||||||
|
assignees: []
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
A short description of the problem.
|
||||||
|
|
||||||
|
## Reported context
|
||||||
|
Where this was observed (build/version, environment, device model, logs, links).
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
What should happen.
|
||||||
|
|
||||||
|
## Actual behavior
|
||||||
|
What actually happens.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
Why this matters (user impact, risk, severity).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- [ ] Repro case is fixed.
|
||||||
|
- [ ] Behavior matches expected result.
|
||||||
|
- [ ] Regression coverage is added/updated.
|
||||||
@@ -583,6 +583,8 @@ class ContactTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showContactDetails(BuildContext context, Contact contact) {
|
void _showContactDetails(BuildContext context, Contact contact) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
// Get room login state
|
// Get room login state
|
||||||
final connectionProvider = context.read<ConnectionProvider>();
|
final connectionProvider = context.read<ConnectionProvider>();
|
||||||
final roomLoginState = contact.type == ContactType.room
|
final roomLoginState = contact.type == ContactType.room
|
||||||
@@ -650,10 +652,18 @@ class ContactTile extends StatelessWidget {
|
|||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
children: [
|
||||||
|
_detailRow(l10n.type, contact.type.displayName),
|
||||||
|
if (contact.isChannel) ...[
|
||||||
_detailRow(
|
_detailRow(
|
||||||
AppLocalizations.of(context)!.type,
|
l10n.channel,
|
||||||
contact.type.displayName,
|
contact.getLocalizedDisplayName(context),
|
||||||
),
|
),
|
||||||
|
if (!contact.isPublicChannel)
|
||||||
|
_detailRow(
|
||||||
|
'Slot',
|
||||||
|
'${l10n.channel} ${contact.publicKey.length > 1 ? contact.publicKey[1] : '-'}',
|
||||||
|
),
|
||||||
|
] else
|
||||||
// Public Key with copy button
|
// Public Key with copy button
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
@@ -663,8 +673,10 @@ class ContactTile extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${AppLocalizations.of(context)!.publicKey}:',
|
'${l10n.publicKey}:',
|
||||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Text(contact.publicKeyShort)),
|
Expanded(child: Text(contact.publicKeyShort)),
|
||||||
@@ -676,9 +688,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(l10n.publicKeyCopied),
|
||||||
AppLocalizations.of(context)!.publicKeyCopied,
|
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -697,7 +707,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_detailRow(
|
_detailRow(
|
||||||
AppLocalizations.of(context)!.lastSeen,
|
l10n.lastSeen,
|
||||||
_getLocalizedTimeSinceLastSeen(context),
|
_getLocalizedTimeSinceLastSeen(context),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../models/contact.dart';
|
||||||
import '../../models/message.dart';
|
import '../../models/message.dart';
|
||||||
import '../../models/sar_marker.dart';
|
import '../../models/sar_marker.dart';
|
||||||
import '../../models/sar_template.dart';
|
import '../../models/sar_template.dart';
|
||||||
@@ -30,6 +31,7 @@ class MessageBubble extends StatefulWidget {
|
|||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final bool isHighlighted;
|
final bool isHighlighted;
|
||||||
final VoidCallback? onNavigateToMap;
|
final VoidCallback? onNavigateToMap;
|
||||||
|
|
||||||
/// Compact mode for fullscreen map overlay (simplified styling)
|
/// Compact mode for fullscreen map overlay (simplified styling)
|
||||||
final bool isCompact;
|
final bool isCompact;
|
||||||
|
|
||||||
@@ -147,7 +149,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
final connectionProvider = context.read<ConnectionProvider>();
|
final connectionProvider = context.read<ConnectionProvider>();
|
||||||
final selfPublicKey = connectionProvider.deviceInfo.publicKey;
|
final selfPublicKey = connectionProvider.deviceInfo.publicKey;
|
||||||
final isOwnMessage =
|
final isOwnMessage =
|
||||||
widget.message.isSentMessage || widget.message.isFromSelf(selfPublicKey);
|
widget.message.isSentMessage ||
|
||||||
|
widget.message.isFromSelf(selfPublicKey);
|
||||||
|
|
||||||
// Check if we can reply to this message (must be contact message from someone else)
|
// Check if we can reply to this message (must be contact message from someone else)
|
||||||
final canReply =
|
final canReply =
|
||||||
@@ -201,8 +204,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
|
|
||||||
// Check if template with this emoji already exists
|
// Check if template with this emoji already exists
|
||||||
final sarTemplateService = SarTemplateService();
|
final sarTemplateService = SarTemplateService();
|
||||||
final templateExists = sarTemplateService.templates
|
final templateExists = sarTemplateService.templates.any(
|
||||||
.any((t) => t.emoji == sarInfo.emoji);
|
(t) => t.emoji == sarInfo.emoji,
|
||||||
|
);
|
||||||
|
|
||||||
if (templateExists) {
|
if (templateExists) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -219,7 +223,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Share location option (only for SAR markers with GPS coordinates)
|
// Share location option (only for SAR markers with GPS coordinates)
|
||||||
if (widget.message.isSarMarker && widget.message.sarGpsCoordinates != null)
|
if (widget.message.isSarMarker &&
|
||||||
|
widget.message.sarGpsCoordinates != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.share_location),
|
leading: const Icon(Icons.share_location),
|
||||||
title: Text(AppLocalizations.of(context)!.shareLocation),
|
title: Text(AppLocalizations.of(context)!.shareLocation),
|
||||||
@@ -310,9 +315,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder: (context) => DirectMessageSheet(
|
builder: (context) => DirectMessageSheet(contact: senderContact),
|
||||||
contact: senderContact,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +337,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
messagesProvider.deleteMessage(widget.message.id);
|
messagesProvider.deleteMessage(widget.message.id);
|
||||||
|
|
||||||
// Also delete the drawing if this is a drawing message
|
// Also delete the drawing if this is a drawing message
|
||||||
if (widget.message.isDrawing && widget.message.drawingId != null) {
|
if (widget.message.isDrawing &&
|
||||||
|
widget.message.drawingId != null) {
|
||||||
final drawingProvider = context.read<DrawingProvider>();
|
final drawingProvider = context.read<DrawingProvider>();
|
||||||
drawingProvider.removeDrawing(widget.message.drawingId!);
|
drawingProvider.removeDrawing(widget.message.drawingId!);
|
||||||
}
|
}
|
||||||
@@ -362,12 +366,14 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
String markerInfo = '';
|
String markerInfo = '';
|
||||||
if (widget.message.sarMarkerType != null) {
|
if (widget.message.sarMarkerType != null) {
|
||||||
markerInfo = widget.message.sarMarkerType!.emoji;
|
markerInfo = widget.message.sarMarkerType!.emoji;
|
||||||
if (widget.message.sarNotes != null && widget.message.sarNotes!.isNotEmpty) {
|
if (widget.message.sarNotes != null &&
|
||||||
|
widget.message.sarNotes!.isNotEmpty) {
|
||||||
markerInfo += ' ${widget.message.sarNotes}';
|
markerInfo += ' ${widget.message.sarNotes}';
|
||||||
}
|
}
|
||||||
} else if (widget.message.sarCustomEmoji != null) {
|
} else if (widget.message.sarCustomEmoji != null) {
|
||||||
markerInfo = widget.message.sarCustomEmoji!;
|
markerInfo = widget.message.sarCustomEmoji!;
|
||||||
if (widget.message.sarNotes != null && widget.message.sarNotes!.isNotEmpty) {
|
if (widget.message.sarNotes != null &&
|
||||||
|
widget.message.sarNotes!.isNotEmpty) {
|
||||||
markerInfo += ' ${widget.message.sarNotes}';
|
markerInfo += ' ${widget.message.sarNotes}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,10 +427,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
await sarTemplateService.addTemplate(template);
|
await sarTemplateService.addTemplate(template);
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ToastLogger.success(
|
ToastLogger.success(context, AppLocalizations.of(context)!.templateSaved);
|
||||||
context,
|
|
||||||
AppLocalizations.of(context)!.templateSaved,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving template: $e');
|
debugPrint('Error saving template: $e');
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@@ -453,12 +456,16 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (drawing is LineDrawing) {
|
if (drawing is LineDrawing) {
|
||||||
coordinatesText = drawing.points
|
coordinatesText = drawing.points
|
||||||
.map(
|
.map(
|
||||||
(p) => '${p.latitude.toStringAsFixed(5)}, ${p.longitude.toStringAsFixed(5)}',
|
(p) =>
|
||||||
|
'${p.latitude.toStringAsFixed(5)}, ${p.longitude.toStringAsFixed(5)}',
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
} else if (drawing is RectangleDrawing) {
|
} else if (drawing is RectangleDrawing) {
|
||||||
coordinatesText = drawing.corners
|
coordinatesText = drawing.corners
|
||||||
.map((p) => '${p.latitude.toStringAsFixed(5)}, ${p.longitude.toStringAsFixed(5)}')
|
.map(
|
||||||
|
(p) =>
|
||||||
|
'${p.latitude.toStringAsFixed(5)}, ${p.longitude.toStringAsFixed(5)}',
|
||||||
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
} else {
|
} else {
|
||||||
ToastLogger.error(context, 'Unknown drawing type');
|
ToastLogger.error(context, 'Unknown drawing type');
|
||||||
@@ -479,12 +486,12 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
final messagesProvider = context.read<MessagesProvider>();
|
final messagesProvider = context.read<MessagesProvider>();
|
||||||
|
|
||||||
// Remove the drawing from map and delete the message
|
// Remove the drawing from map and delete the message
|
||||||
drawingProvider.removeDrawingAndMessage(widget.message.drawingId!, messagesProvider);
|
drawingProvider.removeDrawingAndMessage(
|
||||||
|
widget.message.drawingId!,
|
||||||
ToastLogger.success(
|
messagesProvider,
|
||||||
context,
|
|
||||||
'Drawing removed from map',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ToastLogger.success(context, 'Drawing removed from map');
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getMessageBubbleColor(
|
Color _getMessageBubbleColor(
|
||||||
@@ -495,8 +502,12 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (isOwnMessage) {
|
if (isOwnMessage) {
|
||||||
// Own messages: slightly highlighted with primary color tint
|
// Own messages: slightly highlighted with primary color tint
|
||||||
return isDarkMode
|
return isDarkMode
|
||||||
? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3)
|
? Theme.of(
|
||||||
: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.15);
|
context,
|
||||||
|
).colorScheme.primaryContainer.withValues(alpha: 0.3)
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primaryContainer.withValues(alpha: 0.15);
|
||||||
} else {
|
} else {
|
||||||
// Others' messages: default surface color
|
// Others' messages: default surface color
|
||||||
return Theme.of(context).colorScheme.surfaceContainerHighest;
|
return Theme.of(context).colorScheme.surfaceContainerHighest;
|
||||||
@@ -624,7 +635,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
? AppLocalizations.of(context)!.you
|
? AppLocalizations.of(context)!.you
|
||||||
: message.getRichDisplayName(senderContact);
|
: message.getRichDisplayName(senderContact);
|
||||||
|
|
||||||
// For sent direct messages, look up recipient contact
|
// For sent direct/channel messages, look up destination display label
|
||||||
dynamic recipientContact;
|
dynamic recipientContact;
|
||||||
String? recipientDisplayName;
|
String? recipientDisplayName;
|
||||||
if (isOwnMessage &&
|
if (isOwnMessage &&
|
||||||
@@ -654,6 +665,17 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
recipientContact.displayName ?? recipientContact.advName;
|
recipientContact.displayName ?? recipientContact.advName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (isOwnMessage && message.isChannelMessage) {
|
||||||
|
if (message.channelIdx == 0) {
|
||||||
|
recipientDisplayName = AppLocalizations.of(context)!.publicChannel;
|
||||||
|
} else {
|
||||||
|
final channelContact = contactsProvider.channels.where((c) {
|
||||||
|
return c.publicKey.length > 1 && c.publicKey[1] == message.channelIdx;
|
||||||
|
}).firstOrNull;
|
||||||
|
recipientDisplayName =
|
||||||
|
channelContact?.getLocalizedDisplayName(context) ??
|
||||||
|
'${AppLocalizations.of(context)!.channel} ${message.channelIdx}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -669,8 +691,12 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
? _getSarMarkerColor(context, isDarkMode)
|
? _getSarMarkerColor(context, isDarkMode)
|
||||||
: message.isDrawing
|
: message.isDrawing
|
||||||
? (isDarkMode
|
? (isDarkMode
|
||||||
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.15)
|
? Theme.of(
|
||||||
: Theme.of(context).colorScheme.primary.withValues(alpha: 0.08))
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.15)
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.08))
|
||||||
: _getMessageBubbleColor(context, isOwnMessage, isDarkMode),
|
: _getMessageBubbleColor(context, isOwnMessage, isDarkMode),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: widget.isHighlighted
|
border: widget.isHighlighted
|
||||||
@@ -685,12 +711,16 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
)
|
)
|
||||||
: message.isDrawing
|
: message.isDrawing
|
||||||
? Border.all(
|
? Border.all(
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.4),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.4),
|
||||||
width: 2,
|
width: 2,
|
||||||
)
|
)
|
||||||
: isOwnMessage
|
: isOwnMessage
|
||||||
? Border.all(
|
? Border.all(
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.3),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
)
|
)
|
||||||
: !message.isRead &&
|
: !message.isRead &&
|
||||||
@@ -701,7 +731,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
boxShadow: widget.isHighlighted
|
boxShadow: widget.isHighlighted
|
||||||
? [
|
? [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.5),
|
||||||
blurRadius: 12,
|
blurRadius: 12,
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
@@ -710,10 +742,11 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
: isSarMarker || message.isDrawing
|
: isSarMarker || message.isDrawing
|
||||||
? [
|
? [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: (isSarMarker
|
color:
|
||||||
|
(isSarMarker
|
||||||
? _getSarMarkerBorderColor(context, isDarkMode)
|
? _getSarMarkerBorderColor(context, isDarkMode)
|
||||||
: Theme.of(context).colorScheme.primary
|
: Theme.of(context).colorScheme.primary)
|
||||||
).withValues(alpha: 0.3),
|
.withValues(alpha: 0.3),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -771,11 +804,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(Icons.draw, size: 16, color: Colors.white),
|
||||||
Icons.draw,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.mapDrawing,
|
AppLocalizations.of(context)!.mapDrawing,
|
||||||
@@ -843,23 +872,26 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Show recipient for sent direct messages
|
// Show destination for sent direct/channel messages
|
||||||
if (isOwnMessage &&
|
if (isOwnMessage &&
|
||||||
message.isContactMessage &&
|
|
||||||
recipientDisplayName != null &&
|
recipientDisplayName != null &&
|
||||||
!widget.isCompact) ...[
|
!widget.isCompact) ...[
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Icon(
|
Icon(
|
||||||
Icons.arrow_forward,
|
Icons.arrow_forward,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
recipientDisplayName,
|
recipientDisplayName,
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
color: Theme.of(context).textTheme.labelSmall?.color?.withValues(alpha: 0.7),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall?.color?.withValues(alpha: 0.7),
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -875,13 +907,17 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.alt_route,
|
Icons.alt_route,
|
||||||
size: 11,
|
size: 11,
|
||||||
color: Theme.of(context).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
Text(
|
Text(
|
||||||
message.pathLen == 0 ? 'direct' : '${message.pathLen}hop',
|
message.pathLen == 0 ? 'direct' : '${message.pathLen}hop',
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
color: Theme.of(context).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall?.color?.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
@@ -931,18 +967,23 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
'${message.sarGpsCoordinates!.latitude.toStringAsFixed(5)}, ${message.sarGpsCoordinates!.longitude.toStringAsFixed(5)}',
|
'${message.sarGpsCoordinates!.latitude.toStringAsFixed(5)}, ${message.sarGpsCoordinates!.longitude.toStringAsFixed(5)}',
|
||||||
style: Theme.of(context).textTheme.labelMedium
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||||
?.copyWith(fontFamily: 'monospace'),
|
fontFamily: 'monospace',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
// Drawing message content (skip in compact mode - drawings hidden)
|
// Drawing message content (skip in compact mode - drawings hidden)
|
||||||
else if (message.isDrawing && message.drawingId != null && !widget.isCompact)
|
else if (message.isDrawing &&
|
||||||
|
message.drawingId != null &&
|
||||||
|
!widget.isCompact)
|
||||||
Consumer<DrawingProvider>(
|
Consumer<DrawingProvider>(
|
||||||
builder: (context, drawingProvider, child) {
|
builder: (context, drawingProvider, child) {
|
||||||
final drawing = drawingProvider.getDrawingById(message.drawingId!);
|
final drawing = drawingProvider.getDrawingById(
|
||||||
|
message.drawingId!,
|
||||||
|
);
|
||||||
|
|
||||||
if (drawing == null) {
|
if (drawing == null) {
|
||||||
return Text(
|
return Text(
|
||||||
@@ -953,9 +994,13 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
|
|
||||||
final String drawingTypeLabel;
|
final String drawingTypeLabel;
|
||||||
if (drawing is LineDrawing) {
|
if (drawing is LineDrawing) {
|
||||||
drawingTypeLabel = AppLocalizations.of(context)!.lineDrawing;
|
drawingTypeLabel = AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.lineDrawing;
|
||||||
} else if (drawing is RectangleDrawing) {
|
} else if (drawing is RectangleDrawing) {
|
||||||
drawingTypeLabel = AppLocalizations.of(context)!.rectangleDrawing;
|
drawingTypeLabel = AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.rectangleDrawing;
|
||||||
} else {
|
} else {
|
||||||
drawingTypeLabel = AppLocalizations.of(context)!.drawing;
|
drawingTypeLabel = AppLocalizations.of(context)!.drawing;
|
||||||
}
|
}
|
||||||
@@ -1021,9 +1066,15 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (message.isGroupedMessage) {
|
if (message.isGroupedMessage) {
|
||||||
debugPrint('🎯 [MessageBubble] Rendering grouped message: ${message.id}');
|
debugPrint(
|
||||||
debugPrint(' Recipients: ${message.recipients?.length ?? 0}');
|
'🎯 [MessageBubble] Rendering grouped message: ${message.id}',
|
||||||
debugPrint(' Delivered: ${message.deliveredRecipientsCount}');
|
);
|
||||||
|
debugPrint(
|
||||||
|
' Recipients: ${message.recipients?.length ?? 0}',
|
||||||
|
);
|
||||||
|
debugPrint(
|
||||||
|
' Delivered: ${message.deliveredRecipientsCount}',
|
||||||
|
);
|
||||||
debugPrint(' Failed: ${message.failedRecipientsCount}');
|
debugPrint(' Failed: ${message.failedRecipientsCount}');
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -1040,13 +1091,16 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
message.deliveredRecipientsCount == message.recipients!.length
|
message.deliveredRecipientsCount ==
|
||||||
|
message.recipients!.length
|
||||||
? Icons.done_all
|
? Icons.done_all
|
||||||
: message.failedRecipientsCount > 0
|
: message.failedRecipientsCount > 0
|
||||||
? Icons.error_outline
|
? Icons.error_outline
|
||||||
: Icons.schedule,
|
: Icons.schedule,
|
||||||
size: 12,
|
size: 12,
|
||||||
color: message.deliveredRecipientsCount == message.recipients!.length
|
color:
|
||||||
|
message.deliveredRecipientsCount ==
|
||||||
|
message.recipients!.length
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: message.failedRecipientsCount > 0
|
: message.failedRecipientsCount > 0
|
||||||
? Colors.red
|
? Colors.red
|
||||||
@@ -1054,14 +1108,20 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
message.deliveredRecipientsCount == message.recipients!.length
|
message.deliveredRecipientsCount ==
|
||||||
|
message.recipients!.length
|
||||||
? AppLocalizations.of(context)!.allDelivered
|
? AppLocalizations.of(context)!.allDelivered
|
||||||
: AppLocalizations.of(context)!.deliveredToContacts(
|
: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.deliveredToContacts(
|
||||||
message.deliveredRecipientsCount,
|
message.deliveredRecipientsCount,
|
||||||
message.recipients!.length,
|
message.recipients!.length,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
color: message.deliveredRecipientsCount == message.recipients!.length
|
?.copyWith(
|
||||||
|
color:
|
||||||
|
message.deliveredRecipientsCount ==
|
||||||
|
message.recipients!.length
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: message.failedRecipientsCount > 0
|
: message.failedRecipientsCount > 0
|
||||||
? Colors.red
|
? Colors.red
|
||||||
@@ -1073,7 +1133,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
Icon(
|
Icon(
|
||||||
_isExpanded ? Icons.expand_less : Icons.expand_more,
|
_isExpanded ? Icons.expand_less : Icons.expand_more,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context).textTheme.labelSmall?.color,
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall?.color,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1083,7 +1145,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainerHighest
|
||||||
|
.withValues(alpha: 0.5),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1091,9 +1156,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.recipientDetails,
|
AppLocalizations.of(context)!.recipientDetails,
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
fontWeight: FontWeight.bold,
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
...message.recipients!.map((recipient) {
|
...message.recipients!.map((recipient) {
|
||||||
@@ -1105,39 +1169,55 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
case MessageDeliveryStatus.delivered:
|
case MessageDeliveryStatus.delivered:
|
||||||
statusColor = Colors.green;
|
statusColor = Colors.green;
|
||||||
statusIcon = Icons.check_circle;
|
statusIcon = Icons.check_circle;
|
||||||
statusText = recipient.roundTripTimeMs != null
|
statusText =
|
||||||
|
recipient.roundTripTimeMs != null
|
||||||
? '${recipient.roundTripTimeMs}ms'
|
? '${recipient.roundTripTimeMs}ms'
|
||||||
: AppLocalizations.of(context)!.delivered;
|
: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.delivered;
|
||||||
break;
|
break;
|
||||||
case MessageDeliveryStatus.failed:
|
case MessageDeliveryStatus.failed:
|
||||||
statusColor = Colors.red;
|
statusColor = Colors.red;
|
||||||
statusIcon = Icons.cancel;
|
statusIcon = Icons.cancel;
|
||||||
statusText = AppLocalizations.of(context)!.failed;
|
statusText = AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.failed;
|
||||||
break;
|
break;
|
||||||
case MessageDeliveryStatus.sending:
|
case MessageDeliveryStatus.sending:
|
||||||
case MessageDeliveryStatus.sent:
|
case MessageDeliveryStatus.sent:
|
||||||
default:
|
default:
|
||||||
statusColor = Colors.orange;
|
statusColor = Colors.orange;
|
||||||
statusIcon = Icons.schedule;
|
statusIcon = Icons.schedule;
|
||||||
statusText = AppLocalizations.of(context)!.pending;
|
statusText = AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(statusIcon, size: 14, color: statusColor),
|
Icon(
|
||||||
|
statusIcon,
|
||||||
|
size: 14,
|
||||||
|
color: statusColor,
|
||||||
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
recipient.displayName,
|
recipient.displayName,
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.labelSmall,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
statusText,
|
statusText,
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall
|
||||||
|
?.copyWith(
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -1276,7 +1356,9 @@ class SystemMessageBubble extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
message.getLocalizedTimeAgo(context),
|
message.getLocalizedTimeAgo(context),
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodySmall?.color?.withValues(alpha: 0.6),
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1286,7 +1368,9 @@ class SystemMessageBubble extends StatelessWidget {
|
|||||||
message.text,
|
message.text,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.8),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodySmall?.color?.withValues(alpha: 0.8),
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|||||||
Reference in New Issue
Block a user