diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 3a1cb72..e4fd064 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -511,7 +511,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -530,7 +530,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -547,7 +547,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -679,7 +679,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -702,7 +702,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 70;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 2b9c76d..c18990d 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 70
+ 71
LSRequiresIPhoneOS
NSBluetoothAlwaysUsageDescription
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index e84cb57..b74dd9c 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,22 @@
-
+
-
+
-
+
-
+
diff --git a/lib/widgets/messages/message_bubble.dart b/lib/widgets/messages/message_bubble.dart
index 177a1ea..05d48b9 100644
--- a/lib/widgets/messages/message_bubble.dart
+++ b/lib/widgets/messages/message_bubble.dart
@@ -58,10 +58,17 @@ class MessageBubble extends StatefulWidget {
class _MessageBubbleState extends State {
bool _isExpanded = false;
+ bool _showReceivedStats = false;
@override
void didUpdateWidget(MessageBubble oldWidget) {
super.didUpdateWidget(oldWidget);
+ if (oldWidget.message.id != widget.message.id) {
+ _isExpanded = false;
+ _showReceivedStats = false;
+ return;
+ }
+
// Force rebuild when message properties change (especially recipient statuses)
if (oldWidget.message.id == widget.message.id) {
// Same message, but properties might have changed
@@ -77,6 +84,15 @@ class _MessageBubbleState extends State {
});
}
+ void _handleBubbleTap({required bool isSarMarker, required bool isDrawing}) {
+ if (!widget.isCompact && !isSarMarker && !isDrawing) {
+ setState(() {
+ _showReceivedStats = !_showReceivedStats;
+ });
+ }
+ widget.onTap?.call();
+ }
+
Future _retryFailedMessage(
BuildContext context,
Message failedMessage,
@@ -437,8 +453,8 @@ class _MessageBubbleState extends State {
final snrDb =
matchedRxLog?.logRxDataInfo?.snrDb ??
(widget.message.lastEchoSnrRaw != null
- ? (widget.message.lastEchoSnrRaw!.toSigned(8) / 4.0)
- : null);
+ ? (widget.message.lastEchoSnrRaw!.toSigned(8) / 4.0)
+ : null);
final rssiDbm =
matchedRxLog?.logRxDataInfo?.rssiDbm ?? widget.message.lastEchoRssiDbm;
@@ -604,291 +620,305 @@ class _MessageBubbleState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- _techBadge(
- context,
- icon: Icons.message,
- label: widget.message.messageType.name.toUpperCase(),
- ),
- _techBadge(
- context,
- icon: Icons.route,
- label:
- '${widget.message.pathLen} hop${widget.message.pathLen == 1 ? '' : 's'}',
- ),
- _techBadge(
- context,
- icon: Icons.account_tree_outlined,
- label:
- '${widget.message.echoCount} node${widget.message.echoCount == 1 ? '' : 's'}',
- ),
- if (widget.message.channelIdx != null)
- _techBadge(
- context,
- icon: Icons.group_work,
- label: 'CH ${widget.message.channelIdx}',
- ),
- ],
- ),
- if (widget.message.lastEchoRssiDbm != null ||
- snrDb != null ||
- rssiDbm != null) ...[
- const SizedBox(height: 12),
- _techSection(
- context,
- icon: Icons.network_check,
- title: l10n.linkQuality,
- child: Column(
- children: [
- if (rssiDbm != null)
- _signalRow(
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ _techBadge(
context,
- label: 'RSSI',
- valueLabel: '$rssiDbm dBm',
- normalized:
- ((rssiDbm.toDouble() +
- 120.0) /
- 70.0)
- .clamp(0.0, 1.0),
- color: rssiDbm >= -80
- ? Colors.green
- : rssiDbm >= -95
- ? Colors.amber
- : Colors.redAccent,
+ icon: Icons.message,
+ label: widget.message.messageType.name
+ .toUpperCase(),
),
- if (snrDb != null) ...[
- const SizedBox(height: 8),
- _signalRow(
+ _techBadge(
context,
- label: 'SNR',
- valueLabel: '${snrDb.toStringAsFixed(1)} dB',
- normalized: ((snrDb + 20.0) / 40.0).clamp(0.0, 1.0),
- color: snrDb >= 10
- ? Colors.green
- : snrDb >= 0
- ? Colors.amber
- : Colors.redAccent,
+ icon: Icons.route,
+ label:
+ '${widget.message.pathLen} hop${widget.message.pathLen == 1 ? '' : 's'}',
+ ),
+ _techBadge(
+ context,
+ icon: Icons.account_tree_outlined,
+ label:
+ '${widget.message.echoCount} node${widget.message.echoCount == 1 ? '' : 's'}',
+ ),
+ if (widget.message.channelIdx != null)
+ _techBadge(
+ context,
+ icon: Icons.group_work,
+ label: 'CH ${widget.message.channelIdx}',
+ ),
+ ],
+ ),
+ if (widget.message.lastEchoRssiDbm != null ||
+ snrDb != null ||
+ rssiDbm != null) ...[
+ const SizedBox(height: 12),
+ _techSection(
+ context,
+ icon: Icons.network_check,
+ title: l10n.linkQuality,
+ child: Column(
+ children: [
+ if (rssiDbm != null)
+ _signalRow(
+ context,
+ label: 'RSSI',
+ valueLabel: '$rssiDbm dBm',
+ normalized:
+ ((rssiDbm.toDouble() + 120.0) / 70.0)
+ .clamp(0.0, 1.0),
+ color: rssiDbm >= -80
+ ? Colors.green
+ : rssiDbm >= -95
+ ? Colors.amber
+ : Colors.redAccent,
+ ),
+ if (snrDb != null) ...[
+ const SizedBox(height: 8),
+ _signalRow(
+ context,
+ label: 'SNR',
+ valueLabel: '${snrDb.toStringAsFixed(1)} dB',
+ normalized: ((snrDb + 20.0) / 40.0).clamp(
+ 0.0,
+ 1.0,
+ ),
+ color: snrDb >= 10
+ ? Colors.green
+ : snrDb >= 0
+ ? Colors.amber
+ : Colors.redAccent,
+ ),
+ ],
+ ],
+ ),
+ ),
+ ],
+ const SizedBox(height: 12),
+ _techSection(
+ context,
+ icon: Icons.tune,
+ title: l10n.delivery,
+ child: Column(
+ children: [
+ _detailRow(
+ context,
+ label: l10n.status,
+ value: widget.message.deliveryStatus.name,
+ ),
+ if (widget.message.expectedAckTag != null)
+ _detailRow(
+ context,
+ label: l10n.expectedAckTag,
+ value: widget.message.expectedAckTag!
+ .toString(),
+ ),
+ if (widget.message.roundTripTimeMs != null)
+ _detailRow(
+ context,
+ label: l10n.roundTrip,
+ value: '${widget.message.roundTripTimeMs} ms',
+ ),
+ if (widget.message.retryAttempt > 0)
+ _detailRow(
+ context,
+ label: l10n.retryAttempt,
+ value: widget.message.retryAttempt.toString(),
+ ),
+ if (widget.message.usedFloodFallback)
+ _detailRow(
+ context,
+ label: l10n.floodFallback,
+ value: l10n.yes,
+ ),
+ if (packetPathHex != null)
+ _detailRow(
+ context,
+ label: 'Path bytes',
+ value: packetPathHex,
+ onCopy: () => copyField(packetPathHex),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 12),
+ _techSection(
+ context,
+ icon: Icons.badge,
+ title: l10n.identity,
+ child: Column(
+ children: [
+ _detailRow(
+ context,
+ label: l10n.messageId,
+ value: widget.message.id,
+ onCopy: () => copyField(widget.message.id),
+ ),
+ _detailRow(
+ context,
+ label: l10n.sender,
+ value:
+ senderName ??
+ widget.message.senderName ??
+ 'Unknown',
+ ),
+ if (senderPrefixHex != null)
+ _detailRow(
+ context,
+ label: l10n.senderKey,
+ value: senderPrefixHex,
+ onCopy: () => copyField(senderPrefixHex),
+ ),
+ if (recipientName != null)
+ _detailRow(
+ context,
+ label: l10n.recipient,
+ value: recipientName,
+ ),
+ if (recipientPrefixHex != null)
+ _detailRow(
+ context,
+ label: l10n.recipientKey,
+ value: recipientPrefixHex,
+ onCopy: () => copyField(recipientPrefixHex),
+ ),
+ ],
+ ),
+ ),
+ if (widget.message.isVoice) ...[
+ const SizedBox(height: 12),
+ _techSection(
+ context,
+ icon: Icons.graphic_eq,
+ title: l10n.voice,
+ child: Column(
+ children: [
+ _detailRow(
+ context,
+ label: l10n.voiceId,
+ value: widget.message.voiceId ?? '-',
+ ),
+ _detailRow(
+ context,
+ label: l10n.envelope,
+ value: envelope != null
+ ? 'VE1 compact'
+ : legacyVoicePacket != null
+ ? 'Legacy V packet'
+ : l10n.unknown,
+ ),
+ if (voiceSession != null)
+ _detailRow(
+ context,
+ label: l10n.sessionProgress,
+ value:
+ '${voiceSession.receivedCount}/${voiceSession.total} segments',
+ ),
+ if (voiceSession != null)
+ _detailRow(
+ context,
+ label: l10n.complete,
+ value: voiceSession.isComplete
+ ? l10n.yes
+ : l10n.no,
+ ),
+ if (voiceTxEstimate > Duration.zero)
+ _detailRow(
+ context,
+ label: 'Estimated tx',
+ value: voiceTxEstimate.inSeconds < 60
+ ? '~${voiceTxEstimate.inSeconds}s'
+ : '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
+ ),
+ ],
+ ),
+ ),
+ ],
+ if (imageEnvelope != null) ...[
+ const SizedBox(height: 12),
+ _techSection(
+ context,
+ icon: Icons.image_outlined,
+ title: 'Image',
+ child: Column(
+ children: [
+ _detailRow(
+ context,
+ label: l10n.envelope,
+ value: 'IE1',
+ ),
+ _detailRow(
+ context,
+ label: 'Format',
+ value: imageEnvelope.format.label,
+ ),
+ _detailRow(
+ context,
+ label: 'Dimensions',
+ value:
+ '${imageEnvelope.width}×${imageEnvelope.height}',
+ ),
+ _detailRow(
+ context,
+ label: 'Segments',
+ value: imageSession != null
+ ? '${imageSession.receivedCount}/${imageSession.total}'
+ : '${imageEnvelope.total}',
+ ),
+ if (imageSession != null)
+ _detailRow(
+ context,
+ label: l10n.complete,
+ value: imageSession.isComplete
+ ? l10n.yes
+ : l10n.no,
+ ),
+ if (imageTxEstimate > Duration.zero)
+ _detailRow(
+ context,
+ label: 'Estimated tx',
+ value: imageTxEstimate.inSeconds < 60
+ ? '~${imageTxEstimate.inSeconds}s'
+ : '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
+ ),
+ ],
+ ),
+ ),
+ ],
+ const SizedBox(height: 8),
+ ExpansionTile(
+ tilePadding: EdgeInsets.zero,
+ dense: true,
+ visualDensity: VisualDensity.compact,
+ title: Text(
+ l10n.rawDump,
+ style: const TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(10),
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .colorScheme
+ .surfaceContainerHighest
+ .withValues(alpha: 0.35),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: SelectableText(
+ rawLines.join('\n'),
+ style: const TextStyle(
+ fontFamily: 'monospace',
+ fontSize: 12,
+ ),
+ ),
),
],
- ],
- ),
- ),
- ],
- const SizedBox(height: 12),
- _techSection(
- context,
- icon: Icons.tune,
- title: l10n.delivery,
- child: Column(
- children: [
- _detailRow(
- context,
- label: l10n.status,
- value: widget.message.deliveryStatus.name,
),
- if (widget.message.expectedAckTag != null)
- _detailRow(
- context,
- label: l10n.expectedAckTag,
- value: widget.message.expectedAckTag!.toString(),
- ),
- if (widget.message.roundTripTimeMs != null)
- _detailRow(
- context,
- label: l10n.roundTrip,
- value: '${widget.message.roundTripTimeMs} ms',
- ),
- if (widget.message.retryAttempt > 0)
- _detailRow(
- context,
- label: l10n.retryAttempt,
- value: widget.message.retryAttempt.toString(),
- ),
- if (widget.message.usedFloodFallback)
- _detailRow(
- context,
- label: l10n.floodFallback,
- value: l10n.yes,
- ),
- if (packetPathHex != null)
- _detailRow(
- context,
- label: 'Path bytes',
- value: packetPathHex,
- onCopy: () => copyField(packetPathHex),
- ),
- ],
- ),
- ),
- const SizedBox(height: 12),
- _techSection(
- context,
- icon: Icons.badge,
- title: l10n.identity,
- child: Column(
- children: [
- _detailRow(
- context,
- label: l10n.messageId,
- value: widget.message.id,
- onCopy: () => copyField(widget.message.id),
- ),
- _detailRow(
- context,
- label: l10n.sender,
- value: senderName ?? widget.message.senderName ?? 'Unknown',
- ),
- if (senderPrefixHex != null)
- _detailRow(
- context,
- label: l10n.senderKey,
- value: senderPrefixHex,
- onCopy: () => copyField(senderPrefixHex),
- ),
- if (recipientName != null)
- _detailRow(
- context,
- label: l10n.recipient,
- value: recipientName,
- ),
- if (recipientPrefixHex != null)
- _detailRow(
- context,
- label: l10n.recipientKey,
- value: recipientPrefixHex,
- onCopy: () => copyField(recipientPrefixHex),
- ),
- ],
- ),
- ),
- if (widget.message.isVoice) ...[
- const SizedBox(height: 12),
- _techSection(
- context,
- icon: Icons.graphic_eq,
- title: l10n.voice,
- child: Column(
- children: [
- _detailRow(
- context,
- label: l10n.voiceId,
- value: widget.message.voiceId ?? '-',
- ),
- _detailRow(
- context,
- label: l10n.envelope,
- value: envelope != null
- ? 'VE1 compact'
- : legacyVoicePacket != null
- ? 'Legacy V packet'
- : l10n.unknown,
- ),
- if (voiceSession != null)
- _detailRow(
- context,
- label: l10n.sessionProgress,
- value:
- '${voiceSession.receivedCount}/${voiceSession.total} segments',
- ),
- if (voiceSession != null)
- _detailRow(
- context,
- label: l10n.complete,
- value: voiceSession.isComplete ? l10n.yes : l10n.no,
- ),
- if (voiceTxEstimate > Duration.zero)
- _detailRow(
- context,
- label: 'Estimated tx',
- value: voiceTxEstimate.inSeconds < 60
- ? '~${voiceTxEstimate.inSeconds}s'
- : '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
- ),
- ],
- ),
- ),
- ],
- if (imageEnvelope != null) ...[
- const SizedBox(height: 12),
- _techSection(
- context,
- icon: Icons.image_outlined,
- title: 'Image',
- child: Column(
- children: [
- _detailRow(context, label: l10n.envelope, value: 'IE1'),
- _detailRow(
- context,
- label: 'Format',
- value: imageEnvelope.format.label,
- ),
- _detailRow(
- context,
- label: 'Dimensions',
- value:
- '${imageEnvelope.width}×${imageEnvelope.height}',
- ),
- _detailRow(
- context,
- label: 'Segments',
- value: imageSession != null
- ? '${imageSession.receivedCount}/${imageSession.total}'
- : '${imageEnvelope.total}',
- ),
- if (imageSession != null)
- _detailRow(
- context,
- label: l10n.complete,
- value: imageSession.isComplete ? l10n.yes : l10n.no,
- ),
- if (imageTxEstimate > Duration.zero)
- _detailRow(
- context,
- label: 'Estimated tx',
- value: imageTxEstimate.inSeconds < 60
- ? '~${imageTxEstimate.inSeconds}s'
- : '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
- ),
- ],
- ),
- ),
- ],
- const SizedBox(height: 8),
- ExpansionTile(
- tilePadding: EdgeInsets.zero,
- dense: true,
- visualDensity: VisualDensity.compact,
- title: Text(
- l10n.rawDump,
- style: const TextStyle(
- fontSize: 12,
- fontWeight: FontWeight.w600,
- ),
- ),
- children: [
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(10),
- decoration: BoxDecoration(
- color: Theme.of(context)
- .colorScheme
- .surfaceContainerHighest
- .withValues(alpha: 0.35),
- borderRadius: BorderRadius.circular(8),
- ),
- child: SelectableText(
- rawLines.join('\n'),
- style: const TextStyle(
- fontFamily: 'monospace',
- fontSize: 12,
- ),
- ),
- ),
- ],
- ),
],
),
),
@@ -1043,7 +1073,10 @@ class _MessageBubbleState extends State {
);
}
- BlePacketLog? _findBestMatchingRxLog(List logs, Message message) {
+ BlePacketLog? _findBestMatchingRxLog(
+ List logs,
+ Message message,
+ ) {
if (message.pathLen < 0 || message.pathLen >= 255) return null;
final expectedPayloadType = message.messageType == MessageType.channel
? 0x05
@@ -1465,6 +1498,55 @@ class _MessageBubbleState extends State {
);
}
+ Widget _buildReceivedSignalStatus(
+ BuildContext context,
+ Message message, {
+ required int? rssiDbm,
+ required double? snrDb,
+ }) {
+ final hopLabel = message.pathLen == 0
+ ? 'Direct'
+ : '${message.pathLen} hop${message.pathLen == 1 ? '' : 's'}';
+
+ return Wrap(
+ spacing: 4,
+ runSpacing: 4,
+ crossAxisAlignment: WrapCrossAlignment.center,
+ children: [
+ _techChip(
+ context,
+ icon: Icons.alt_route,
+ label: hopLabel,
+ color: Colors.indigo,
+ ),
+ if (rssiDbm != null || snrDb != null) ...[
+ _techChip(
+ context,
+ icon: Icons.bolt,
+ label: _linkQualityLabel(rssiDbm, snrDb),
+ color: _linkQualityColor(_linkQualityLabel(rssiDbm, snrDb)),
+ ),
+ if (rssiDbm != null)
+ _signalCapsule(
+ context,
+ icon: Icons.network_cell,
+ label: '$rssiDbm',
+ filled: _rssiScore(rssiDbm),
+ color: Colors.blueGrey,
+ ),
+ if (snrDb != null)
+ _signalCapsule(
+ context,
+ icon: Icons.graphic_eq,
+ label: snrDb.toStringAsFixed(1),
+ filled: _snrScore(snrDb),
+ color: Colors.teal,
+ ),
+ ],
+ ],
+ );
+ }
+
Widget _techChip(
BuildContext context, {
required IconData icon,
@@ -1585,6 +1667,19 @@ class _MessageBubbleState extends State {
final selfPublicKey = connectionProvider.deviceInfo.publicKey;
final isOwnMessage =
message.isSentMessage || message.isFromSelf(selfPublicKey);
+ final matchedRxLog = !isOwnMessage
+ ? _findBestMatchingRxLog(
+ connectionProvider.bleService.packetLogs,
+ message,
+ )
+ : null;
+ final snrDb =
+ matchedRxLog?.logRxDataInfo?.snrDb ??
+ (message.lastEchoSnrRaw != null
+ ? (message.lastEchoSnrRaw!.toSigned(8) / 4.0)
+ : null);
+ final rssiDbm =
+ matchedRxLog?.logRxDataInfo?.rssiDbm ?? message.lastEchoRssiDbm;
// Look up contact information for rich display name
final contactsProvider = context.read();
@@ -1660,7 +1755,10 @@ class _MessageBubbleState extends State {
: recipientDisplayName;
return GestureDetector(
- onTap: widget.onTap,
+ onTap: () => _handleBubbleTap(
+ isSarMarker: isSarMarker,
+ isDrawing: message.isDrawing,
+ ),
onLongPress: widget.isCompact ? null : () => _showMessageOptions(context),
child: Container(
margin: const EdgeInsets.only(bottom: 8),
@@ -2065,6 +2163,19 @@ class _MessageBubbleState extends State {
else if (!message.isDrawing || widget.isCompact)
Text(message.text, style: Theme.of(context).textTheme.bodyMedium),
+ if (!widget.isCompact &&
+ !isSarMarker &&
+ !message.isDrawing &&
+ _showReceivedStats) ...[
+ const SizedBox(height: 6),
+ _buildReceivedSignalStatus(
+ context,
+ message,
+ rssiDbm: rssiDbm,
+ snrDb: snrDb,
+ ),
+ ],
+
// Delivery status for sent messages (skip in compact mode)
if (message.isSentMessage && !widget.isCompact) ...[
const SizedBox(height: 6),