mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
feat: Add message location details
This commit is contained in:
@@ -516,73 +516,174 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
messenger.showSnackBar(SnackBar(content: Text(copiedMessage)));
|
||||
}
|
||||
|
||||
String _locationSharingErrorMessage(Object error) {
|
||||
final message = error.toString();
|
||||
if (message.startsWith('Bad state: ')) {
|
||||
return message.substring('Bad state: '.length);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
Future<void> _setChannelLocationSharing(
|
||||
BuildContext context,
|
||||
Contact channel,
|
||||
bool enabled,
|
||||
) async {
|
||||
final channelIdx = channel.publicKey.length > 1 ? channel.publicKey[1] : 0;
|
||||
try {
|
||||
final result = await context.read<AppProvider>().setChannelLocationSharingEnabled(
|
||||
channelIdx,
|
||||
enabled,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
ToastLogger.success(context, result.message);
|
||||
} catch (error) {
|
||||
if (!context.mounted) return;
|
||||
ToastLogger.error(context, _locationSharingErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleChannelLocationSharingAction(
|
||||
BuildContext context,
|
||||
Contact channel,
|
||||
) async {
|
||||
if (channel.isPublicChannel) {
|
||||
if (!context.mounted) return;
|
||||
ToastLogger.error(
|
||||
context,
|
||||
'Select a private channel to share your location.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final channelIdx = channel.publicKey.length > 1 ? channel.publicKey[1] : 0;
|
||||
if (channelIdx <= 0) {
|
||||
if (!context.mounted) return;
|
||||
ToastLogger.error(
|
||||
context,
|
||||
'Select a private channel to share your location.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final sharingState = await context
|
||||
.read<AppProvider>()
|
||||
.getChannelLocationSharingState(channelIdx);
|
||||
if (!context.mounted) return;
|
||||
await _setChannelLocationSharing(context, channel, !sharingState.isSharing);
|
||||
} catch (error) {
|
||||
if (!context.mounted) return;
|
||||
ToastLogger.error(context, _locationSharingErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
void _showChannelActionSheet(BuildContext context, Contact channel) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final canExportHashChannelPsk = Channel.isHashChannelName(
|
||||
channel.advName.trim(),
|
||||
);
|
||||
final actions = <_ChannelSheetAction>[
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.message_outlined,
|
||||
label: l10n.messages,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await _openMessagesForChannel(context, channel);
|
||||
},
|
||||
),
|
||||
if (channel.displayLocation != null)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.map_outlined,
|
||||
label: l10n.viewOnMap,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
_showChannelOnMap(context, channel);
|
||||
},
|
||||
),
|
||||
if (canExportHashChannelPsk)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.key_outlined,
|
||||
label: '${l10n.exportToClipboard} psk_base64',
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await _exportHashChannelPskBase64(context, channel);
|
||||
},
|
||||
),
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.language_rounded,
|
||||
label: l10n.setRegionScope,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
if (!context.mounted) return;
|
||||
_showRegionScopeForChannel(context, channel);
|
||||
},
|
||||
),
|
||||
if (!channel.isPublicChannel)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
label: l10n.deleteChannel,
|
||||
destructive: true,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!context.mounted) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
_showDeleteChannelDialog(context, channel);
|
||||
});
|
||||
},
|
||||
),
|
||||
];
|
||||
final channelLocationSharingFuture =
|
||||
!channel.isPublicChannel && channel.publicKey.length > 1
|
||||
? context
|
||||
.read<AppProvider>()
|
||||
.getChannelLocationSharingState(channel.publicKey[1])
|
||||
: null;
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (sheetContext) => _ChannelActionSheet(
|
||||
channel: channel,
|
||||
actions: actions,
|
||||
onClose: () => Navigator.pop(sheetContext),
|
||||
),
|
||||
builder: (sheetContext) {
|
||||
List<_ChannelSheetAction> buildActions(
|
||||
ChannelLocationSharingState? sharingState,
|
||||
) {
|
||||
return <_ChannelSheetAction>[
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.message_outlined,
|
||||
label: l10n.messages,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await _openMessagesForChannel(context, channel);
|
||||
},
|
||||
),
|
||||
if (!channel.isPublicChannel)
|
||||
_ChannelSheetAction(
|
||||
icon: sharingState?.isSharing == true
|
||||
? Icons.location_off_rounded
|
||||
: Icons.share_location_rounded,
|
||||
label: sharingState?.isSharing == true
|
||||
? 'Stop sharing my location'
|
||||
: 'Share my location',
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await _handleChannelLocationSharingAction(context, channel);
|
||||
},
|
||||
),
|
||||
if (channel.displayLocation != null)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.map_outlined,
|
||||
label: l10n.viewOnMap,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
_showChannelOnMap(context, channel);
|
||||
},
|
||||
),
|
||||
if (canExportHashChannelPsk)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.key_outlined,
|
||||
label: '${l10n.exportToClipboard} psk_base64',
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await _exportHashChannelPskBase64(context, channel);
|
||||
},
|
||||
),
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.language_rounded,
|
||||
label: l10n.setRegionScope,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
if (!context.mounted) return;
|
||||
_showRegionScopeForChannel(context, channel);
|
||||
},
|
||||
),
|
||||
if (!channel.isPublicChannel)
|
||||
_ChannelSheetAction(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
label: l10n.deleteChannel,
|
||||
destructive: true,
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!context.mounted) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
_showDeleteChannelDialog(context, channel);
|
||||
});
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Widget buildSheet(ChannelLocationSharingState? sharingState) {
|
||||
return _ChannelActionSheet(
|
||||
channel: channel,
|
||||
actions: buildActions(sharingState),
|
||||
onClose: () => Navigator.pop(sheetContext),
|
||||
);
|
||||
}
|
||||
|
||||
if (channelLocationSharingFuture == null) {
|
||||
return buildSheet(null);
|
||||
}
|
||||
|
||||
return FutureBuilder<ChannelLocationSharingState>(
|
||||
future: channelLocationSharingFuture,
|
||||
builder: (context, snapshot) {
|
||||
return buildSheet(snapshot.data);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1820,6 +1921,28 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
return l10n.daysAgo(diff.inDays);
|
||||
}
|
||||
|
||||
Widget _buildChannelLocationSharingMarker(
|
||||
BuildContext context,
|
||||
ChannelLocationSharingMode mode,
|
||||
) {
|
||||
final isHardware = mode == ChannelLocationSharingMode.hardware;
|
||||
const accent = Color(0xFF16A34A);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: accent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
),
|
||||
child: Icon(
|
||||
isHardware ? Icons.public_rounded : Icons.smartphone_rounded,
|
||||
size: 11,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
@@ -1828,6 +1951,9 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
letterSpacing: -0.3,
|
||||
);
|
||||
final channelIdx = channel.publicKey.length > 1 ? channel.publicKey[1] : 0;
|
||||
final channelLocationSharingMode = context
|
||||
.watch<AppProvider>()
|
||||
.channelLocationSharingModeForChannel(channelIdx);
|
||||
final channelMessages = messagesProvider.getMessagesForChannel(channelIdx)
|
||||
..sort((a, b) => b.sentAt.compareTo(a.sentAt));
|
||||
final lastActivityAt = messagesProvider.getLastActivityForDestination(
|
||||
@@ -1870,7 +1996,21 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ContactAvatar(contact: channel, radius: 24),
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
ContactAvatar(contact: channel, radius: 24),
|
||||
if (channelLocationSharingMode != null)
|
||||
Positioned(
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
child: _buildChannelLocationSharingMarker(
|
||||
context,
|
||||
channelLocationSharingMode,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
@@ -1746,7 +1746,85 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
await action();
|
||||
}
|
||||
|
||||
int? _selectedPrivateChannelIdx() {
|
||||
if (_destinationType !=
|
||||
MessageDestinationPreferences.destinationTypeChannel) {
|
||||
return null;
|
||||
}
|
||||
final recipient = _selectedRecipient;
|
||||
if (recipient == null ||
|
||||
recipient.isPublicChannel ||
|
||||
recipient.publicKey.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return recipient.publicKey[1];
|
||||
}
|
||||
|
||||
String _locationSharingErrorMessage(Object error) {
|
||||
final message = error.toString();
|
||||
if (message.startsWith('Bad state: ')) {
|
||||
return message.substring('Bad state: '.length);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
Future<void> _setSelectedChannelLocationSharing(
|
||||
int channelIdx,
|
||||
bool enabled,
|
||||
) async {
|
||||
try {
|
||||
final result = await context.read<AppProvider>().setChannelLocationSharingEnabled(
|
||||
channelIdx,
|
||||
enabled,
|
||||
);
|
||||
if (!mounted) return;
|
||||
ToastLogger.success(context, result.message);
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
ToastLogger.error(context, _locationSharingErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleChannelLocationSharingAction() async {
|
||||
final recipient = _selectedRecipient;
|
||||
if (recipient == null || recipient.isPublicChannel) {
|
||||
if (!mounted) return;
|
||||
ToastLogger.error(
|
||||
context,
|
||||
'Select a private channel to share your location.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final channelIdx = recipient.publicKey.length > 1 ? recipient.publicKey[1] : 0;
|
||||
if (channelIdx <= 0) {
|
||||
if (!mounted) return;
|
||||
ToastLogger.error(
|
||||
context,
|
||||
'Select a private channel to share your location.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final sharingState = await context
|
||||
.read<AppProvider>()
|
||||
.getChannelLocationSharingState(channelIdx);
|
||||
if (!mounted) return;
|
||||
await _setSelectedChannelLocationSharing(channelIdx, !sharingState.isSharing);
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
ToastLogger.error(context, _locationSharingErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
void _showComposerActions() {
|
||||
final privateChannelIdx = _selectedPrivateChannelIdx();
|
||||
final locationSharingStateFuture = privateChannelIdx == null
|
||||
? null
|
||||
: context
|
||||
.read<AppProvider>()
|
||||
.getChannelLocationSharingState(privateChannelIdx);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
@@ -1759,114 +1837,142 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
await _runAfterSheetDismissal(sheetContext, action);
|
||||
}
|
||||
|
||||
final actions = <Widget>[
|
||||
_ComposerActionTile(
|
||||
icon: Icons.search_rounded,
|
||||
title: l10n.searchMessages,
|
||||
color: const Color(0xFF2B6CB0),
|
||||
onTap: () => runAction(() async {
|
||||
_showFilteredMessageSearch();
|
||||
}),
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.add_location_alt_rounded,
|
||||
title: l10n.sendSarMarker,
|
||||
color: const Color(0xFFB45309),
|
||||
onTap: () => runAction(() async {
|
||||
_showSarDialog();
|
||||
}),
|
||||
),
|
||||
if (_voiceSupported)
|
||||
Widget buildActionsSheet(ChannelLocationSharingState? sharingState) {
|
||||
final actions = <Widget>[
|
||||
_ComposerActionTile(
|
||||
icon: _isRecording ? Icons.stop_rounded : Icons.mic_rounded,
|
||||
title: _isRecording ? 'Stop recording' : 'Record voice',
|
||||
color: const Color(0xFF7C3AED),
|
||||
enabled: !_isSendingVoice,
|
||||
busy: _isSendingVoice,
|
||||
onTap: !_isSendingVoice
|
||||
icon: Icons.search_rounded,
|
||||
title: l10n.searchMessages,
|
||||
color: const Color(0xFF2B6CB0),
|
||||
onTap: () => runAction(() async {
|
||||
_showFilteredMessageSearch();
|
||||
}),
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.add_location_alt_rounded,
|
||||
title: l10n.sendSarMarker,
|
||||
color: const Color(0xFFB45309),
|
||||
onTap: () => runAction(() async {
|
||||
_showSarDialog();
|
||||
}),
|
||||
),
|
||||
if (_destinationType ==
|
||||
MessageDestinationPreferences.destinationTypeChannel)
|
||||
_ComposerActionTile(
|
||||
icon: sharingState?.isSharing == true
|
||||
? Icons.location_off_rounded
|
||||
: Icons.share_location_rounded,
|
||||
title: sharingState?.isSharing == true
|
||||
? 'Stop sharing my location'
|
||||
: 'Share my location',
|
||||
color: const Color(0xFF0F766E),
|
||||
onTap: () => runAction(() async {
|
||||
await _handleChannelLocationSharingAction();
|
||||
}),
|
||||
),
|
||||
if (_voiceSupported)
|
||||
_ComposerActionTile(
|
||||
icon: _isRecording ? Icons.stop_rounded : Icons.mic_rounded,
|
||||
title: _isRecording ? 'Stop recording' : 'Record voice',
|
||||
color: const Color(0xFF7C3AED),
|
||||
enabled: !_isSendingVoice,
|
||||
busy: _isSendingVoice,
|
||||
onTap: !_isSendingVoice
|
||||
? () => runAction(() async {
|
||||
if (_isRecording) {
|
||||
await _stopAndSendVoice();
|
||||
} else {
|
||||
await _startVoiceRecording();
|
||||
}
|
||||
})
|
||||
: null,
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.photo_library_rounded,
|
||||
title: l10n.sendImageFromGallery,
|
||||
color: const Color(0xFF0F766E),
|
||||
enabled: !_isSendingImage,
|
||||
busy: _isSendingImage,
|
||||
onTap: !_isSendingImage
|
||||
? () => runAction(() async {
|
||||
if (_isRecording) {
|
||||
await _stopAndSendVoice();
|
||||
} else {
|
||||
await _startVoiceRecording();
|
||||
}
|
||||
await _pickAndSendImage(source: ImageSource.gallery);
|
||||
})
|
||||
: null,
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.photo_library_rounded,
|
||||
title: l10n.sendImageFromGallery,
|
||||
color: const Color(0xFF0F766E),
|
||||
enabled: !_isSendingImage,
|
||||
busy: _isSendingImage,
|
||||
onTap: !_isSendingImage
|
||||
? () => runAction(() async {
|
||||
await _pickAndSendImage(source: ImageSource.gallery);
|
||||
})
|
||||
: null,
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.camera_alt_rounded,
|
||||
title: l10n.takePhoto,
|
||||
color: const Color(0xFF2563EB),
|
||||
enabled: !_isSendingImage,
|
||||
busy: _isSendingImage,
|
||||
onTap: !_isSendingImage
|
||||
? () => runAction(() async {
|
||||
await _pickAndSendImage(source: ImageSource.camera);
|
||||
})
|
||||
: null,
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.grid_3x3_rounded,
|
||||
title: l10n.startTictactoe,
|
||||
color: const Color(0xFFBE185D),
|
||||
onTap: () => runAction(() async {
|
||||
await _startTicTacToeGame();
|
||||
}),
|
||||
),
|
||||
if (_destinationType ==
|
||||
MessageDestinationPreferences.destinationTypeChannel)
|
||||
_ComposerActionTile(
|
||||
icon: _channelRegionScopeName != null
|
||||
? Icons.language_rounded
|
||||
: Icons.public_rounded,
|
||||
title: _channelRegionScopeName != null
|
||||
? '${l10n.regionScope}: $_channelRegionScopeName'
|
||||
: l10n.setRegionScope,
|
||||
color: const Color(0xFF7C3AED),
|
||||
icon: Icons.camera_alt_rounded,
|
||||
title: l10n.takePhoto,
|
||||
color: const Color(0xFF2563EB),
|
||||
enabled: !_isSendingImage,
|
||||
busy: _isSendingImage,
|
||||
onTap: !_isSendingImage
|
||||
? () => runAction(() async {
|
||||
await _pickAndSendImage(source: ImageSource.camera);
|
||||
})
|
||||
: null,
|
||||
),
|
||||
_ComposerActionTile(
|
||||
icon: Icons.grid_3x3_rounded,
|
||||
title: l10n.startTictactoe,
|
||||
color: const Color(0xFFBE185D),
|
||||
onTap: () => runAction(() async {
|
||||
_showRegionScopeSheet();
|
||||
await _startTicTacToeGame();
|
||||
}),
|
||||
),
|
||||
];
|
||||
if (_destinationType ==
|
||||
MessageDestinationPreferences.destinationTypeChannel)
|
||||
_ComposerActionTile(
|
||||
icon: _channelRegionScopeName != null
|
||||
? Icons.language_rounded
|
||||
: Icons.public_rounded,
|
||||
title: _channelRegionScopeName != null
|
||||
? '${l10n.regionScope}: $_channelRegionScopeName'
|
||||
: l10n.setRegionScope,
|
||||
color: const Color(0xFF7C3AED),
|
||||
onTap: () => runAction(() async {
|
||||
_showRegionScopeSheet();
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
return SafeArea(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(sheetContext).size.height * 0.72,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'More actions',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
return SafeArea(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(sheetContext).size.height * 0.72,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'More actions',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
for (var index = 0; index < actions.length; index++) ...[
|
||||
actions[index],
|
||||
if (index != actions.length - 1) const SizedBox(height: 8),
|
||||
const SizedBox(height: 16),
|
||||
for (var index = 0; index < actions.length; index++) ...[
|
||||
actions[index],
|
||||
if (index != actions.length - 1)
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (locationSharingStateFuture == null) {
|
||||
return buildActionsSheet(null);
|
||||
}
|
||||
|
||||
return FutureBuilder<ChannelLocationSharingState>(
|
||||
future: locationSharingStateFuture,
|
||||
builder: (context, snapshot) {
|
||||
return buildActionsSheet(snapshot.data);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -2027,10 +2133,45 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
Widget _buildDestinationAvatar(BuildContext context) {
|
||||
final recipient = _selectedRecipient;
|
||||
if (recipient != null) {
|
||||
return ContactAvatar(
|
||||
contact: recipient,
|
||||
radius: 14,
|
||||
displayName: _getDestinationLabel(),
|
||||
final sharingMode =
|
||||
recipient.isChannel &&
|
||||
!recipient.isPublicChannel &&
|
||||
recipient.publicKey.length > 1
|
||||
? context.watch<AppProvider>().channelLocationSharingModeForChannel(
|
||||
recipient.publicKey[1],
|
||||
)
|
||||
: null;
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
ContactAvatar(
|
||||
contact: recipient,
|
||||
radius: 14,
|
||||
displayName: _getDestinationLabel(),
|
||||
),
|
||||
if (sharingMode != null)
|
||||
Positioned(
|
||||
right: -3,
|
||||
bottom: -3,
|
||||
child: Container(
|
||||
width: 14,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF16A34A),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
sharingMode == ChannelLocationSharingMode.hardware
|
||||
? Icons.public_rounded
|
||||
: Icons.smartphone_rounded,
|
||||
size: 9,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -420,6 +420,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Future<void> _setFastLocationUpdatesEnabled(bool enabled) async {
|
||||
await _locationService.setFastLocationUpdatesEnabled(enabled);
|
||||
if (!mounted) return;
|
||||
context.read<AppProvider>().notifyChannelLocationSharingChanged();
|
||||
setState(() {
|
||||
_fastLocationUpdatesEnabled = _locationService.fastLocationUpdatesEnabled;
|
||||
});
|
||||
@@ -593,6 +594,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
selected < 0 ? null : selected,
|
||||
);
|
||||
if (!mounted) return;
|
||||
context.read<AppProvider>().notifyChannelLocationSharingChanged();
|
||||
setState(() {
|
||||
_fastLocationChannelIdx = _locationService.fastLocationChannelIdx;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user