diff --git a/lib/helpers/link_handler.dart b/lib/helpers/link_handler.dart index b931ca1..c2eae29 100644 --- a/lib/helpers/link_handler.dart +++ b/lib/helpers/link_handler.dart @@ -3,6 +3,7 @@ import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:url_launcher/url_launcher.dart'; import '../l10n/l10n.dart'; import '../utils/platform_info.dart'; +import '../helpers/snack_bar_builder.dart'; class LinkHandler { static TextStyle defaultLinkStyle(BuildContext context, TextStyle base) { @@ -93,21 +94,19 @@ class LinkHandler { final uri = Uri.parse(url); if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_couldNotOpenLink(url)), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_couldNotOpenLink(url)), + backgroundColor: Colors.red, ); } } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_invalidLink), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_invalidLink), + backgroundColor: Colors.red, ); } } diff --git a/lib/helpers/snack_bar_builder.dart b/lib/helpers/snack_bar_builder.dart new file mode 100644 index 0000000..d7409b6 --- /dev/null +++ b/lib/helpers/snack_bar_builder.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +// showDismissibleSnackBar shows a [SnackBar] with tap to dismiss +// all other properties are default and optional +void showDismissibleSnackBar( + BuildContext context, { + Key? key, + required Widget content, + Color? backgroundColor, + double? elevation, + EdgeInsetsGeometry? margin, + EdgeInsetsGeometry? padding, + double? width, + ShapeBorder? shape, + HitTestBehavior? hitTestBehavior, + SnackBarBehavior? behavior, + SnackBarAction? action, + double? actionOverflowThreshold, + bool? showCloseIcon, + Color? closeIconColor, + Duration? duration, + bool? persist, + Animation? animation, + void Function()? onVisible, + DismissDirection? dismissDirection, + Clip? clipBehavior, +}) { + final messenger = ScaffoldMessenger.of(context); + messenger.showSnackBar( + SnackBar( + key: key, + content: GestureDetector( + onTap: () => messenger.hideCurrentSnackBar(), + child: content, + ), + backgroundColor: backgroundColor, + elevation: elevation, + margin: margin, + padding: padding, + width: width, + shape: shape, + hitTestBehavior: hitTestBehavior, + behavior: behavior, + action: action, + actionOverflowThreshold: actionOverflowThreshold, + showCloseIcon: showCloseIcon, + closeIconColor: closeIconColor, + duration: duration ?? const Duration(seconds: 4), + persist: persist, + animation: animation, + onVisible: onVisible, + dismissDirection: dismissDirection ?? DismissDirection.down, + clipBehavior: clipBehavior ?? Clip.hardEdge, + ), + ); +} diff --git a/lib/screens/app_debug_log_screen.dart b/lib/screens/app_debug_log_screen.dart index 4877038..ca6a6bf 100644 --- a/lib/screens/app_debug_log_screen.dart +++ b/lib/screens/app_debug_log_screen.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import '../l10n/l10n.dart'; import '../services/app_debug_log_service.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; class AppDebugLogScreen extends StatelessWidget { const AppDebugLogScreen({super.key}); @@ -34,8 +35,9 @@ class AppDebugLogScreen extends StatelessWidget { .join('\n'); await Clipboard.setData(ClipboardData(text: text)); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.debugLog_copied)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.debugLog_copied), ); } : null, diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index cd7fb67..f4cab7f 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -10,6 +10,7 @@ import '../services/app_settings_service.dart'; import '../services/notification_service.dart'; import '../services/translation_service.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; import 'map_cache_screen.dart'; class AppSettingsScreen extends StatelessWidget { @@ -151,13 +152,12 @@ class AppSettingsScreen extends StatelessWidget { .requestPermissions(); if (!granted) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.appSettings_notificationPermissionDenied, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.appSettings_notificationPermissionDenied, ), + duration: const Duration(seconds: 2), ); } return; @@ -166,15 +166,14 @@ class AppSettingsScreen extends StatelessWidget { await settingsService.setNotificationsEnabled(value); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - value - ? context.l10n.appSettings_notificationsEnabled - : context.l10n.appSettings_notificationsDisabled, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + value + ? context.l10n.appSettings_notificationsEnabled + : context.l10n.appSettings_notificationsDisabled, ), + duration: const Duration(seconds: 2), ); } }, @@ -301,15 +300,14 @@ class AppSettingsScreen extends StatelessWidget { value: settingsService.settings.clearPathOnMaxRetry, onChanged: (value) { settingsService.setClearPathOnMaxRetry(value); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - value - ? context.l10n.appSettings_pathsWillBeCleared - : context.l10n.appSettings_pathsWillNotBeCleared, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + value + ? context.l10n.appSettings_pathsWillBeCleared + : context.l10n.appSettings_pathsWillNotBeCleared, ), + duration: const Duration(seconds: 2), ); }, ), @@ -329,15 +327,14 @@ class AppSettingsScreen extends StatelessWidget { value: settingsService.settings.autoRouteRotationEnabled, onChanged: (value) { settingsService.setAutoRouteRotationEnabled(value); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - value - ? context.l10n.appSettings_autoRouteRotationEnabled - : context.l10n.appSettings_autoRouteRotationDisabled, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + value + ? context.l10n.appSettings_autoRouteRotationEnabled + : context.l10n.appSettings_autoRouteRotationDisabled, ), + duration: const Duration(seconds: 2), ); }, ), @@ -1164,8 +1161,9 @@ class AppSettingsScreen extends StatelessWidget { String? id, }) async { if (sourceUrl.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.translation_enterUrlFirst)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.translation_enterUrlFirst), ); return; } @@ -1176,22 +1174,23 @@ class AppSettingsScreen extends StatelessWidget { id: id, ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.translation_modelDownloaded)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.translation_modelDownloaded), ); await settingsService.setTranslationEnabled(true); } on TranslationDownloadCancelled { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.translation_downloadStopped)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.translation_downloadStopped), ); } catch (error) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.translation_downloadFailed(error.toString()), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.translation_downloadFailed(error.toString()), ), ); } @@ -1236,16 +1235,16 @@ class AppSettingsScreen extends StatelessWidget { try { await translationService.removeModel(model); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - // TODO: l10n - content: Text('Deleted ${translationModelFriendlyName(model)}.'), - ), + showDismissibleSnackBar( + context, + // TODO: l10n + content: Text('Deleted ${translationModelFriendlyName(model)}.'), ); } catch (error) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Delete failed: $error')), + showDismissibleSnackBar( + context, + content: Text('Delete failed: $error'), ); // TODO: l10n } } @@ -1279,15 +1278,14 @@ class AppSettingsScreen extends StatelessWidget { onChanged: (value) async { await settingsService.setAppDebugLogEnabled(value); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - value - ? context.l10n.appSettings_appDebugLoggingEnabled - : context.l10n.appSettings_appDebugLoggingDisabled, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + value + ? context.l10n.appSettings_appDebugLoggingEnabled + : context.l10n.appSettings_appDebugLoggingDisabled, ), + duration: const Duration(seconds: 2), ); }, ), diff --git a/lib/screens/ble_debug_log_screen.dart b/lib/screens/ble_debug_log_screen.dart index 1009bc4..6d18697 100644 --- a/lib/screens/ble_debug_log_screen.dart +++ b/lib/screens/ble_debug_log_screen.dart @@ -5,6 +5,7 @@ import '../l10n/l10n.dart'; import '../services/ble_debug_log_service.dart'; import '../connector/meshcore_protocol.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; enum _BleLogView { frames, rawLogRx } @@ -52,10 +53,9 @@ class _BleDebugLogScreenState extends State { .join('\n'); await Clipboard.setData(ClipboardData(text: text)); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.debugLog_bleCopied), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.debugLog_bleCopied), ); } : null, diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 64da058..e5b5f67 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -14,6 +14,7 @@ import '../connector/meshcore_protocol.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; import '../helpers/utf8_length_limiter.dart'; +import '../helpers/snack_bar_builder.dart'; import '../l10n/l10n.dart'; import '../models/channel.dart'; import '../models/channel_message.dart'; @@ -144,11 +145,10 @@ class _ChannelChatScreenState extends State { Future _scrollToMessage(String messageId) async { final key = _messageKeys[messageId]; if (key == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_originalMessageNotFound), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_originalMessageNotFound), + duration: const Duration(seconds: 2), ); return; } @@ -1151,9 +1151,10 @@ class _ChannelChatScreenState extends State { final now = DateTime.now(); if (_lastChannelSendAt != null && now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) { - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown))); + content: Text(context.l10n.chat_sendCooldown), + ); return; } _lastChannelSendAt = now; @@ -1195,8 +1196,9 @@ class _ChannelChatScreenState extends State { final maxBytes = maxChannelMessageBytes(connector.selfName); if (utf8.encode(messageText).length > maxBytes) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_messageTooLong(maxBytes)), ); return; } @@ -1323,17 +1325,19 @@ class _ChannelChatScreenState extends State { void _copyMessageText(String text) { Clipboard.setData(ClipboardData(text: text)); - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageCopied))); + content: Text(context.l10n.chat_messageCopied), + ); } Future _deleteMessage(ChannelMessage message) async { await context.read().deleteChannelMessage(message); if (!mounted) return; - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageDeleted))); + content: Text(context.l10n.chat_messageDeleted), + ); } String _formatPathPrefixes(Uint8List pathBytes) { diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 51d2453..44c7a69 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -24,6 +24,7 @@ import '../widgets/empty_state.dart'; import '../widgets/qr_code_display.dart'; import '../widgets/quick_switch_bar.dart'; import '../widgets/unread_badge.dart'; +import '../helpers/snack_bar_builder.dart'; import 'channel_chat_screen.dart'; import 'community_qr_scanner_screen.dart'; import 'contacts_screen.dart'; @@ -809,15 +810,12 @@ class _ChannelsScreenState extends State onPressed: () async { final name = nameController.text.trim(); if (name.isEmpty) { - ScaffoldMessenger.of( - dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext - .l10n - .channels_enterChannelName, - ), + showDismissibleSnackBar( + context, + content: Text( + dialogContext + .l10n + .channels_enterChannelName, ), ); return; @@ -837,13 +835,10 @@ class _ChannelsScreenState extends State nextIndex, ); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_channelAdded( - name, - ), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_channelAdded(name), ), ); } @@ -897,15 +892,12 @@ class _ChannelsScreenState extends State final name = nameController.text.trim(); final pskHex = pskController.text.trim(); if (name.isEmpty) { - ScaffoldMessenger.of( - dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext - .l10n - .channels_enterChannelName, - ), + showDismissibleSnackBar( + context, + content: Text( + dialogContext + .l10n + .channels_enterChannelName, ), ); return; @@ -914,15 +906,12 @@ class _ChannelsScreenState extends State try { psk = Channel.parsePskHex(pskHex); } on FormatException { - ScaffoldMessenger.of( - dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext - .l10n - .channels_pskMustBe32Hex, - ), + showDismissibleSnackBar( + context, + content: Text( + dialogContext + .l10n + .channels_pskMustBe32Hex, ), ); return; @@ -930,13 +919,10 @@ class _ChannelsScreenState extends State Navigator.pop(dialogContext); connector.setChannel(nextIndex, name, psk); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_channelAdded( - name, - ), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_channelAdded(name), ), ); } @@ -967,11 +953,10 @@ class _ChannelsScreenState extends State Navigator.pop(dialogContext); connector.setChannel(nextIndex, 'Public', psk); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_publicChannelAdded, - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_publicChannelAdded, ), ); } @@ -1097,15 +1082,12 @@ class _ChannelsScreenState extends State onPressed: () async { var hashtag = hashtagController.text.trim(); if (hashtag.isEmpty) { - ScaffoldMessenger.of( - dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext - .l10n - .channels_enterChannelName, - ), + showDismissibleSnackBar( + context, + content: Text( + dialogContext + .l10n + .channels_enterChannelName, ), ); return; @@ -1125,15 +1107,12 @@ class _ChannelsScreenState extends State } else { // Community hashtag - HMAC derivation from community secret if (selectedCommunity == null) { - ScaffoldMessenger.of( + showDismissibleSnackBar( dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext - .l10n - .community_selectCommunity, - ), + content: Text( + dialogContext + .l10n + .community_selectCommunity, ), ); return; @@ -1159,12 +1138,11 @@ class _ChannelsScreenState extends State psk, ); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_channelAdded( - channelName, - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_channelAdded( + channelName, ), ), ); @@ -1259,13 +1237,10 @@ class _ChannelsScreenState extends State onPressed: () async { final name = nameController.text.trim(); if (name.isEmpty) { - ScaffoldMessenger.of( - dialogContext, - ).showSnackBar( - SnackBar( - content: Text( - dialogContext.l10n.community_enterName, - ), + showDismissibleSnackBar( + context, + content: Text( + dialogContext.l10n.community_enterName, ), ); return; @@ -1301,11 +1276,10 @@ class _ChannelsScreenState extends State _loadCommunities(); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.community_created(name), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.community_created(name), ), ); @@ -1494,10 +1468,9 @@ class _ChannelsScreenState extends State try { psk = Channel.parsePskHex(pskHex); } on FormatException { - ScaffoldMessenger.of(dialogContext).showSnackBar( - SnackBar( - content: Text(dialogContext.l10n.channels_pskMustBe32Hex), - ), + showDismissibleSnackBar( + dialogContext, + content: Text(dialogContext.l10n.channels_pskMustBe32Hex), ); return; } @@ -1510,16 +1483,16 @@ class _ChannelsScreenState extends State smazEnabled, ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.channels_channelUpdated(name)), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.channels_channelUpdated(name)), ); } catch (e, st) { debugPrint(st.toString()); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to update channel: $e')), + showDismissibleSnackBar( + context, + content: Text('Failed to update channel: $e'), ); } }, @@ -1559,21 +1532,19 @@ class _ChannelsScreenState extends State if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_channelDeleted(channel.name), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_channelDeleted(channel.name), ), ); } catch (e, st) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.channels_channelDeleteFailed(channel.name), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_channelDeleteFailed(channel.name), ), ); @@ -1594,8 +1565,9 @@ class _ChannelsScreenState extends State void _addPublicChannel(BuildContext context, MeshCoreConnector connector) { final psk = Channel.parsePskHex(Channel.publicChannelPsk); connector.setChannel(0, 'Public', psk); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.channels_publicChannelAdded)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.channels_publicChannelAdded), ); } @@ -1810,12 +1782,9 @@ class _ChannelsScreenState extends State _loadCommunities(); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.community_deleted(community.name), - ), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.community_deleted(community.name)), ); } }, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index a4ebc76..2aee61c 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -43,6 +43,7 @@ import '../widgets/radio_stats_entry.dart'; import '../widgets/translated_message_content.dart'; import '../utils/app_logger.dart'; import '../l10n/l10n.dart'; +import '../helpers/snack_bar_builder.dart'; import 'telemetry_screen.dart'; class ChatScreen extends StatefulWidget { @@ -633,9 +634,10 @@ class _ChatScreenState extends State { final now = DateTime.now(); if (_lastTextSendAt != null && now.difference(_lastTextSendAt!) < const Duration(seconds: 1)) { - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown))); + content: Text(context.l10n.chat_sendCooldown), + ); return; } _lastTextSendAt = now; @@ -671,8 +673,9 @@ class _ChatScreenState extends State { } final maxBytes = maxContactMessageBytes(); if (utf8.encode(outgoingText).length > maxBytes) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_messageTooLong(maxBytes)), ); return; } @@ -860,15 +863,12 @@ class _ChatScreenState extends State { _showFullPathDialog(context, path.pathBytes), onTap: () async { if (path.pathBytes.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context - .l10n - .chat_pathDetailsNotAvailable, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.chat_pathDetailsNotAvailable, ), + duration: const Duration(seconds: 2), ); return; } @@ -952,11 +952,10 @@ class _ChatScreenState extends State { _resolveContact(connector), ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_pathCleared), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_pathCleared), + duration: const Duration(seconds: 2), ); Navigator.pop(context); }, @@ -982,11 +981,10 @@ class _ChatScreenState extends State { pathLen: -1, ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_floodModeEnabled), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_floodModeEnabled), + duration: const Duration(seconds: 2), ); Navigator.pop(context); }, @@ -1020,11 +1018,10 @@ class _ChatScreenState extends State { void _showFullPathDialog(BuildContext context, List pathBytes) { if (pathBytes.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_pathDetailsNotAvailable), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_pathDetailsNotAvailable), + duration: const Duration(seconds: 2), ); return; } @@ -1137,11 +1134,10 @@ class _ChatScreenState extends State { : (verified ? context.l10n.chat_pathDeviceConfirmed : context.l10n.chat_pathDeviceNotConfirmed); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.chat_pathSetHops(hopCount, status)), - duration: const Duration(seconds: 3), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.chat_pathSetHops(hopCount, status)), + duration: const Duration(seconds: 3), ); } @@ -1490,26 +1486,29 @@ class _ChatScreenState extends State { void _copyMessageText(String text) { Clipboard.setData(ClipboardData(text: text)); - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageCopied))); + content: Text(context.l10n.chat_messageCopied), + ); } Future _deleteMessage(Message message) async { await context.read().deleteMessage(message); if (!mounted) return; - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageDeleted))); + content: Text(context.l10n.chat_messageDeleted), + ); } void _retryMessage(Message message) { final connector = Provider.of(context, listen: false); // Retry using the contact's current path override setting connector.sendMessage(_resolveContact(connector), message.text); - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.chat_retryingMessage))); + content: Text(context.l10n.chat_retryingMessage), + ); } void _showEmojiPicker(Message message, Contact senderContact) { diff --git a/lib/screens/community_qr_scanner_screen.dart b/lib/screens/community_qr_scanner_screen.dart index 6852dfa..6b71715 100644 --- a/lib/screens/community_qr_scanner_screen.dart +++ b/lib/screens/community_qr_scanner_screen.dart @@ -8,6 +8,7 @@ import '../models/community.dart'; import '../storage/community_store.dart'; import '../widgets/adaptive_app_bar_title.dart'; import '../widgets/qr_scanner_widget.dart'; +import '../helpers/snack_bar_builder.dart'; /// Screen for scanning community QR codes to join communities. /// @@ -76,11 +77,10 @@ class _CommunityQrScannerScreenState extends State { } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.community_invalidQrCode), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.community_invalidQrCode), + backgroundColor: Colors.red, ); } } finally { @@ -93,12 +93,11 @@ class _CommunityQrScannerScreenState extends State { } void _showInvalidQrError(BuildContext context) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.community_invalidQrCode), - backgroundColor: Colors.orange, - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.community_invalidQrCode), + backgroundColor: Colors.orange, + duration: const Duration(seconds: 2), ); } @@ -229,11 +228,10 @@ class _CommunityQrScannerScreenState extends State { } if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.community_joined(community.name)), - backgroundColor: Colors.green, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.community_joined(community.name)), + backgroundColor: Colors.green, ); // Return to previous screen diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 46e2be6..5a6f359 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -27,6 +27,7 @@ import '../widgets/quick_switch_bar.dart'; import '../widgets/repeater_login_dialog.dart'; import '../widgets/room_login_dialog.dart'; import '../widgets/unread_badge.dart'; +import '../helpers/snack_bar_builder.dart'; import 'channels_screen.dart'; import 'chat_screen.dart'; import 'discovery_screen.dart'; @@ -150,9 +151,10 @@ class _ContactsScreenState extends State } void _showGroupsUnavailableMessage(BuildContext context) { - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(context.l10n.common_loading))); + content: Text(context.l10n.common_loading), + ); } void _setupFrameListener() { @@ -169,10 +171,9 @@ class _ContactsScreenState extends State // Validate packet has expected minimum size (98+ bytes per protocol) if (advertPacket.length < 98) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_invalidAdvertFormat), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_invalidAdvertFormat), ); } _pendingOperations.remove(ContactOperationType.export); @@ -187,24 +188,23 @@ class _ContactsScreenState extends State if (!mounted) return; if (_pendingOperations.contains(ContactOperationType.import)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_contactImported)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_contactImported), ); } if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_zeroHopContactAdvertSent), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_zeroHopContactAdvertSent), ); } if (_pendingOperations.contains(ContactOperationType.export)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_contactAdvertCopied), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_contactAdvertCopied), ); } @@ -216,25 +216,22 @@ class _ContactsScreenState extends State if (!mounted) return; if (_pendingOperations.contains(ContactOperationType.import)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_contactImportFailed), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_contactImportFailed), ); } if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_zeroHopContactAdvertFailed), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_zeroHopContactAdvertFailed), ); } if (_pendingOperations.contains(ContactOperationType.export)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_contactAdvertCopyFailed), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_contactAdvertCopyFailed), ); } @@ -271,8 +268,9 @@ class _ContactsScreenState extends State final clipboardData = await Clipboard.getData('text/plain'); if (clipboardData == null || clipboardData.text == null) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_clipboardEmpty), ); } return; @@ -280,8 +278,9 @@ class _ContactsScreenState extends State final text = clipboardData.text!.trim(); if (!text.startsWith('meshcore://')) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_invalidAdvertFormat), ); } return; @@ -294,8 +293,9 @@ class _ContactsScreenState extends State connector.importContact(importContactFrame); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_invalidAdvertFormat), ); } } @@ -330,10 +330,9 @@ class _ContactsScreenState extends State ), onTap: () => { connector.sendSelfAdvert(flood: false), - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.settings_advertisementSent), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_advertisementSent), ), }, ), @@ -347,10 +346,9 @@ class _ContactsScreenState extends State ), onTap: () => { connector.sendSelfAdvert(flood: true), - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.settings_advertisementSent), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_advertisementSent), ), }, ), @@ -1146,19 +1144,17 @@ class _ContactsScreenState extends State onPressed: () async { final name = nameController.text.trim(); if (name.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_groupNameRequired), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_groupNameRequired), ); return; } if (name.toLowerCase() == contactsAllGroupsValue.toLowerCase()) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.contacts_groupNameReserved), - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_groupNameReserved), ); return; } @@ -1167,11 +1163,10 @@ class _ContactsScreenState extends State return g.name.toLowerCase() == name.toLowerCase(); }); if (exists) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.contacts_groupAlreadyExists(name), - ), + showDismissibleSnackBar( + context, + content: Text( + context.l10n.contacts_groupAlreadyExists(name), ), ); return; diff --git a/lib/screens/discovery_screen.dart b/lib/screens/discovery_screen.dart index 3f9d965..f9f0e07 100644 --- a/lib/screens/discovery_screen.dart +++ b/lib/screens/discovery_screen.dart @@ -12,6 +12,7 @@ import '../utils/contact_search.dart'; import '../utils/platform_info.dart'; import '../widgets/app_bar.dart'; import '../widgets/list_filter_widget.dart'; +import '../helpers/snack_bar_builder.dart'; enum DiscoverySortOption { lastSeen, name, type } @@ -234,8 +235,9 @@ class _DiscoveryScreenState extends State { final hexString = pubKeyToHex(contact.rawPacket!); Clipboard.setData(ClipboardData(text: "meshcore://$hexString")); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.contacts_contactAdvertCopied), ); break; case 'delete_contact': diff --git a/lib/screens/map_cache_screen.dart b/lib/screens/map_cache_screen.dart index 1391660..1eb59a8 100644 --- a/lib/screens/map_cache_screen.dart +++ b/lib/screens/map_cache_screen.dart @@ -8,6 +8,7 @@ import '../l10n/l10n.dart'; import '../services/app_settings_service.dart'; import '../services/map_tile_cache_service.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; class MapCacheScreen extends StatefulWidget { const MapCacheScreen({super.key}); @@ -112,15 +113,17 @@ class _MapCacheScreenState extends State { Future _startDownload() async { final bounds = _selectedBounds; if (bounds == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.mapCache_selectAreaFirst)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.mapCache_selectAreaFirst), ); return; } if (_estimatedTiles == 0) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.mapCache_noTilesToDownload)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.mapCache_noTilesToDownload), ); return; } @@ -182,9 +185,7 @@ class _MapCacheScreenState extends State { result.failed, ) : context.l10n.mapCache_cachedTiles(result.downloaded); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + showDismissibleSnackBar(context, content: Text(message)); } Future _clearCache() async { @@ -210,8 +211,9 @@ class _MapCacheScreenState extends State { final cacheService = context.read(); await cacheService.clearCache(); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.mapCache_offlineCacheCleared)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.mapCache_offlineCacheCleared), ); } diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index f2d09f3..daf49d9 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -29,6 +29,7 @@ import 'chat_screen.dart'; import 'contacts_screen.dart'; import '../widgets/repeater_login_dialog.dart'; import '../widgets/room_login_dialog.dart'; +import '../helpers/snack_bar_builder.dart'; import 'repeater_hub_screen.dart'; import 'settings_screen.dart'; import 'line_of_sight_map_screen.dart'; @@ -1659,7 +1660,10 @@ class _MapScreenState extends State { ); await connector.refreshDeviceInfo(); if (!mounted) return; - messenger.showSnackBar(SnackBar(content: Text(successMsg))); + showDismissibleSnackBar( + messenger.context, + content: Text(successMsg), + ); }, ), ListTile( @@ -1681,8 +1685,9 @@ class _MapScreenState extends State { required String flags, }) async { if (!connector.isConnected) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.map_connectToShareMarkers)), + showDismissibleSnackBar( + context, + content: Text(context.l10n.map_connectToShareMarkers), ); return; } @@ -2271,8 +2276,9 @@ class _MapScreenState extends State { _points.clear(); _polylines.clear(); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.map_pathTraceCancelled)), + showDismissibleSnackBar( + context, + content: Text(l10n.map_pathTraceCancelled), ); }, tooltip: l10n.common_cancel, diff --git a/lib/screens/neighbors_screen.dart b/lib/screens/neighbors_screen.dart index 7286eb0..77559d4 100644 --- a/lib/screens/neighbors_screen.dart +++ b/lib/screens/neighbors_screen.dart @@ -11,6 +11,7 @@ import '../connector/meshcore_protocol.dart'; import '../services/repeater_command_service.dart'; import '../widgets/path_management_dialog.dart'; import '../widgets/snr_indicator.dart'; +import '../helpers/snack_bar_builder.dart'; class NeighborsScreen extends StatefulWidget { final Contact repeater; @@ -163,11 +164,10 @@ class _NeighborsScreenState extends State { _neighborCount = neighborCount; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.neighbors_receivedData), - backgroundColor: Colors.green, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.neighbors_receivedData), + backgroundColor: Colors.green, ); _statusTimeout?.cancel(); if (!mounted) return; @@ -224,11 +224,10 @@ class _NeighborsScreenState extends State { _isLoading = false; _isLoaded = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.neighbors_requestTimedOut), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.neighbors_requestTimedOut), + backgroundColor: Colors.red, ); _recordStatusResult(false); }); @@ -239,11 +238,10 @@ class _NeighborsScreenState extends State { _isLoaded = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.neighbors_errorLoading(e.toString())), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.neighbors_errorLoading(e.toString())), + backgroundColor: Colors.red, ); } } diff --git a/lib/screens/repeater_cli_screen.dart b/lib/screens/repeater_cli_screen.dart index 5f76828..5e9a462 100644 --- a/lib/screens/repeater_cli_screen.dart +++ b/lib/screens/repeater_cli_screen.dart @@ -9,6 +9,7 @@ import '../connector/meshcore_protocol.dart'; import '../widgets/debug_frame_viewer.dart'; import '../services/repeater_command_service.dart'; import '../widgets/path_management_dialog.dart'; +import '../helpers/snack_bar_builder.dart'; class RepeaterCliScreen extends StatefulWidget { final Contact repeater; @@ -336,8 +337,9 @@ class _RepeaterCliScreenState extends State { if (_commandController.text.trim().isNotEmpty) { _sendCommand(showDebug: true); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.repeater_enterCommandFirst)), + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_enterCommandFirst), ); } }, diff --git a/lib/screens/repeater_settings_screen.dart b/lib/screens/repeater_settings_screen.dart index d0236bb..6d0b4e6 100644 --- a/lib/screens/repeater_settings_screen.dart +++ b/lib/screens/repeater_settings_screen.dart @@ -10,6 +10,7 @@ import '../services/app_debug_log_service.dart'; import '../services/repeater_command_service.dart'; import '../services/storage_service.dart'; import '../widgets/path_management_dialog.dart'; +import '../helpers/snack_bar_builder.dart'; class RepeaterSettingsScreen extends StatefulWidget { final Contact repeater; @@ -468,18 +469,16 @@ class _RepeaterSettingsScreenState extends State { if (mounted) { if (successCount > 0) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.repeater_refreshed(label)), - backgroundColor: Colors.green, - ), + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_refreshed(label)), + backgroundColor: Colors.green, ); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.repeater_errorRefreshing(label)), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_errorRefreshing(label)), + backgroundColor: Colors.red, ); } @@ -666,11 +665,10 @@ class _RepeaterSettingsScreenState extends State { }); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.repeater_settingsSaved), - backgroundColor: Colors.green, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.repeater_settingsSaved), + backgroundColor: Colors.green, ); } } catch (e) { @@ -679,13 +677,12 @@ class _RepeaterSettingsScreenState extends State { }); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.repeater_errorSavingSettings(e.toString()), - ), - backgroundColor: Colors.red, + showDismissibleSnackBar( + context, + content: Text( + context.l10n.repeater_errorSavingSettings(e.toString()), ), + backgroundColor: Colors.red, ); } } @@ -1429,9 +1426,10 @@ class _RepeaterSettingsScreenState extends State { if (command == 'erase') { if (mounted) { - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(l10n.repeater_eraseSerialOnly))); + content: Text(l10n.repeater_eraseSerialOnly), + ); } return; } @@ -1453,17 +1451,17 @@ class _RepeaterSettingsScreenState extends State { await connector.sendFrame(frame); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.repeater_commandSent(command))), + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_commandSent(command)), ); } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.repeater_errorSendingCommand(e.toString())), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_errorSendingCommand(e.toString())), + backgroundColor: Colors.red, ); } } diff --git a/lib/screens/repeater_status_screen.dart b/lib/screens/repeater_status_screen.dart index f938419..720c32a 100644 --- a/lib/screens/repeater_status_screen.dart +++ b/lib/screens/repeater_status_screen.dart @@ -12,6 +12,7 @@ import '../services/app_settings_service.dart'; import '../services/repeater_command_service.dart'; import '../utils/battery_utils.dart'; import '../widgets/path_management_dialog.dart'; +import '../helpers/snack_bar_builder.dart'; class RepeaterStatusScreen extends StatefulWidget { final Contact repeater; @@ -309,11 +310,10 @@ class _RepeaterStatusScreenState extends State { setState(() { _isLoading = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.repeater_statusRequestTimeout), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.repeater_statusRequestTimeout), + backgroundColor: Colors.red, ); _recordStatusResult(false); }); @@ -323,13 +323,10 @@ class _RepeaterStatusScreenState extends State { _isLoading = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.repeater_errorLoadingStatus(e.toString()), - ), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.repeater_errorLoadingStatus(e.toString())), + backgroundColor: Colors.red, ); } _recordStatusResult(false); diff --git a/lib/screens/scanner_screen.dart b/lib/screens/scanner_screen.dart index 17f26ea..a503ec0 100644 --- a/lib/screens/scanner_screen.dart +++ b/lib/screens/scanner_screen.dart @@ -10,6 +10,7 @@ import '../services/linux_ble_error_classifier.dart'; import '../utils/app_logger.dart'; import '../widgets/adaptive_app_bar_title.dart'; import '../widgets/device_tile.dart'; +import '../helpers/snack_bar_builder.dart'; import 'contacts_screen.dart'; import 'tcp_screen.dart'; import 'usb_screen.dart'; @@ -317,11 +318,10 @@ class _ScannerScreenState extends State { return; } if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.scanner_connectionFailed(e.toString())), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.scanner_connectionFailed(e.toString())), + backgroundColor: Colors.red, ); } } diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index e9b73f8..47b9b9c 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -11,6 +11,7 @@ import '../l10n/l10n.dart'; import '../models/radio_settings.dart'; import '../services/app_debug_log_service.dart'; import '../widgets/app_bar.dart'; +import '../helpers/snack_bar_builder.dart'; import 'app_settings_screen.dart'; import 'app_debug_log_screen.dart'; import 'ble_debug_log_screen.dart'; @@ -513,8 +514,9 @@ class _SettingsScreenState extends State { await connector.setNodeName(controller.text); await connector.refreshDeviceInfo(); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_nodeNameUpdated)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_nodeNameUpdated), ); }, child: Text(l10n.common_save), @@ -628,10 +630,9 @@ class _SettingsScreenState extends State { final interval = int.tryParse(intervalText); if (interval == null || interval < 60 || interval >= 86400) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.settings_locationIntervalInvalid), - ), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_locationIntervalInvalid), ); return; } @@ -639,8 +640,9 @@ class _SettingsScreenState extends State { await connector.setCustomVar("gps_interval:$interval"); await connector.refreshDeviceInfo(); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_locationUpdated)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_locationUpdated), ); } @@ -660,15 +662,17 @@ class _SettingsScreenState extends State { : currentLon; if (lat == null || lon == null) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_locationBothRequired)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_locationBothRequired), ); return; } if (lat < -90 || lat > 90 || lon < -180 || lon > 180) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_locationInvalid)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_locationInvalid), ); return; } @@ -676,8 +680,9 @@ class _SettingsScreenState extends State { await connector.setNodeLocation(lat: lat, lon: lon); await connector.refreshDeviceInfo(); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_locationUpdated)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_locationUpdated), ); }, child: Text(l10n.common_save), @@ -691,9 +696,10 @@ class _SettingsScreenState extends State { void _syncTime(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; connector.syncTime(); - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_timeSynchronized))); + content: Text(l10n.settings_timeSynchronized), + ); } void _confirmReboot(BuildContext context, MeshCoreConnector connector) { @@ -758,23 +764,27 @@ class _SettingsScreenState extends State { if (!mounted) return; switch (result) { case gpxExportSuccess: - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportSuccess))); + content: Text(l10n.settings_gpxExportSuccess), + ); case gpxExportNoContacts: - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_gpxExportNoContacts)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_gpxExportNoContacts), ); break; case gpxExportNotAvailable: - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_gpxExportNotAvailable)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_gpxExportNotAvailable), ); break; case gpxExportFailed: - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportError))); + content: Text(l10n.settings_gpxExportError), + ); break; } } @@ -1077,8 +1087,9 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) { ); await connector.refreshDeviceInfo(); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_telemetryModeUpdated)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_telemetryModeUpdated), ); }, child: Text(l10n.common_save), @@ -1410,18 +1421,18 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> { final txPower = int.tryParse(_txPowerController.text); if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) { - ScaffoldMessenger.of( + showDismissibleSnackBar( context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_frequencyInvalid))); + content: Text(l10n.settings_frequencyInvalid), + ); return; } final maxTxPower = widget.connector.maxTxPower ?? 22; if (txPower == null || txPower < 0 || txPower > maxTxPower) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${l10n.settings_txPowerInvalid} (0-$maxTxPower dBm)'), - ), + showDismissibleSnackBar( + context, + content: Text('${l10n.settings_txPowerInvalid} (0-$maxTxPower dBm)'), ); return; } @@ -1441,8 +1452,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> { if (knownRepeat) { const validRepeatFreqsKHz = {433000, 869000, 918000}; if (_clientRepeat && !validRepeatFreqsKHz.contains(freqHz)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_clientRepeatFreqWarning)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_clientRepeatFreqWarning), ); return; } @@ -1472,14 +1484,16 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> { if (!mounted) return; _logRadioSettingsState('Radio settings saved successfully'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_radioSettingsUpdated)), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_radioSettingsUpdated), ); } catch (e) { _appLog.warn('Radio settings save failed: $e', tag: 'RadioSettings'); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_error(e.toString()))), + showDismissibleSnackBar( + context, + content: Text(l10n.settings_error(e.toString())), ); } Navigator.pop(context); diff --git a/lib/screens/tcp_screen.dart b/lib/screens/tcp_screen.dart index 11ab80a..3bd1b0b 100644 --- a/lib/screens/tcp_screen.dart +++ b/lib/screens/tcp_screen.dart @@ -8,6 +8,7 @@ import '../l10n/l10n.dart'; import '../services/app_settings_service.dart'; import '../utils/platform_info.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; import 'contacts_screen.dart'; import 'usb_screen.dart'; @@ -270,8 +271,10 @@ class _TcpScreenState extends State { void _showError(String message) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message), backgroundColor: Colors.red), + showDismissibleSnackBar( + context, + content: Text(message), + backgroundColor: Colors.red, ); } diff --git a/lib/screens/telemetry_screen.dart b/lib/screens/telemetry_screen.dart index 66911dc..47593a3 100644 --- a/lib/screens/telemetry_screen.dart +++ b/lib/screens/telemetry_screen.dart @@ -14,6 +14,7 @@ import '../utils/app_logger.dart'; import '../widgets/path_management_dialog.dart'; import '../helpers/cayenne_lpp.dart'; import '../utils/battery_utils.dart'; +import '../helpers/snack_bar_builder.dart'; class TelemetryScreen extends StatefulWidget { final Contact contact; @@ -86,11 +87,10 @@ class _TelemetryScreenState extends State { _isLoading = false; _isLoaded = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.telemetry_requestTimeout), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.telemetry_requestTimeout), + backgroundColor: Colors.red, ); _recordTelemetryResult(false); }); @@ -137,11 +137,10 @@ class _TelemetryScreenState extends State { _parsedTelemetry = parsedTelemetry; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.telemetry_receivedData), - backgroundColor: Colors.green, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.telemetry_receivedData), + backgroundColor: Colors.green, ); _statusTimeout?.cancel(); if (!mounted) return; @@ -182,11 +181,10 @@ class _TelemetryScreenState extends State { _isLoaded = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.telemetry_errorLoading(e.toString())), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.telemetry_errorLoading(e.toString())), + backgroundColor: Colors.red, ); } } diff --git a/lib/screens/usb_screen.dart b/lib/screens/usb_screen.dart index 2f2713a..6b8fe9d 100644 --- a/lib/screens/usb_screen.dart +++ b/lib/screens/usb_screen.dart @@ -10,6 +10,7 @@ import '../utils/app_logger.dart'; import '../utils/platform_info.dart'; import '../utils/usb_port_labels.dart'; import '../widgets/adaptive_app_bar_title.dart'; +import '../helpers/snack_bar_builder.dart'; import 'contacts_screen.dart'; import 'scanner_screen.dart'; import 'tcp_screen.dart'; @@ -383,11 +384,10 @@ class _UsbScreenState extends State { void _showError(Object error) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(_friendlyErrorMessage(error)), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(_friendlyErrorMessage(error)), + backgroundColor: Colors.red, ); } diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index 4e91a69..094805a 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -11,6 +11,7 @@ import '../l10n/l10n.dart'; import '../models/contact.dart'; import '../helpers/path_helper.dart'; import '../services/path_history_service.dart'; +import '../helpers/snack_bar_builder.dart'; import 'path_selection_dialog.dart'; class PathManagementDialog { @@ -65,11 +66,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { void _showFullPathDialog(BuildContext context, List pathBytes) { final l10n = context.l10n; if (pathBytes.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.chat_pathDetailsNotAvailable), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(l10n.chat_pathDetailsNotAvailable), + duration: const Duration(seconds: 2), ); return; } @@ -159,11 +159,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.chat_hopsCount(result.length)), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(l10n.chat_hopsCount(result.length)), + duration: const Duration(seconds: 2), ); } } @@ -337,13 +336,12 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { _showFullPathDialog(context, path.pathBytes), onTap: () async { if (path.pathBytes.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - l10n.chat_pathDetailsNotAvailable, - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + l10n.chat_pathDetailsNotAvailable, ), + duration: const Duration(seconds: 2), ); return; } @@ -361,13 +359,12 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { if (!context.mounted) return; Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - l10n.path_usingHopsPath(path.hopCount), - ), - duration: const Duration(seconds: 2), + showDismissibleSnackBar( + context, + content: Text( + l10n.path_usingHopsPath(path.hopCount), ), + duration: const Duration(seconds: 2), ); }, ), @@ -459,11 +456,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { onTap: () async { await connector.clearContactPath(currentContact); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.chat_pathCleared), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(l10n.chat_pathCleared), + duration: const Duration(seconds: 2), ); Navigator.pop(context); }, @@ -489,11 +485,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { pathLen: -1, ); if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.chat_floodModeEnabled), - duration: const Duration(seconds: 2), - ), + showDismissibleSnackBar( + context, + content: Text(l10n.chat_floodModeEnabled), + duration: const Duration(seconds: 2), ); Navigator.pop(context); }, diff --git a/lib/widgets/path_selection_dialog.dart b/lib/widgets/path_selection_dialog.dart index b1733fc..7a890ec 100644 --- a/lib/widgets/path_selection_dialog.dart +++ b/lib/widgets/path_selection_dialog.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:meshcore_open/connector/meshcore_protocol.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../helpers/snack_bar_builder.dart'; class PathSelectionDialog extends StatefulWidget { final List availableContacts; @@ -138,26 +139,22 @@ class _PathSelectionDialogState extends State { // Show error for invalid prefixes if (invalidPrefixes.isNotEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - l10n.path_invalidHexPrefixes(invalidPrefixes.join(", ")), - ), - duration: const Duration(seconds: 3), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(l10n.path_invalidHexPrefixes(invalidPrefixes.join(", "))), + duration: const Duration(seconds: 3), + backgroundColor: Colors.red, ); return; } // Check max path length (64 hops) if (pathBytesList.length > 64) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(l10n.path_tooLong), - duration: const Duration(seconds: 3), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(l10n.path_tooLong), + duration: const Duration(seconds: 3), + backgroundColor: Colors.red, ); return; } diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index 3a923fe..d4028a3 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -10,6 +10,7 @@ import '../services/storage_service.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; import '../utils/app_logger.dart'; +import '../helpers/snack_bar_builder.dart'; import 'path_management_dialog.dart'; class RoomLoginDialog extends StatefulWidget { @@ -175,11 +176,10 @@ class _RoomLoginDialogState extends State { setState(() { _isLoggingIn = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.login_failed(e.toString())), - backgroundColor: Colors.red, - ), + showDismissibleSnackBar( + context, + content: Text(context.l10n.login_failed(e.toString())), + backgroundColor: Colors.red, ); } }