mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: Show message signal chips by default #123
This commit is contained in:
@@ -74,14 +74,12 @@ class MessageBubble extends StatefulWidget {
|
|||||||
class _MessageBubbleState extends State<MessageBubble> {
|
class _MessageBubbleState extends State<MessageBubble> {
|
||||||
static final RegExp _mentionPattern = RegExp(r'@\[(.+?)\]');
|
static final RegExp _mentionPattern = RegExp(r'@\[(.+?)\]');
|
||||||
bool _isExpanded = false;
|
bool _isExpanded = false;
|
||||||
bool _showReceivedStats = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(MessageBubble oldWidget) {
|
void didUpdateWidget(MessageBubble oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.message.id != widget.message.id) {
|
if (oldWidget.message.id != widget.message.id) {
|
||||||
_isExpanded = false;
|
_isExpanded = false;
|
||||||
_showReceivedStats = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +172,6 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
!widget.message.isSystemMessage) {
|
!widget.message.isSystemMessage) {
|
||||||
context.read<MessagesProvider>().markAsRead(widget.message.id);
|
context.read<MessagesProvider>().markAsRead(widget.message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!widget.isCompact && !isSarMarker && !isDrawing) {
|
|
||||||
setState(() {
|
|
||||||
_showReceivedStats = !_showReceivedStats;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
widget.onTap?.call();
|
widget.onTap?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +676,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
|
|
||||||
void copyField(String value) {
|
void copyField(String value) {
|
||||||
Clipboard.setData(ClipboardData(text: value));
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
ToastLogger.success(context, AppLocalizations.of(context)!.textCopiedToClipboard);
|
ToastLogger.success(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.textCopiedToClipboard,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -812,7 +807,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
),
|
),
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.receivedRfc3339,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.receivedRfc3339,
|
||||||
value: _formatRfc3339(widget.message.receivedAt),
|
value: _formatRfc3339(widget.message.receivedAt),
|
||||||
onCopy: () => copyField(
|
onCopy: () => copyField(
|
||||||
_formatRfc3339(widget.message.receivedAt),
|
_formatRfc3339(widget.message.receivedAt),
|
||||||
@@ -821,14 +818,18 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (widget.message.expectedAckTag != null)
|
if (widget.message.expectedAckTag != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.expectedAckTag,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.expectedAckTag,
|
||||||
value: widget.message.expectedAckTag!
|
value: widget.message.expectedAckTag!
|
||||||
.toString(),
|
.toString(),
|
||||||
),
|
),
|
||||||
if (receptionDetails?.senderToReceiptMs != null)
|
if (receptionDetails?.senderToReceiptMs != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.senderToReceipt,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.senderToReceipt,
|
||||||
value: _formatDurationMs(
|
value: _formatDurationMs(
|
||||||
receptionDetails!.senderToReceiptMs!,
|
receptionDetails!.senderToReceiptMs!,
|
||||||
),
|
),
|
||||||
@@ -836,7 +837,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (receptionDetails?.estimatedTransmitMs != null)
|
if (receptionDetails?.estimatedTransmitMs != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.estimatedTx,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.estimatedTx,
|
||||||
value: _formatDurationMs(
|
value: _formatDurationMs(
|
||||||
receptionDetails!.estimatedTransmitMs!,
|
receptionDetails!.estimatedTransmitMs!,
|
||||||
),
|
),
|
||||||
@@ -844,7 +847,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (receptionDetails?.postTransmitDelayMs != null)
|
if (receptionDetails?.postTransmitDelayMs != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.posttxDelay,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.posttxDelay,
|
||||||
value: _formatDurationMs(
|
value: _formatDurationMs(
|
||||||
receptionDetails!.postTransmitDelayMs!,
|
receptionDetails!.postTransmitDelayMs!,
|
||||||
),
|
),
|
||||||
@@ -852,7 +857,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (widget.receivedCopies > 1)
|
if (widget.receivedCopies > 1)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.receivedCopies,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.receivedCopies,
|
||||||
value: '${widget.receivedCopies}',
|
value: '${widget.receivedCopies}',
|
||||||
),
|
),
|
||||||
if (widget.message.suggestedTimeoutMs != null)
|
if (widget.message.suggestedTimeoutMs != null)
|
||||||
@@ -883,7 +890,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (widget.message.retryAttempt > 0)
|
if (widget.message.retryAttempt > 0)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.retryAttempt,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.retryAttempt,
|
||||||
value: '${widget.message.retryAttempt}/4',
|
value: '${widget.message.retryAttempt}/4',
|
||||||
),
|
),
|
||||||
if (widget.message.lastRetryAt != null)
|
if (widget.message.lastRetryAt != null)
|
||||||
@@ -900,21 +909,27 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (widget.message.usedFloodFallback)
|
if (widget.message.usedFloodFallback)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.floodFallback,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.floodFallback,
|
||||||
value: AppLocalizations.of(context)!.yes,
|
value: AppLocalizations.of(context)!.yes,
|
||||||
),
|
),
|
||||||
if (routeMetadata?.canonicalPath
|
if (routeMetadata?.canonicalPath
|
||||||
case final routePath?)
|
case final routePath?)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.selectedPath,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.selectedPath,
|
||||||
value: routePath,
|
value: routePath,
|
||||||
onCopy: () => copyField(routePath),
|
onCopy: () => copyField(routePath),
|
||||||
),
|
),
|
||||||
if (retryResult != null)
|
if (retryResult != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.retryResult,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.retryResult,
|
||||||
value: retryResult,
|
value: retryResult,
|
||||||
),
|
),
|
||||||
if (packetPathHex != null)
|
if (packetPathHex != null)
|
||||||
@@ -964,7 +979,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (recipientPrefixHex != null)
|
if (recipientPrefixHex != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.recipientKey,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.recipientKey,
|
||||||
value: recipientPrefixHex,
|
value: recipientPrefixHex,
|
||||||
onCopy: () => copyField(recipientPrefixHex),
|
onCopy: () => copyField(recipientPrefixHex),
|
||||||
),
|
),
|
||||||
@@ -994,7 +1011,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (voiceSession != null)
|
if (voiceSession != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.sessionProgress,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.sessionProgress,
|
||||||
value:
|
value:
|
||||||
'${voiceSession.receivedCount}/${voiceSession.total} segments',
|
'${voiceSession.receivedCount}/${voiceSession.total} segments',
|
||||||
),
|
),
|
||||||
@@ -1009,14 +1028,18 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (transferDetails != null)
|
if (transferDetails != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.transfers,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.transfers,
|
||||||
value: '${transferDetails.totalTransfers}',
|
value: '${transferDetails.totalTransfers}',
|
||||||
),
|
),
|
||||||
if (transferDetails != null &&
|
if (transferDetails != null &&
|
||||||
transferDetails.downloaders.isNotEmpty)
|
transferDetails.downloaders.isNotEmpty)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.downloadedBy,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.downloadedBy,
|
||||||
value: _formatDownloaderSummary(
|
value: _formatDownloaderSummary(
|
||||||
transferDetails,
|
transferDetails,
|
||||||
),
|
),
|
||||||
@@ -1024,7 +1047,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (voiceTxEstimate > Duration.zero)
|
if (voiceTxEstimate > Duration.zero)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.estimatedTx,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.estimatedTx,
|
||||||
value: voiceTxEstimate.inSeconds < 60
|
value: voiceTxEstimate.inSeconds < 60
|
||||||
? '~${voiceTxEstimate.inSeconds}s'
|
? '~${voiceTxEstimate.inSeconds}s'
|
||||||
: '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
|
: '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
|
||||||
@@ -1075,14 +1100,18 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (transferDetails != null)
|
if (transferDetails != null)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.transfers,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.transfers,
|
||||||
value: '${transferDetails.totalTransfers}',
|
value: '${transferDetails.totalTransfers}',
|
||||||
),
|
),
|
||||||
if (transferDetails != null &&
|
if (transferDetails != null &&
|
||||||
transferDetails.downloaders.isNotEmpty)
|
transferDetails.downloaders.isNotEmpty)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.downloadedBy,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.downloadedBy,
|
||||||
value: _formatDownloaderSummary(
|
value: _formatDownloaderSummary(
|
||||||
transferDetails,
|
transferDetails,
|
||||||
),
|
),
|
||||||
@@ -1090,7 +1119,9 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (imageTxEstimate > Duration.zero)
|
if (imageTxEstimate > Duration.zero)
|
||||||
_detailRow(
|
_detailRow(
|
||||||
sheetContext,
|
sheetContext,
|
||||||
label: AppLocalizations.of(context)!.estimatedTx,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.estimatedTx,
|
||||||
value: imageTxEstimate.inSeconds < 60
|
value: imageTxEstimate.inSeconds < 60
|
||||||
? '~${imageTxEstimate.inSeconds}s'
|
? '~${imageTxEstimate.inSeconds}s'
|
||||||
: '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
|
: '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
|
||||||
@@ -1438,7 +1469,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
|
|
||||||
// Share the location
|
// Share the location
|
||||||
SharePlus.instance.share(
|
SharePlus.instance.share(
|
||||||
ShareParams(text: shareText, subject: AppLocalizations.of(context)!.sarLocationShare),
|
ShareParams(
|
||||||
|
text: shareText,
|
||||||
|
subject: AppLocalizations.of(context)!.sarLocationShare,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2459,8 +2493,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
if (!widget.isCompact &&
|
if (!widget.isCompact &&
|
||||||
!isSarMarker &&
|
!isSarMarker &&
|
||||||
!message.isDrawing &&
|
!message.isDrawing &&
|
||||||
!message.isSentMessage &&
|
!message.isSentMessage) ...[
|
||||||
_showReceivedStats) ...[
|
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
buildReceivedSignalStatus(
|
buildReceivedSignalStatus(
|
||||||
context,
|
context,
|
||||||
@@ -2673,7 +2706,6 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
message.isContactMessage &&
|
message.isContactMessage &&
|
||||||
message.deliveryStatus ==
|
message.deliveryStatus ==
|
||||||
MessageDeliveryStatus.delivered &&
|
MessageDeliveryStatus.delivered &&
|
||||||
_showReceivedStats &&
|
|
||||||
message.roundTripTimeMs != null;
|
message.roundTripTimeMs != null;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -2779,10 +2811,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (shouldShowSentChannelStats(
|
if (shouldShowSentChannelStats(message)) ...[
|
||||||
message,
|
|
||||||
showReceivedStats: _showReceivedStats,
|
|
||||||
)) ...[
|
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
buildChannelEchoStatus(context, message),
|
buildChannelEchoStatus(context, message),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -92,10 +92,7 @@ Widget buildChannelEchoStatus(BuildContext context, Message message) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldShowSentChannelStats(
|
bool shouldShowSentChannelStats(Message message) {
|
||||||
Message message, {
|
|
||||||
required bool showReceivedStats,
|
|
||||||
}) {
|
|
||||||
if (!message.isSentMessage || !message.isChannelMessage) {
|
if (!message.isSentMessage || !message.isChannelMessage) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -105,7 +102,7 @@ bool shouldShowSentChannelStats(
|
|||||||
message.lastEchoRssiDbm != null ||
|
message.lastEchoRssiDbm != null ||
|
||||||
message.lastEchoSnrRaw != null ||
|
message.lastEchoSnrRaw != null ||
|
||||||
message.expectedAckTag != null;
|
message.expectedAckTag != null;
|
||||||
return showReceivedStats && hasSignalData;
|
return hasSignalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildReceivedSignalStatus(
|
Widget buildReceivedSignalStatus(
|
||||||
|
|||||||
199
test/widgets/message_bubble_test.dart
Normal file
199
test/widgets/message_bubble_test.dart
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||||
|
import 'package:meshcore_sar_app/models/message.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/app_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/channels_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/drawing_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/image_provider.dart' as ip;
|
||||||
|
import 'package:meshcore_sar_app/providers/messages_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/voice_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/services/voice_codec_service.dart';
|
||||||
|
import 'package:meshcore_sar_app/services/voice_player_service.dart';
|
||||||
|
import 'package:meshcore_sar_app/widgets/messages/message_bubble.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('received bubbles show signal chips without tapping', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final harness = await _TestHarness.create();
|
||||||
|
try {
|
||||||
|
final message = Message(
|
||||||
|
id: 'received-signal',
|
||||||
|
messageType: MessageType.contact,
|
||||||
|
senderPublicKeyPrefix: _prefix(1),
|
||||||
|
pathLen: 1,
|
||||||
|
textType: MessageTextType.plain,
|
||||||
|
senderTimestamp: 1700000000,
|
||||||
|
text: 'Inbound message',
|
||||||
|
receivedAt: DateTime.fromMillisecondsSinceEpoch(1700000000500),
|
||||||
|
deliveryStatus: MessageDeliveryStatus.received,
|
||||||
|
lastEchoRssiDbm: -84,
|
||||||
|
lastEchoSnrRaw: 24,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildApp(harness, message));
|
||||||
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
|
|
||||||
|
expect(find.text('1 hop'), findsOneWidget);
|
||||||
|
expect(find.text('Fair'), findsOneWidget);
|
||||||
|
expect(find.text('-84'), findsOneWidget);
|
||||||
|
expect(find.text('6.0'), findsOneWidget);
|
||||||
|
} finally {
|
||||||
|
await _disposeHarness(tester, harness);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('delivered direct bubbles show timing chips without tapping', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final harness = await _TestHarness.create();
|
||||||
|
try {
|
||||||
|
final message = Message(
|
||||||
|
id: 'sent-direct-signal',
|
||||||
|
messageType: MessageType.contact,
|
||||||
|
senderPublicKeyPrefix: _prefix(11),
|
||||||
|
recipientPublicKey: _key(77),
|
||||||
|
pathLen: 0,
|
||||||
|
textType: MessageTextType.plain,
|
||||||
|
senderTimestamp: 1700000000,
|
||||||
|
text: 'Outbound message',
|
||||||
|
receivedAt: DateTime.fromMillisecondsSinceEpoch(1700000000500),
|
||||||
|
deliveryStatus: MessageDeliveryStatus.delivered,
|
||||||
|
roundTripTimeMs: 320,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildApp(harness, message));
|
||||||
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
|
|
||||||
|
expect(find.text('Direct'), findsOneWidget);
|
||||||
|
expect(find.text('320ms'), findsOneWidget);
|
||||||
|
} finally {
|
||||||
|
await _disposeHarness(tester, harness);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('sent channel bubbles show echo chips without tapping', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final harness = await _TestHarness.create();
|
||||||
|
try {
|
||||||
|
final message = Message(
|
||||||
|
id: 'sent-channel-signal',
|
||||||
|
messageType: MessageType.channel,
|
||||||
|
senderPublicKeyPrefix: _prefix(21),
|
||||||
|
channelIdx: 0,
|
||||||
|
pathLen: 0,
|
||||||
|
textType: MessageTextType.plain,
|
||||||
|
senderTimestamp: 1700000000,
|
||||||
|
text: 'Broadcast message',
|
||||||
|
receivedAt: DateTime.fromMillisecondsSinceEpoch(1700000000500),
|
||||||
|
deliveryStatus: MessageDeliveryStatus.sent,
|
||||||
|
echoCount: 2,
|
||||||
|
lastEchoRssiDbm: -76,
|
||||||
|
lastEchoSnrRaw: 20,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildApp(harness, message));
|
||||||
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
|
|
||||||
|
expect(find.text('x2'), findsOneWidget);
|
||||||
|
expect(find.text('-76'), findsOneWidget);
|
||||||
|
expect(find.text('5.0'), findsOneWidget);
|
||||||
|
} finally {
|
||||||
|
await _disposeHarness(tester, harness);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildApp(_TestHarness harness, Message message) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: harness.connectionProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.contactsProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.messagesProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.drawingProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.channelsProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.voiceProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.imageProvider),
|
||||||
|
ChangeNotifierProvider.value(value: harness.appProvider),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: Scaffold(body: MessageBubble(message: message)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestHarness {
|
||||||
|
final connectionProvider = ConnectionProvider();
|
||||||
|
final contactsProvider = ContactsProvider();
|
||||||
|
final messagesProvider = MessagesProvider();
|
||||||
|
final drawingProvider = DrawingProvider();
|
||||||
|
final channelsProvider = ChannelsProvider()..initializePublicChannel();
|
||||||
|
final voiceProvider = VoiceProvider(
|
||||||
|
codec: VoiceCodecService(),
|
||||||
|
player: VoicePlayerService(),
|
||||||
|
);
|
||||||
|
final imageProvider = ip.ImageProvider();
|
||||||
|
|
||||||
|
late final AppProvider appProvider;
|
||||||
|
|
||||||
|
static Future<_TestHarness> create() async {
|
||||||
|
final harness = _TestHarness();
|
||||||
|
await harness.messagesProvider.initialize();
|
||||||
|
await harness.drawingProvider.initialize();
|
||||||
|
harness.appProvider = AppProvider(
|
||||||
|
connectionProvider: harness.connectionProvider,
|
||||||
|
contactsProvider: harness.contactsProvider,
|
||||||
|
messagesProvider: harness.messagesProvider,
|
||||||
|
drawingProvider: harness.drawingProvider,
|
||||||
|
channelsProvider: harness.channelsProvider,
|
||||||
|
voiceProvider: harness.voiceProvider,
|
||||||
|
imageProvider: harness.imageProvider,
|
||||||
|
);
|
||||||
|
return harness;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isDisposed = false;
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
appProvider.dispose();
|
||||||
|
voiceProvider.dispose();
|
||||||
|
imageProvider.dispose();
|
||||||
|
drawingProvider.dispose();
|
||||||
|
messagesProvider.dispose();
|
||||||
|
contactsProvider.dispose();
|
||||||
|
connectionProvider.dispose();
|
||||||
|
channelsProvider.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _prefix(int seed) =>
|
||||||
|
Uint8List.fromList(List<int>.generate(6, (index) => seed + index));
|
||||||
|
|
||||||
|
Uint8List _key(int seed) =>
|
||||||
|
Uint8List.fromList(List<int>.generate(32, (index) => seed + index));
|
||||||
|
|
||||||
|
Future<void> _disposeHarness(WidgetTester tester, _TestHarness harness) async {
|
||||||
|
await tester.pumpWidget(const SizedBox.shrink());
|
||||||
|
harness.dispose();
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user