mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Fix message bubble layout
This commit is contained in:
@@ -1616,19 +1616,13 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
}
|
||||
|
||||
Widget _buildChannelEchoStatus(BuildContext context, Message message) {
|
||||
final statusColor = _getDeliveryStatusColor(message.deliveryStatus);
|
||||
final hasEcho = message.echoCount > 0;
|
||||
|
||||
if (!hasEcho) {
|
||||
return Text(
|
||||
message.getLocalizedDeliveryStatus(context),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: statusColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final statusColor = _getDeliveryStatusColor(message.deliveryStatus);
|
||||
final rssi = message.lastEchoRssiDbm;
|
||||
final snr = message.lastEchoSnrRaw != null
|
||||
? message.lastEchoSnrRaw!.toSigned(8) / 4.0
|
||||
@@ -1792,6 +1786,40 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDirectHeaderCounterpart(
|
||||
BuildContext context, {
|
||||
required bool isOwnMessage,
|
||||
required String label,
|
||||
}) {
|
||||
final textColor = Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.color?.withValues(alpha: 0.75);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
isOwnMessage ? Icons.arrow_forward : Icons.arrow_back,
|
||||
size: 12,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.color?.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: textColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _hopDebugLabel(Message message) {
|
||||
if (message.pathLen >= 255 && message.isContactMessage) {
|
||||
return 'Direct (raw: ${message.pathLen})';
|
||||
@@ -2097,6 +2125,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
isOwnMessage && message.isChannelMessage && recipientDisplayName != null
|
||||
? '${l10n.channel}: $recipientDisplayName'
|
||||
: recipientDisplayName;
|
||||
final directCounterpartLabel = !message.isChannelMessage
|
||||
? (isOwnMessage ? recipientSubtitle : l10n.you)
|
||||
: null;
|
||||
final receivedChannelSubtitle =
|
||||
!isOwnMessage && message.isChannelMessage && channelDisplayName != null
|
||||
? '${l10n.channel}: $channelDisplayName'
|
||||
@@ -2293,7 +2324,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
Expanded(
|
||||
child: Text(
|
||||
displayName,
|
||||
style: Theme.of(context).textTheme.labelMedium
|
||||
@@ -2309,10 +2340,12 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
if (!widget.isCompact &&
|
||||
(recipientSubtitle != null ||
|
||||
directCounterpartLabel != null ||
|
||||
receivedChannelSubtitle != null)) ...[
|
||||
const SizedBox(width: 8),
|
||||
if (message.isChannelMessage)
|
||||
Flexible(
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: _buildChannelHeaderPill(
|
||||
context,
|
||||
label: isOwnMessage
|
||||
@@ -2322,41 +2355,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isOwnMessage
|
||||
? Icons.arrow_forward
|
||||
: Icons.arrow_back,
|
||||
size: 12,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.color
|
||||
?.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
isOwnMessage
|
||||
? recipientSubtitle!
|
||||
: receivedChannelSubtitle!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.color
|
||||
?.withValues(alpha: 0.75),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: _buildDirectHeaderCounterpart(
|
||||
context,
|
||||
isOwnMessage: isOwnMessage,
|
||||
label: directCounterpartLabel!,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -2838,7 +2840,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
]
|
||||
// Show single message delivery status
|
||||
else
|
||||
else if (!message.isChannelMessage ||
|
||||
message.deliveryStatus == MessageDeliveryStatus.failed)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
|
||||
@@ -51,7 +51,7 @@ class MessagesComposer extends StatelessWidget {
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(10, 10, 10, bottomPadding),
|
||||
padding: EdgeInsets.fromLTRB(10, 4, 10, bottomPadding),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
|
||||
@@ -281,12 +281,29 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
.sublist(0, 6)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join('');
|
||||
final request = VoiceFetchRequest(
|
||||
final missing = voiceProvider.missingPacketIndices(sessionId);
|
||||
final totalPackets = sessionPacketCount(
|
||||
voiceProvider: voiceProvider,
|
||||
sessionId: sessionId,
|
||||
requesterKey6: requesterKey6,
|
||||
timestampSec: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
version: 2,
|
||||
envelope: envelope,
|
||||
);
|
||||
final isPartialResume =
|
||||
missing.isNotEmpty && totalPackets > 0 && missing.length < totalPackets;
|
||||
final request = isPartialResume
|
||||
? VoiceFetchRequest(
|
||||
sessionId: sessionId,
|
||||
want: 'missing',
|
||||
missingIndices: missing,
|
||||
requesterKey6: requesterKey6,
|
||||
timestampSec: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
version: 2,
|
||||
)
|
||||
: VoiceFetchRequest(
|
||||
sessionId: sessionId,
|
||||
requesterKey6: requesterKey6,
|
||||
timestampSec: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
version: 2,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_isRequesting = true;
|
||||
@@ -309,11 +326,18 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
final effectivePathLen = sender.outPathLen >= 0
|
||||
? sender.outPathLen
|
||||
: pathLen;
|
||||
final estimatedDurationMs =
|
||||
envelope != null &&
|
||||
totalPackets > 0 &&
|
||||
missing.isNotEmpty &&
|
||||
missing.length < totalPackets
|
||||
? ((envelope.durationMs * missing.length) / totalPackets).round()
|
||||
: envelope?.durationMs;
|
||||
final txEstimate = envelope != null
|
||||
? estimateVoiceTransmitDuration(
|
||||
packetCount: envelope.total,
|
||||
packetCount: isPartialResume ? missing.length : envelope.total,
|
||||
mode: envelope.mode,
|
||||
durationMs: envelope.durationMs,
|
||||
durationMs: estimatedDurationMs ?? envelope.durationMs,
|
||||
pathLen: effectivePathLen,
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
@@ -331,6 +355,14 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
);
|
||||
}
|
||||
|
||||
int sessionPacketCount({
|
||||
required VoiceProvider voiceProvider,
|
||||
required String sessionId,
|
||||
required VoiceEnvelope? envelope,
|
||||
}) {
|
||||
return voiceProvider.session(sessionId)?.total ?? envelope?.total ?? 0;
|
||||
}
|
||||
|
||||
void _setUnavailable() {
|
||||
if (!mounted) return;
|
||||
_showToast(AppLocalizations.of(context)!.voiceUnavailable);
|
||||
|
||||
Reference in New Issue
Block a user