feat: Enhance drawing functionality with sender tracking and sharing capabilities

This commit is contained in:
Janez T
2025-10-15 22:41:57 +02:00
parent 317addba9e
commit 800786ec9c
12 changed files with 618 additions and 107 deletions

View File

@@ -1172,50 +1172,53 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
children: [
// Drawing toolbar
const DrawingToolbar(),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'center_map',
onPressed: !_isMapReady ? null : () async {
// Force update GPS location and jump to it
final position = await _locationService.getCurrentPosition();
if (position != null && mounted) {
setState(() {
// Position updated in service
});
_mapController.move(
LatLng(position.latitude, position.longitude),
16,
);
} else {
// Fallback to cached position or default center
final currentPosition = _locationService.currentPosition;
if (currentPosition != null) {
// Hide other buttons when in drawing mode
if (!drawingProvider.isDrawing) ...[
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'center_map',
onPressed: !_isMapReady ? null : () async {
// Force update GPS location and jump to it
final position = await _locationService.getCurrentPosition();
if (position != null && mounted) {
setState(() {
// Position updated in service
});
_mapController.move(
LatLng(
currentPosition.latitude,
currentPosition.longitude,
),
LatLng(position.latitude, position.longitude),
16,
);
} else {
_mapController.move(center, _defaultZoom);
// Fallback to cached position or default center
final currentPosition = _locationService.currentPosition;
if (currentPosition != null) {
_mapController.move(
LatLng(
currentPosition.latitude,
currentPosition.longitude,
),
16,
);
} else {
_mapController.move(center, _defaultZoom);
}
}
}
},
child: const Icon(Icons.my_location),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'layer_selector',
onPressed: () => _showLayerSelector(context),
child: const Icon(Icons.layers),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'options_menu',
onPressed: () => _showOptionsMenu(context),
child: const Icon(Icons.more_vert),
},
child: const Icon(Icons.my_location),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'layer_selector',
onPressed: () => _showLayerSelector(context),
child: const Icon(Icons.layers),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'options_menu',
onPressed: () => _showOptionsMenu(context),
child: const Icon(Icons.more_vert),
),
],
],
),
),

View File

@@ -10,6 +10,7 @@ import '../providers/connection_provider.dart';
import '../models/message.dart';
import '../models/sar_marker.dart';
import '../widgets/messages/sar_update_sheet.dart';
import '../widgets/contacts/direct_message_sheet.dart';
import '../utils/toast_logger.dart';
class MessagesTab extends StatefulWidget {
@@ -507,6 +508,16 @@ class _MessageBubble extends StatelessWidget {
}
void _showMessageOptions(BuildContext context) {
// Determine if this is own message
final connectionProvider = context.read<ConnectionProvider>();
final selfPublicKey = connectionProvider.deviceInfo.publicKey;
final isOwnMessage = message.isSentMessage || message.isFromSelf(selfPublicKey);
// Check if we can reply to this message (must be contact message from someone else)
final canReply = message.isContactMessage &&
!isOwnMessage &&
message.senderPublicKeyPrefix != null;
showModalBottomSheet(
context: context,
backgroundColor: Theme.of(context).colorScheme.surface,
@@ -518,6 +529,16 @@ class _MessageBubble extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Reply option (only for contact messages from others)
if (canReply)
ListTile(
leading: const Icon(Icons.reply),
title: const Text('Reply'),
onTap: () {
Navigator.pop(context);
_showReplySheet(context);
},
),
// Copy text option
ListTile(
leading: const Icon(Icons.copy),
@@ -543,6 +564,39 @@ class _MessageBubble extends StatelessWidget {
);
}
void _showReplySheet(BuildContext context) {
// Find the sender contact by public key prefix
final contactsProvider = context.read<ContactsProvider>();
if (message.senderPublicKeyPrefix == null) {
ToastLogger.error(context, 'Cannot reply: sender information missing');
return;
}
// Find contact by public key prefix (first 6 bytes)
final senderKeyHex = message.senderPublicKeyPrefix!
.sublist(0, message.senderPublicKeyPrefix!.length < 6 ? message.senderPublicKeyPrefix!.length : 6)
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join('');
final senderContact = contactsProvider.contacts.where((c) {
return c.publicKeyHex.startsWith(senderKeyHex);
}).firstOrNull;
if (senderContact == null) {
ToastLogger.error(context, 'Cannot reply: contact not found');
return;
}
// Show direct message sheet
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => DirectMessageSheet(contact: senderContact),
);
}
void _showDeleteConfirmation(BuildContext context) {
showDialog(
context: context,