diff --git a/lib/widgets/messages/message_bubble.dart b/lib/widgets/messages/message_bubble.dart index 42bff39..bac6336 100644 --- a/lib/widgets/messages/message_bubble.dart +++ b/lib/widgets/messages/message_bubble.dart @@ -74,12 +74,14 @@ class MessageBubble extends StatefulWidget { class _MessageBubbleState extends State { static final RegExp _mentionPattern = RegExp(r'@\[(.+?)\]'); 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; } @@ -175,6 +177,19 @@ class _MessageBubbleState extends State { widget.onTap?.call(); } + void _handleBubbleDoubleTap({ + required bool isSarMarker, + required bool isDrawing, + }) { + if (widget.isCompact || isSarMarker || isDrawing) { + return; + } + + setState(() { + _showReceivedStats = !_showReceivedStats; + }); + } + Future _retryFailedMessage( BuildContext context, Message failedMessage, @@ -1924,6 +1939,10 @@ class _MessageBubbleState extends State { isSarMarker: isSarMarker, isDrawing: message.isDrawing, ), + onDoubleTap: () => _handleBubbleDoubleTap( + isSarMarker: isSarMarker, + isDrawing: message.isDrawing, + ), onLongPress: widget.isCompact ? null : () => _showMessageOptions(context), @@ -2493,7 +2512,8 @@ class _MessageBubbleState extends State { if (!widget.isCompact && !isSarMarker && !message.isDrawing && - !message.isSentMessage) ...[ + !message.isSentMessage && + _showReceivedStats) ...[ const SizedBox(height: 6), buildReceivedSignalStatus( context, @@ -2706,6 +2726,7 @@ class _MessageBubbleState extends State { message.isContactMessage && message.deliveryStatus == MessageDeliveryStatus.delivered && + _showReceivedStats && message.roundTripTimeMs != null; return Column( @@ -2811,7 +2832,8 @@ class _MessageBubbleState extends State { ); }, ), - if (shouldShowSentChannelStats(message)) ...[ + if (_showReceivedStats && + shouldShowSentChannelStats(message)) ...[ const SizedBox(height: 6), buildChannelEchoStatus(context, message), ], diff --git a/test/widgets/message_bubble_test.dart b/test/widgets/message_bubble_test.dart index 6f7c57a..c52ce4e 100644 --- a/test/widgets/message_bubble_test.dart +++ b/test/widgets/message_bubble_test.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:meshcore_sar_app/l10n/app_localizations.dart'; @@ -25,7 +26,7 @@ void main() { SharedPreferences.setMockInitialValues({}); }); - testWidgets('received bubbles show signal chips without tapping', ( + testWidgets('received bubbles show signal chips on double tap', ( tester, ) async { final harness = await _TestHarness.create(); @@ -47,6 +48,19 @@ void main() { await tester.pumpWidget(_buildApp(harness, message)); await tester.pump(const Duration(milliseconds: 60)); + expect(find.text('1 hop'), findsNothing); + expect(find.text('Fair'), findsNothing); + expect(find.text('-84'), findsNothing); + expect(find.text('6.0'), findsNothing); + + await tester.tap(find.text('Inbound message')); + await tester.pump(kDoubleTapTimeout); + + expect(find.text('1 hop'), findsNothing); + expect(find.text('-84'), findsNothing); + + await _doubleTap(tester, find.text('Inbound message')); + expect(find.text('1 hop'), findsOneWidget); expect(find.text('Fair'), findsOneWidget); expect(find.text('-84'), findsOneWidget); @@ -56,7 +70,7 @@ void main() { } }); - testWidgets('delivered direct bubbles show timing chips without tapping', ( + testWidgets('delivered direct bubbles show timing chips on double tap', ( tester, ) async { final harness = await _TestHarness.create(); @@ -78,6 +92,17 @@ void main() { await tester.pumpWidget(_buildApp(harness, message)); await tester.pump(const Duration(milliseconds: 60)); + expect(find.text('Direct'), findsNothing); + expect(find.text('320ms'), findsNothing); + + await tester.tap(find.text('Outbound message')); + await tester.pump(kDoubleTapTimeout); + + expect(find.text('Direct'), findsNothing); + expect(find.text('320ms'), findsNothing); + + await _doubleTap(tester, find.text('Outbound message')); + expect(find.text('Direct'), findsOneWidget); expect(find.text('320ms'), findsOneWidget); } finally { @@ -85,7 +110,7 @@ void main() { } }); - testWidgets('sent channel bubbles show echo chips without tapping', ( + testWidgets('sent channel bubbles show echo chips on double tap', ( tester, ) async { final harness = await _TestHarness.create(); @@ -109,6 +134,18 @@ void main() { await tester.pumpWidget(_buildApp(harness, message)); await tester.pump(const Duration(milliseconds: 60)); + expect(find.text('x2'), findsNothing); + expect(find.text('-76'), findsNothing); + expect(find.text('5.0'), findsNothing); + + await tester.tap(find.text('Broadcast message')); + await tester.pump(kDoubleTapTimeout); + + expect(find.text('x2'), findsNothing); + expect(find.text('-76'), findsNothing); + + await _doubleTap(tester, find.text('Broadcast message')); + expect(find.text('x2'), findsOneWidget); expect(find.text('-76'), findsOneWidget); expect(find.text('5.0'), findsOneWidget); @@ -193,7 +230,15 @@ Uint8List _key(int seed) => Uint8List.fromList(List.generate(32, (index) => seed + index)); Future _disposeHarness(WidgetTester tester, _TestHarness harness) async { + await tester.pump(kDoubleTapTimeout); await tester.pumpWidget(const SizedBox.shrink()); harness.dispose(); await tester.pump(); } + +Future _doubleTap(WidgetTester tester, Finder finder) async { + await tester.tap(finder); + await tester.pump(kDoubleTapMinTime); + await tester.tap(finder); + await tester.pump(); +}