mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add new contact and message tracking features, including unread status and removal functionality
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:latlong2/latlong.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../models/room_login_state.dart';
|
||||
import '../../providers/connection_provider.dart';
|
||||
import '../../providers/contacts_provider.dart';
|
||||
import '../../providers/map_provider.dart';
|
||||
import 'direct_message_sheet.dart';
|
||||
import 'room_login_sheet.dart';
|
||||
@@ -66,7 +67,22 @@ class ContactTile extends StatelessWidget {
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
// Room login status indicator badge
|
||||
// New contact indicator badge (top-right)
|
||||
if (contact.isNew)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Room login status indicator badge (bottom-right)
|
||||
if (contact.type == ContactType.room && roomLoginState != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
@@ -361,6 +377,62 @@ class ContactTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showDeleteConfirmation(BuildContext context, Contact contact) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Delete Contact'),
|
||||
content: Text(
|
||||
'Are you sure you want to delete "${contact.displayName}"?\n\n'
|
||||
'This will remove the contact from both the app and the companion radio device.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context); // Close confirmation dialog
|
||||
Navigator.pop(context); // Close contact details sheet
|
||||
await _deleteContact(context, contact);
|
||||
},
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteContact(BuildContext context, Contact contact) async {
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
final contactsProvider = context.read<ContactsProvider>();
|
||||
|
||||
try {
|
||||
// Show loading toast
|
||||
ToastLogger.info(context, 'Removing ${contact.displayName}...');
|
||||
|
||||
// Remove contact from provider (which will also remove from device)
|
||||
await contactsProvider.removeContact(
|
||||
contact.publicKeyHex,
|
||||
onRemoveFromDevice: (publicKey) async {
|
||||
if (connectionProvider.deviceInfo.isConnected) {
|
||||
await connectionProvider.removeContact(publicKey);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
ToastLogger.success(context, 'Contact "${contact.displayName}" removed');
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ToastLogger.error(context, 'Failed to remove contact: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showContactDetails(BuildContext context, Contact contact) {
|
||||
// Get room login state
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
@@ -684,6 +756,23 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
// Delete Contact button (for all contact types except Public Channel)
|
||||
if (contact.advName != 'Public Channel') ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _showDeleteConfirmation(context, contact),
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
label: const Text('Delete Contact'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: const BorderSide(color: Colors.red),
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../models/message.dart';
|
||||
import '../../providers/connection_provider.dart';
|
||||
import '../../providers/messages_provider.dart';
|
||||
import '../../utils/toast_logger.dart';
|
||||
|
||||
class DirectMessageSheet extends StatefulWidget {
|
||||
@@ -44,6 +47,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
||||
if (text.isEmpty) return;
|
||||
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
final messagesProvider = context.read<MessagesProvider>();
|
||||
|
||||
if (!connectionProvider.deviceInfo.isConnected) {
|
||||
if (!mounted) return;
|
||||
@@ -52,13 +56,44 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
||||
}
|
||||
|
||||
try {
|
||||
// Create message ID
|
||||
final messageId = '${DateTime.now().millisecondsSinceEpoch}_dm_sent';
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// Get current device's public key (first 6 bytes)
|
||||
final devicePublicKey = connectionProvider.deviceInfo.publicKey;
|
||||
final senderPublicKeyPrefix = devicePublicKey?.sublist(0, 6);
|
||||
|
||||
// Create sent message object with recipient public key for retry support
|
||||
final sentMessage = Message(
|
||||
id: messageId,
|
||||
messageType: MessageType.contact,
|
||||
senderPublicKeyPrefix: senderPublicKeyPrefix,
|
||||
pathLen: 0,
|
||||
textType: MessageTextType.plain,
|
||||
senderTimestamp: timestamp,
|
||||
text: text,
|
||||
receivedAt: DateTime.now(),
|
||||
deliveryStatus: MessageDeliveryStatus.sending,
|
||||
recipientPublicKey: widget.contact.publicKey, // Store recipient for retry
|
||||
);
|
||||
|
||||
// Add to messages list with "sending" status
|
||||
messagesProvider.addSentMessage(sentMessage);
|
||||
|
||||
// Send direct message to contact (include contact for path logging)
|
||||
await connectionProvider.sendTextMessage(
|
||||
final sentSuccessfully = await connectionProvider.sendTextMessage(
|
||||
contactPublicKey: widget.contact.publicKey,
|
||||
text: text,
|
||||
messageId: messageId, // Pass message ID for tracking
|
||||
contact: widget.contact,
|
||||
);
|
||||
|
||||
if (!sentSuccessfully) {
|
||||
// Mark message as failed if sending failed
|
||||
messagesProvider.markMessageFailed(messageId);
|
||||
}
|
||||
|
||||
_textController.clear();
|
||||
_focusNode.unfocus();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user