mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
Add message filters and counts
This commit is contained in:
@@ -193,6 +193,59 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
.where((m) => !m.isRead && !m.isSentMessage && !m.isSystemMessage)
|
.where((m) => !m.isRead && !m.isSentMessage && !m.isSystemMessage)
|
||||||
.length;
|
.length;
|
||||||
|
|
||||||
|
int getMessageCountForDestination(Contact contact) => _messages
|
||||||
|
.where((message) => _isMessageForDestination(message, contact))
|
||||||
|
.length;
|
||||||
|
|
||||||
|
int getUnreadCountForDestination(Contact contact) => _messages
|
||||||
|
.where(
|
||||||
|
(message) =>
|
||||||
|
_isMessageForDestination(message, contact) &&
|
||||||
|
!message.isRead &&
|
||||||
|
!message.isSentMessage &&
|
||||||
|
!message.isSystemMessage,
|
||||||
|
)
|
||||||
|
.length;
|
||||||
|
|
||||||
|
bool _isMessageForDestination(Message message, Contact contact) {
|
||||||
|
if (message.isSystemMessage) return false;
|
||||||
|
|
||||||
|
if (contact.isChannel) {
|
||||||
|
final channelIdx = contact.publicKey.length > 1
|
||||||
|
? contact.publicKey[1]
|
||||||
|
: 0;
|
||||||
|
return message.isChannelMessage &&
|
||||||
|
(message.channelIdx ?? 0) == channelIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.isContactMessage || contact.publicKey.length < 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final selectedPrefix = contact.publicKey.sublist(0, 6);
|
||||||
|
|
||||||
|
if (message.recipientPublicKey != null &&
|
||||||
|
message.recipientPublicKey!.length >= 6 &&
|
||||||
|
_matchesPrefix(message.recipientPublicKey!, selectedPrefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.senderPublicKeyPrefix != null &&
|
||||||
|
message.senderPublicKeyPrefix!.length >= 6 &&
|
||||||
|
_matchesPrefix(message.senderPublicKeyPrefix!, selectedPrefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _matchesPrefix(Uint8List key, Uint8List prefix) {
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
if (key[i] != prefix[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize and load persisted messages
|
/// Initialize and load persisted messages
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../models/contact.dart';
|
|||||||
import '../providers/contacts_provider.dart';
|
import '../providers/contacts_provider.dart';
|
||||||
import '../providers/app_provider.dart';
|
import '../providers/app_provider.dart';
|
||||||
import '../providers/connection_provider.dart';
|
import '../providers/connection_provider.dart';
|
||||||
|
import '../providers/messages_provider.dart';
|
||||||
import '../widgets/contacts/contact_tile.dart';
|
import '../widgets/contacts/contact_tile.dart';
|
||||||
import '../widgets/contacts/add_channel_dialog.dart';
|
import '../widgets/contacts/add_channel_dialog.dart';
|
||||||
|
|
||||||
@@ -201,8 +202,8 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Consumer<ContactsProvider>(
|
body: Consumer2<ContactsProvider, MessagesProvider>(
|
||||||
builder: (context, contactsProvider, child) {
|
builder: (context, contactsProvider, messagesProvider, child) {
|
||||||
final chatContacts = _sortContactsByDistance(
|
final chatContacts = _sortContactsByDistance(
|
||||||
contactsProvider.chatContacts,
|
contactsProvider.chatContacts,
|
||||||
);
|
);
|
||||||
@@ -284,6 +285,10 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
calculateDistance: _calculateDistanceInMeters,
|
calculateDistance: _calculateDistanceInMeters,
|
||||||
formatDistance: _formatDistance,
|
formatDistance: _formatDistance,
|
||||||
onNavigateToMap: widget.onNavigateToMap,
|
onNavigateToMap: widget.onNavigateToMap,
|
||||||
|
messageCount: messagesProvider
|
||||||
|
.getMessageCountForDestination(contact),
|
||||||
|
unreadMessageCount: messagesProvider
|
||||||
|
.getUnreadCountForDestination(contact),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
@@ -303,6 +308,10 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
calculateDistance: _calculateDistanceInMeters,
|
calculateDistance: _calculateDistanceInMeters,
|
||||||
formatDistance: _formatDistance,
|
formatDistance: _formatDistance,
|
||||||
onNavigateToMap: widget.onNavigateToMap,
|
onNavigateToMap: widget.onNavigateToMap,
|
||||||
|
messageCount: messagesProvider
|
||||||
|
.getMessageCountForDestination(contact),
|
||||||
|
unreadMessageCount: messagesProvider
|
||||||
|
.getUnreadCountForDestination(contact),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
@@ -322,6 +331,10 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
calculateDistance: _calculateDistanceInMeters,
|
calculateDistance: _calculateDistanceInMeters,
|
||||||
formatDistance: _formatDistance,
|
formatDistance: _formatDistance,
|
||||||
onNavigateToMap: widget.onNavigateToMap,
|
onNavigateToMap: widget.onNavigateToMap,
|
||||||
|
messageCount: messagesProvider
|
||||||
|
.getMessageCountForDestination(contact),
|
||||||
|
unreadMessageCount: messagesProvider
|
||||||
|
.getUnreadCountForDestination(contact),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
@@ -341,6 +354,10 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
calculateDistance: _calculateDistanceInMeters,
|
calculateDistance: _calculateDistanceInMeters,
|
||||||
formatDistance: _formatDistance,
|
formatDistance: _formatDistance,
|
||||||
onNavigateToMap: widget.onNavigateToMap,
|
onNavigateToMap: widget.onNavigateToMap,
|
||||||
|
messageCount: messagesProvider
|
||||||
|
.getMessageCountForDestination(contact),
|
||||||
|
unreadMessageCount: messagesProvider
|
||||||
|
.getUnreadCountForDestination(contact),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class ContactTile extends StatelessWidget {
|
|||||||
final double Function(double, double, double, double)? calculateDistance;
|
final double Function(double, double, double, double)? calculateDistance;
|
||||||
final String Function(double)? formatDistance;
|
final String Function(double)? formatDistance;
|
||||||
final VoidCallback? onNavigateToMap;
|
final VoidCallback? onNavigateToMap;
|
||||||
|
final int messageCount;
|
||||||
|
final int unreadMessageCount;
|
||||||
|
|
||||||
const ContactTile({
|
const ContactTile({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -29,6 +31,8 @@ class ContactTile extends StatelessWidget {
|
|||||||
this.calculateDistance,
|
this.calculateDistance,
|
||||||
this.formatDistance,
|
this.formatDistance,
|
||||||
this.onNavigateToMap,
|
this.onNavigateToMap,
|
||||||
|
this.messageCount = 0,
|
||||||
|
this.unreadMessageCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Get localized time since last seen
|
/// Get localized time since last seen
|
||||||
@@ -263,6 +267,13 @@ class ContactTile extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (messageCount > 0) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_MessageCountBadge(
|
||||||
|
totalCount: messageCount,
|
||||||
|
unreadCount: unreadMessageCount,
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(timeAgoText, style: timeAgoStyle),
|
Text(timeAgoText, style: timeAgoStyle),
|
||||||
if (isPingInProgress) ...[
|
if (isPingInProgress) ...[
|
||||||
@@ -1301,3 +1312,59 @@ 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user