mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Match sensors bg and hide counts
This commit is contained in:
@@ -7,7 +7,6 @@ import '../models/contact.dart';
|
||||
import '../providers/contacts_provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../providers/messages_provider.dart';
|
||||
import '../widgets/contacts/contact_tile.dart';
|
||||
import '../widgets/contacts/add_channel_dialog.dart';
|
||||
|
||||
@@ -207,8 +206,8 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
body: Consumer2<ContactsProvider, MessagesProvider>(
|
||||
builder: (context, contactsProvider, messagesProvider, child) {
|
||||
body: Consumer<ContactsProvider>(
|
||||
builder: (context, contactsProvider, child) {
|
||||
final chatContacts = _sortContactsByDistance(
|
||||
contactsProvider.chatContacts,
|
||||
);
|
||||
@@ -292,10 +291,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
formatDistance: _formatDistance,
|
||||
onNavigateToMap: widget.onNavigateToMap,
|
||||
onNavigateToMessages: widget.onNavigateToMessages,
|
||||
messageCount: messagesProvider
|
||||
.getMessageCountForDestination(contact),
|
||||
unreadMessageCount: messagesProvider
|
||||
.getUnreadCountForDestination(contact),
|
||||
),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
@@ -316,10 +311,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
formatDistance: _formatDistance,
|
||||
onNavigateToMap: widget.onNavigateToMap,
|
||||
onNavigateToMessages: widget.onNavigateToMessages,
|
||||
messageCount: messagesProvider
|
||||
.getMessageCountForDestination(contact),
|
||||
unreadMessageCount: messagesProvider
|
||||
.getUnreadCountForDestination(contact),
|
||||
),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
@@ -340,10 +331,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
formatDistance: _formatDistance,
|
||||
onNavigateToMap: widget.onNavigateToMap,
|
||||
onNavigateToMessages: widget.onNavigateToMessages,
|
||||
messageCount: messagesProvider
|
||||
.getMessageCountForDestination(contact),
|
||||
unreadMessageCount: messagesProvider
|
||||
.getUnreadCountForDestination(contact),
|
||||
),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
@@ -364,10 +351,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
formatDistance: _formatDistance,
|
||||
onNavigateToMap: widget.onNavigateToMap,
|
||||
onNavigateToMessages: widget.onNavigateToMessages,
|
||||
messageCount: messagesProvider
|
||||
.getMessageCountForDestination(contact),
|
||||
unreadMessageCount: messagesProvider
|
||||
.getUnreadCountForDestination(contact),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -371,6 +371,7 @@ class _SensorCard extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final telemetry = contact?.telemetry;
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final metrics = contact == null || telemetry == null
|
||||
? const <_MetricCardData>[]
|
||||
: _buildMetricCards(l10n, telemetry, contact!);
|
||||
@@ -378,16 +379,23 @@ class _SensorCard extends StatelessWidget {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surfaceContainerLow,
|
||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.14),
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 8),
|
||||
color: Colors.black.withValues(alpha: 0.045),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -24,8 +24,6 @@ class ContactTile extends StatelessWidget {
|
||||
final String Function(double)? formatDistance;
|
||||
final VoidCallback? onNavigateToMap;
|
||||
final VoidCallback? onNavigateToMessages;
|
||||
final int messageCount;
|
||||
final int unreadMessageCount;
|
||||
|
||||
const ContactTile({
|
||||
super.key,
|
||||
@@ -35,8 +33,6 @@ class ContactTile extends StatelessWidget {
|
||||
this.formatDistance,
|
||||
this.onNavigateToMap,
|
||||
this.onNavigateToMessages,
|
||||
this.messageCount = 0,
|
||||
this.unreadMessageCount = 0,
|
||||
});
|
||||
|
||||
/// Get localized time since last seen
|
||||
@@ -240,13 +236,6 @@ class ContactTile extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (messageCount > 0) ...[
|
||||
const SizedBox(width: 8),
|
||||
_MessageCountBadge(
|
||||
totalCount: messageCount,
|
||||
unreadCount: unreadMessageCount,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
Text(timeAgoText, style: timeAgoStyle),
|
||||
if (isPingInProgress) ...[
|
||||
@@ -321,21 +310,22 @@ class ContactTile extends StatelessWidget {
|
||||
_showSetRouteDialog(context, contact);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(
|
||||
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
if (!contact.isPublicChannel)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(
|
||||
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
if (contact.isChannel) {
|
||||
_showDeleteChannelDialog(context, contact);
|
||||
} else {
|
||||
_showDeleteConfirmation(context, contact);
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
if (contact.isChannel) {
|
||||
_showDeleteChannelDialog(context, contact);
|
||||
} else {
|
||||
_showDeleteConfirmation(context, contact);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -373,7 +363,11 @@ class ContactTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showDeleteConfirmation(BuildContext context, Contact contact) {
|
||||
void _showDeleteConfirmation(
|
||||
BuildContext context,
|
||||
Contact contact, {
|
||||
bool closeDetailsSheetOnDelete = false,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
@@ -391,7 +385,9 @@ class ContactTile extends StatelessWidget {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context); // Close confirmation dialog
|
||||
Navigator.pop(context); // Close contact details sheet
|
||||
if (closeDetailsSheetOnDelete) {
|
||||
Navigator.pop(context); // Close contact details sheet
|
||||
}
|
||||
await _deleteContact(context, contact);
|
||||
},
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
@@ -895,7 +891,11 @@ class ContactTile extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
_showDeleteConfirmation(context, contact);
|
||||
_showDeleteConfirmation(
|
||||
context,
|
||||
contact,
|
||||
closeDetailsSheetOnDelete: true,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
label: Text(
|
||||
@@ -1256,6 +1256,10 @@ class ContactTile extends StatelessWidget {
|
||||
Contact contact, {
|
||||
bool closeDetailsSheetOnDelete = false,
|
||||
}) {
|
||||
if (contact.isPublicChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
showDialog(
|
||||
@@ -1390,59 +1394,3 @@ class ContactTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageCountBadge extends StatelessWidget {
|
||||
final int totalCount;
|
||||
final int unreadCount;
|
||||
|
||||
const _MessageCountBadge({
|
||||
required this.totalCount,
|
||||
required this.unreadCount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final hasUnread = unreadCount > 0;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: hasUnread
|
||||
? colorScheme.primaryContainer
|
||||
: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: hasUnread
|
||||
? colorScheme.primary.withValues(alpha: 0.35)
|
||||
: colorScheme.outlineVariant.withValues(alpha: 0.45),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (hasUnread) ...[
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
],
|
||||
Text(
|
||||
hasUnread ? '$unreadCount/$totalCount' : '$totalCount',
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: hasUnread
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,8 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
? l10n.broadcastToAllNearby
|
||||
: '${l10n.channel} ${channel.publicKey[1]}', // Show slot number
|
||||
unreadCount:
|
||||
widget.unreadCountsByPublicKey[channel.publicKeyHex] ??
|
||||
widget.unreadCountsByPublicKey[channel
|
||||
.publicKeyHex] ??
|
||||
0,
|
||||
isSelected: _isSelected('channel', channel),
|
||||
onTap: () {
|
||||
@@ -240,7 +241,8 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
title: contact.displayName,
|
||||
subtitle: contact.publicKeyShort,
|
||||
unreadCount:
|
||||
widget.unreadCountsByPublicKey[contact.publicKeyHex] ??
|
||||
widget.unreadCountsByPublicKey[contact
|
||||
.publicKeyHex] ??
|
||||
0,
|
||||
isSelected: _isSelected('contact', contact),
|
||||
onTap: () {
|
||||
@@ -288,7 +290,8 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
title: room.displayName,
|
||||
subtitle: room.publicKeyShort,
|
||||
unreadCount:
|
||||
widget.unreadCountsByPublicKey[room.publicKeyHex] ?? 0,
|
||||
widget.unreadCountsByPublicKey[room.publicKeyHex] ??
|
||||
0,
|
||||
isSelected: _isSelected('room', room),
|
||||
onTap: () {
|
||||
widget.onSelect('room', room);
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2026.0308.1+21
|
||||
version: 2026.0308.3+23
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
74
test/widgets/recipient_selector_sheet_test.dart
Normal file
74
test/widgets/recipient_selector_sheet_test.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
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/contact.dart';
|
||||
import 'package:meshcore_sar_app/widgets/messages/recipient_selector_sheet.dart';
|
||||
|
||||
void main() {
|
||||
Contact buildContact({
|
||||
required String name,
|
||||
required ContactType type,
|
||||
int secondByte = 0,
|
||||
}) {
|
||||
final publicKey = Uint8List(32);
|
||||
publicKey[1] = secondByte;
|
||||
|
||||
return Contact(
|
||||
publicKey: publicKey,
|
||||
type: type,
|
||||
flags: 0,
|
||||
outPathLen: 0,
|
||||
outPath: Uint8List(0),
|
||||
advName: name,
|
||||
lastAdvert: 0,
|
||||
advLat: 0,
|
||||
advLon: 0,
|
||||
lastMod: 0,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pumpSheet(WidgetTester tester) async {
|
||||
final channel = buildContact(
|
||||
name: 'Ops',
|
||||
type: ContactType.channel,
|
||||
secondByte: 3,
|
||||
);
|
||||
final contact = buildContact(name: 'John Smith', type: ContactType.chat);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: RecipientSelectorSheet(
|
||||
contacts: [contact],
|
||||
rooms: const [],
|
||||
channels: [channel],
|
||||
unreadCount: 11,
|
||||
unreadCountsByPublicKey: {
|
||||
channel.publicKeyHex: 7,
|
||||
contact.publicKeyHex: 3,
|
||||
},
|
||||
currentDestinationType: 'all',
|
||||
onSelect: (_, __) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders unread badges for all and destination filters', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpSheet(tester);
|
||||
|
||||
expect(find.byKey(const Key('unread-badge-11')), findsOneWidget);
|
||||
expect(find.byKey(const Key('unread-badge-7')), findsOneWidget);
|
||||
expect(find.byKey(const Key('unread-badge-3')), findsOneWidget);
|
||||
expect(find.text('11'), findsOneWidget);
|
||||
expect(find.text('7'), findsOneWidget);
|
||||
expect(find.text('3'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user