fix: show destination and channel key

ref:
This commit is contained in:
Janez T
2026-02-28 14:16:46 +01:00
parent 6f94d3a9ba
commit c90fd8f617
3 changed files with 257 additions and 131 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View File

@@ -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,54 +652,62 @@ class ContactTile extends StatelessWidget {
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
_detailRow( _detailRow(l10n.type, contact.type.displayName),
AppLocalizations.of(context)!.type, if (contact.isChannel) ...[
contact.type.displayName, _detailRow(
), l10n.channel,
// Public Key with copy button contact.getLocalizedDisplayName(context),
Padding( ),
padding: const EdgeInsets.symmetric(vertical: 4), if (!contact.isPublicChannel)
child: Row( _detailRow(
crossAxisAlignment: CrossAxisAlignment.center, 'Slot',
children: [ '${l10n.channel} ${contact.publicKey.length > 1 ? contact.publicKey[1] : '-'}',
SizedBox( ),
width: 100, ] else
child: Text( // Public Key with copy button
'${AppLocalizations.of(context)!.publicKey}:', Padding(
style: const TextStyle(fontWeight: FontWeight.w500), padding: const EdgeInsets.symmetric(vertical: 4),
), child: Row(
), crossAxisAlignment: CrossAxisAlignment.center,
Expanded(child: Text(contact.publicKeyShort)), children: [
const SizedBox(width: 8), SizedBox(
InkWell( width: 100,
onTap: () { child: Text(
Clipboard.setData( '${l10n.publicKey}:',
ClipboardData(text: contact.publicKeyHex), style: const TextStyle(
); fontWeight: FontWeight.w500,
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.publicKeyCopied,
),
duration: const Duration(seconds: 2),
), ),
);
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: const EdgeInsets.all(4),
child: Icon(
Icons.copy,
size: 16,
color: Theme.of(context).colorScheme.primary,
), ),
), ),
), Expanded(child: Text(contact.publicKeyShort)),
], const SizedBox(width: 8),
InkWell(
onTap: () {
Clipboard.setData(
ClipboardData(text: contact.publicKeyHex),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.publicKeyCopied),
duration: const Duration(seconds: 2),
),
);
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: const EdgeInsets.all(4),
child: Icon(
Icons.copy,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
), ),
),
_detailRow( _detailRow(
AppLocalizations.of(context)!.lastSeen, l10n.lastSeen,
_getLocalizedTimeSinceLastSeen(context), _getLocalizedTimeSinceLastSeen(context),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),

View File

@@ -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:
? _getSarMarkerBorderColor(context, isDarkMode) (isSarMarker
: Theme.of(context).colorScheme.primary ? _getSarMarkerBorderColor(context, isDarkMode)
).withValues(alpha: 0.3), : Theme.of(context).colorScheme.primary)
.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,40 +1091,51 @@ 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
: Colors.orange, : Colors.orange,
), ),
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(
? Colors.green color:
: message.failedRecipientsCount > 0 message.deliveredRecipientsCount ==
message.recipients!.length
? Colors.green
: message.failedRecipientsCount > 0
? Colors.red ? Colors.red
: Colors.orange, : Colors.orange,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
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,42 +1169,58 @@ 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)
color: statusColor, .textTheme
fontWeight: FontWeight.w500, .labelSmall
), ?.copyWith(
color: statusColor,
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,