mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Fix null check crash in TabBar
This commit is contained in:
@@ -59,6 +59,11 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
return Consumer<ip.ImageProvider>(
|
||||
builder: (context, imageProvider, _) {
|
||||
final session = imageProvider.session(envelope.sessionId);
|
||||
final sender = _resolveSender(envelope);
|
||||
final effectivePathLen =
|
||||
sender != null && sender.outPathLen >= 0
|
||||
? sender.outPathLen
|
||||
: widget.message.pathLen;
|
||||
final isComplete = imageProvider.isComplete(envelope.sessionId);
|
||||
final eta = imageProvider.estimateRemainingTransferTime(
|
||||
envelope.sessionId,
|
||||
@@ -98,6 +103,7 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
pathLen: effectivePathLen,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -109,13 +115,13 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
received: received,
|
||||
total: total,
|
||||
envelope: envelope,
|
||||
pathLen: widget.message.pathLen,
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
error: _errorText,
|
||||
isSentByMe: widget.isSentByMe,
|
||||
eta: eta,
|
||||
pathLen: effectivePathLen,
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
@@ -143,6 +149,7 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
required int? radioBw,
|
||||
required int? radioSf,
|
||||
required int? radioCr,
|
||||
required int pathLen,
|
||||
}) {
|
||||
if (isComplete && imageBytes != null) {
|
||||
return AspectRatio(
|
||||
@@ -184,7 +191,7 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
pathLen: widget.message.pathLen,
|
||||
pathLen: pathLen,
|
||||
),
|
||||
icon: const Icon(Icons.download_rounded, size: 40),
|
||||
color: Colors.white70,
|
||||
@@ -298,12 +305,13 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
if (!mounted) return;
|
||||
|
||||
// Timeout = 2× estimated LoRa airtime (min 30s).
|
||||
final effectivePathLen = sender.outPathLen >= 0 ? sender.outPathLen : pathLen;
|
||||
final txEstimate = estimateImageTransmitDuration(
|
||||
fragmentCount: missing.isEmpty ? envelope.total : missing.length,
|
||||
sizeBytes: missing.isEmpty
|
||||
? envelope.sizeBytes
|
||||
: (envelope.sizeBytes * missing.length / envelope.total).round(),
|
||||
pathLen: pathLen,
|
||||
pathLen: effectivePathLen,
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
@@ -335,6 +343,19 @@ class _ImageMessageBubbleState extends State<ImageMessageBubble> {
|
||||
);
|
||||
if (contact != null) return contact;
|
||||
|
||||
// For sent direct messages, fetch must target the recipient peer.
|
||||
final recipientKey = widget.message.recipientPublicKey;
|
||||
if (widget.isSentByMe && recipientKey != null && recipientKey.isNotEmpty) {
|
||||
final byKey = contactsProvider.findContactByKey(recipientKey);
|
||||
if (byKey != null) return byKey;
|
||||
if (recipientKey.length >= 6) {
|
||||
final byPrefix = contactsProvider.findContactByPrefix(
|
||||
Uint8List.fromList(recipientKey.sublist(0, 6)),
|
||||
);
|
||||
if (byPrefix != null) return byPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
final senderName = widget.message.senderName?.trim();
|
||||
if (senderName != null && senderName.isNotEmpty) {
|
||||
for (final c in contactsProvider.contacts) {
|
||||
|
||||
@@ -22,6 +22,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final event = TicTacToeMessageParser.tryParse(message.text);
|
||||
if (event == null) {
|
||||
return const SizedBox.shrink();
|
||||
@@ -82,6 +83,12 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
|
||||
final mySymbol = selfKey6 == state.xPlayerKey6 ? 'X' : 'O';
|
||||
final isMyTurn = !state.isFinished && state.nextSymbol == mySymbol;
|
||||
final titleColor = isSentByMe
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurface;
|
||||
final statusColor = isSentByMe
|
||||
? colorScheme.onPrimaryContainer.withValues(alpha: 0.85)
|
||||
: colorScheme.onSurface.withValues(alpha: 0.85);
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 230),
|
||||
@@ -92,12 +99,16 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
'Tic-Tac-Toe · Game ${state.gameId}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
).textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_BoardGrid(
|
||||
board: state.board,
|
||||
enabled: isMyTurn,
|
||||
isSentByMe: isSentByMe,
|
||||
onTapCell: (idx) => _onCellTap(
|
||||
context: context,
|
||||
idx: idx,
|
||||
@@ -110,7 +121,9 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_statusText(state: state, mySymbol: mySymbol),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.copyWith(color: statusColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -221,16 +234,26 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
class _BoardGrid extends StatelessWidget {
|
||||
final List<String?> board;
|
||||
final bool enabled;
|
||||
final bool isSentByMe;
|
||||
final ValueChanged<int> onTapCell;
|
||||
|
||||
const _BoardGrid({
|
||||
required this.board,
|
||||
required this.enabled,
|
||||
required this.isSentByMe,
|
||||
required this.onTapCell,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final cellBackground = isSentByMe
|
||||
? colorScheme.primaryContainer.withValues(alpha: 0.35)
|
||||
: colorScheme.surface;
|
||||
final cellBorder = isSentByMe
|
||||
? colorScheme.primary.withValues(alpha: 0.45)
|
||||
: colorScheme.outline.withValues(alpha: 0.35);
|
||||
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
height: 180,
|
||||
@@ -251,12 +274,18 @@ class _BoardGrid extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
color: cellBackground,
|
||||
border: Border.all(color: cellBorder),
|
||||
),
|
||||
child: Text(
|
||||
value ?? '',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: value == 'X'
|
||||
? colorScheme.primary
|
||||
: value == 'O'
|
||||
? colorScheme.tertiary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -57,6 +57,11 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
builder: (context, voiceProvider, _) {
|
||||
final session = voiceProvider.session(voiceId);
|
||||
final envelope = VoiceEnvelope.tryParseText(widget.message.text);
|
||||
final sender = _resolveSenderContact();
|
||||
final effectivePathLen =
|
||||
sender != null && sender.outPathLen >= 0
|
||||
? sender.outPathLen
|
||||
: widget.message.pathLen;
|
||||
final isPlaying = voiceProvider.isPlaying(voiceId);
|
||||
final isComplete = voiceProvider.isComplete(voiceId);
|
||||
|
||||
@@ -96,7 +101,7 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
session: session,
|
||||
envelope: envelope,
|
||||
messageText: widget.message.text,
|
||||
pathLen: widget.message.pathLen,
|
||||
pathLen: effectivePathLen,
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
@@ -123,7 +128,7 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
pathLen: widget.message.pathLen,
|
||||
pathLen: effectivePathLen,
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
@@ -280,12 +285,13 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
}
|
||||
|
||||
// Timeout = 2× estimated LoRa airtime (min 30s).
|
||||
final effectivePathLen = sender.outPathLen >= 0 ? sender.outPathLen : pathLen;
|
||||
final txEstimate = envelope != null
|
||||
? estimateVoiceTransmitDuration(
|
||||
packetCount: envelope.total,
|
||||
mode: envelope.mode,
|
||||
durationMs: envelope.durationMs,
|
||||
pathLen: pathLen,
|
||||
pathLen: effectivePathLen,
|
||||
radioBw: radioBw,
|
||||
radioSf: radioSf,
|
||||
radioCr: radioCr,
|
||||
@@ -329,6 +335,19 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
if (contact != null) return contact;
|
||||
}
|
||||
|
||||
// For sent direct messages, fetch must target the recipient peer.
|
||||
final recipientKey = widget.message.recipientPublicKey;
|
||||
if (widget.isSentByMe && recipientKey != null && recipientKey.isNotEmpty) {
|
||||
final byKey = contactsProvider.findContactByKey(recipientKey);
|
||||
if (byKey != null) return byKey;
|
||||
if (recipientKey.length >= 6) {
|
||||
final byPrefix = contactsProvider.findContactByPrefix(
|
||||
Uint8List.fromList(recipientKey.sublist(0, 6)),
|
||||
);
|
||||
if (byPrefix != null) return byPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
final senderName = widget.message.senderName?.trim();
|
||||
if (senderName != null && senderName.isNotEmpty) {
|
||||
for (final contact in contactsProvider.contacts) {
|
||||
|
||||
Reference in New Issue
Block a user