diff --git a/analysis_options.yaml b/analysis_options.yaml index 05852ec2..a2bf618f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -36,7 +36,6 @@ analyzer: - dart_code_linter errors: todo: ignore - use_build_context_synchronously: ignore exclude: - lib/l10n/*.dart diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart index 5f8c1e23..7e22c861 100644 --- a/lib/pages/archive/archive.dart +++ b/lib/pages/archive/archive.dart @@ -48,6 +48,7 @@ class ArchiveController extends State { OkCancelResult.ok) { return; } + if (!mounted) return; await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 4ade9f98..2e3af6a8 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -382,6 +382,7 @@ class BootstrapDialogState extends State { ).wrongRecoveryKey, ); } catch (e, s) { + if (!context.mounted) return; ErrorReporter( context, 'Unable to open SSSS with recovery key', @@ -425,6 +426,7 @@ class BootstrapDialogState extends State { cancelLabel: L10n.of(context).cancel, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; final req = await showFutureLoadingDialog( context: context, delay: false, @@ -435,11 +437,12 @@ class BootstrapDialogState extends State { }, ); if (req.error != null) return; + if (!context.mounted) return; final success = await KeyVerificationDialog( request: req.result!, ).show(context); if (success != true) return; - if (!mounted) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 2dfc70d5..7572566a 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -211,6 +211,7 @@ class ChatController extends State context: context, future: room.leave, ); + if (!mounted) return; if (success.error != null) return; context.go('/rooms'); } @@ -475,8 +476,9 @@ class ChatController extends State Future? loadTimelineFuture; Future _getTimeline({String? eventContextId}) async { - await Matrix.of(context).client.roomsLoading; - await Matrix.of(context).client.accountDataLoading; + final matrix = Matrix.of(context); + await matrix.client.roomsLoading; + await matrix.client.accountDataLoading; if (eventContextId != null && (!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) { eventContextId = null; @@ -632,6 +634,7 @@ class ChatController extends State Future sendFileAction({FileType type = FileType.any}) async { final files = await selectFiles(context, allowMultiple: true, type: type); if (files.isEmpty) return; + if (!mounted) return; await showAdaptiveDialog( context: context, builder: (c) => SendFileDialog( @@ -663,6 +666,7 @@ class ChatController extends State FocusScope.of(context).requestFocus(FocusNode()); final file = await ImagePicker().pickImage(source: ImageSource.camera); if (file == null) return; + if (!mounted) return; await showAdaptiveDialog( context: context, @@ -684,6 +688,7 @@ class ChatController extends State maxDuration: const Duration(minutes: 1), ); if (file == null) return; + if (!mounted) return; await showAdaptiveDialog( context: context, @@ -726,26 +731,27 @@ class ChatController extends State mimeType: mimeType, ); - room - .sendFileEvent( - file, - inReplyTo: replyEvent, - threadRootEventId: activeThreadId, - extraContent: { - 'info': {...file.info, 'duration': duration}, - 'org.matrix.msc3245.voice': {}, - 'org.matrix.msc1767.audio': { - 'duration': duration, - 'waveform': waveform, - }, + try { + await room.sendFileEvent( + file, + inReplyTo: replyEvent, + threadRootEventId: activeThreadId, + extraContent: { + 'info': {...file.info, 'duration': duration}, + 'org.matrix.msc3245.voice': {}, + 'org.matrix.msc1767.audio': { + 'duration': duration, + 'waveform': waveform, }, - ) - .catchError((e) { - scaffoldMessenger.showSnackBar( - SnackBar(content: Text((e as Object).toLocalizedString(context))), - ); - return null; - }); + }, + ); + } catch (e) { + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(e.toLocalizedString(context))), + ); + return; + } setState(() { replyEvent = null; }); @@ -807,29 +813,30 @@ class ChatController extends State Future reportEventAction() async { final event = selectedEvents.single; + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final score = await showModalActionPopup( context: context, - title: L10n.of(context).reportMessage, - message: L10n.of(context).howOffensiveIsThisContent, - cancelLabel: L10n.of(context).cancel, + title: l10n.reportMessage, + message: l10n.howOffensiveIsThisContent, + cancelLabel: l10n.cancel, actions: [ - AdaptiveModalAction( - value: -100, - label: L10n.of(context).extremeOffensive, - ), - AdaptiveModalAction(value: -50, label: L10n.of(context).offensive), - AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive), + AdaptiveModalAction(value: -100, label: l10n.extremeOffensive), + AdaptiveModalAction(value: -50, label: l10n.offensive), + AdaptiveModalAction(value: 0, label: l10n.inoffensive), ], ); if (score == null) return; + if (!mounted) return; final reason = await showTextInputDialog( context: context, - title: L10n.of(context).whyDoYouWantToReportThis, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).reason, + title: l10n.whyDoYouWantToReportThis, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.reason, ); if (reason == null || reason.isEmpty) return; + if (!mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => Matrix.of(context).client.reportEvent( @@ -840,12 +847,13 @@ class ChatController extends State ), ); if (result.error != null) return; + if (!mounted) return; setState(() { showEmojiPicker = false; selectedEvents.clear(); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contentHasBeenReported)), ); } @@ -861,6 +869,7 @@ class ChatController extends State } setState(selectedEvents.clear); } catch (e, s) { + if (!mounted) return; ErrorReporter( context, 'Error while delete error events action', @@ -885,6 +894,7 @@ class ChatController extends State : null; if (reasonInput == null) return; final reason = reasonInput.isEmpty ? null : reasonInput; + if (!mounted) return; await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { @@ -1239,6 +1249,7 @@ class ChatController extends State okLabel: L10n.of(context).unpin, cancelLabel: L10n.of(context).cancel, ); + if (!mounted) return; if (response == OkCancelResult.ok) { final events = room.pinnedEventIds ..removeWhere((oldEvent) => oldEvent == eventId); @@ -1328,17 +1339,18 @@ class ChatController extends State Future onPhoneButtonTap() async { // VoIP required Android SDK 21 if (PlatformInfos.isAndroid) { - DeviceInfoPlugin().androidInfo.then((value) { - if (value.version.sdkInt < 21) { - Navigator.pop(context); - showOkAlertDialog( - context: context, - title: L10n.of(context).unsupportedAndroidVersion, - message: L10n.of(context).unsupportedAndroidVersionLong, - okLabel: L10n.of(context).close, - ); - } - }); + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; + if (androidInfo.version.sdkInt < 21) { + Navigator.pop(context); + await showOkAlertDialog( + context: context, + title: L10n.of(context).unsupportedAndroidVersion, + message: L10n.of(context).unsupportedAndroidVersionLong, + okLabel: L10n.of(context).close, + ); + return; + } } final callType = await showModalActionPopup( context: context, @@ -1359,11 +1371,13 @@ class ChatController extends State ], ); if (callType == null) return; + if (!mounted) return; final voipPlugin = Matrix.of(context).voipPlugin; try { await voipPlugin!.voip.inviteToCall(room, callType); } catch (e) { + if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 9fc2dbf1..413aef01 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -189,6 +189,7 @@ class AudioPlayerState extends State { }); } catch (e, s) { Logs().v('Could not download audio file', e, s); + if (!mounted) rethrow; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); @@ -208,6 +209,7 @@ class AudioPlayerState extends State { ), ); } + if (!mounted) return; audioPlayer.play().onError( ErrorReporter(context, 'Unable to play audio message').onErrorCallback, diff --git a/lib/pages/chat/events/cute_events.dart b/lib/pages/chat/events/cute_events.dart index 01d53c6d..1c84f59f 100644 --- a/lib/pages/chat/events/cute_events.dart +++ b/lib/pages/chat/events/cute_events.dart @@ -50,6 +50,7 @@ class _CuteContentState extends State { Future addOverlay() async { _isOverlayShown = true; await Future.delayed(const Duration(milliseconds: 50)); + if (!mounted) return; OverlayEntry? overlay; overlay = OverlayEntry( diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index eb5aa649..1ec5f5f0 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -61,9 +61,11 @@ class MessageContent extends StatelessWidget { final client = Matrix.of(context).client; final state = await client.getCryptoIdentityState(); if (!state.connected) { + if (!context.mounted) return; final success = await context.push('/backup'); if (success != true) return; } + if (!context.mounted) return; event.requestKey(); final sender = event.senderFromMemoryOrFallback; await showAdaptiveBottomSheet( diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 53b6ebcf..e183e918 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -15,6 +15,7 @@ class PinnedEvents extends StatelessWidget { const PinnedEvents(this.controller, {super.key}); Future _displayPinnedEventsDialog(BuildContext context) async { + final l10n = L10n.of(context); final eventsResult = await showFutureLoadingDialog( context: context, future: () => Future.wait( @@ -25,13 +26,14 @@ class PinnedEvents extends StatelessWidget { ); final events = eventsResult.result; if (events == null) return; + if (!context.mounted) return; final eventId = events.length == 1 ? events.single?.eventId : await showModalActionPopup( context: context, - title: L10n.of(context).pin, - cancelLabel: L10n.of(context).cancel, + title: l10n.pin, + cancelLabel: l10n.cancel, actions: events .map( (event) => AdaptiveModalAction( @@ -39,7 +41,7 @@ class PinnedEvents extends StatelessWidget { icon: const Icon(Icons.push_pin_outlined), label: event?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)), + MatrixLocals(l10n), withSenderNamePrefix: true, hideReply: true, ) ?? diff --git a/lib/pages/chat/recording_view_model.dart b/lib/pages/chat/recording_view_model.dart index d66ccbe4..e7d85e0d 100644 --- a/lib/pages/chat/recording_view_model.dart +++ b/lib/pages/chat/recording_view_model.dart @@ -44,6 +44,7 @@ class RecordingViewModelState extends State { room.client.getConfig(); // Preload server file configuration. if (PlatformInfos.isAndroid) { final info = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; if (info.version.sdkInt < 19) { showOkAlertDialog( context: context, @@ -76,6 +77,7 @@ class RecordingViewModelState extends State { final result = await audioRecorder.hasPermission(); if (result != true) { + if (!mounted) return; showOkAlertDialog( context: context, title: L10n.of(context).oopsSomethingWentWrong, @@ -97,10 +99,12 @@ class RecordingViewModelState extends State { ), path: path ?? '', ); + if (!mounted) return; setState(() => duration = Duration.zero); _subscribe(); } catch (e, s) { Logs().w('Unable to start voice message recording', e, s); + if (!mounted) return; showOkAlertDialog( context: context, title: L10n.of(context).oopsSomethingWentWrong, diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index ef6b9cb5..0b6d14f3 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -146,6 +146,7 @@ class SendFileDialogState extends State { scaffoldMessenger.clearSnackBars(); } catch (e) { scaffoldMessenger.clearSnackBars(); + if (!mounted || !widget.outerContext.mounted) rethrow; final theme = Theme.of(context); scaffoldMessenger.showSnackBar( SnackBar( diff --git a/lib/pages/chat/send_location_dialog.dart b/lib/pages/chat/send_location_dialog.dart index 5c460eb6..b5cde79e 100644 --- a/lib/pages/chat/send_location_dialog.dart +++ b/lib/pages/chat/send_location_dialog.dart @@ -81,6 +81,7 @@ class SendLocationDialogState extends State { context: context, future: () => widget.room.sendLocation(body, uri), ); + if (!mounted) return; Navigator.of(context, rootNavigator: false).pop(); } diff --git a/lib/pages/chat/start_poll_bottom_sheet.dart b/lib/pages/chat/start_poll_bottom_sheet.dart index 5612f7fe..eb054dd7 100644 --- a/lib/pages/chat/start_poll_bottom_sheet.dart +++ b/lib/pages/chat/start_poll_bottom_sheet.dart @@ -44,6 +44,7 @@ class _StartPollBottomSheetState extends State { maxSelections: _allowMultipleAnswers ? _answers.length : 1, txid: _txid, ); + if (!mounted) return; Navigator.of(context).pop(); } catch (e, s) { Logs().w('Unable to create poll', e, s); diff --git a/lib/pages/chat_access_settings/chat_access_settings_controller.dart b/lib/pages/chat_access_settings/chat_access_settings_controller.dart index e090e863..1c55f889 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_controller.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -160,6 +160,7 @@ class ChatAccessSettingsController extends State { } Future updateRoomAction() async { + final l10n = L10n.of(context); final roomVersion = room .getState(EventTypes.RoomCreate)! .content @@ -170,10 +171,11 @@ class ChatAccessSettingsController extends State { ); final capabilities = capabilitiesResult.result; if (capabilities == null) return; + if (!mounted) return; final newVersion = await showModalActionPopup( context: context, - title: L10n.of(context).replaceRoomWithNewerVersion, - cancelLabel: L10n.of(context).cancel, + title: l10n.replaceRoomWithNewerVersion, + cancelLabel: l10n.cancel, actions: capabilities.mRoomVersions!.available.entries .where((r) => r.key != roomVersion) .map( @@ -185,18 +187,20 @@ class ChatAccessSettingsController extends State { ) .toList(), ); - if (newVersion == null || - OkCancelResult.cancel == - await showOkCancelAlertDialog( - context: context, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, - title: L10n.of(context).areYouSure, - message: L10n.of(context).roomUpgradeDescription, - isDestructive: true, - )) { + if (newVersion == null) return; + if (!mounted) return; + final confirmUpgrade = await showOkCancelAlertDialog( + context: context, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, + title: l10n.areYouSure, + message: l10n.roomUpgradeDescription, + isDestructive: true, + ); + if (confirmUpgrade == OkCancelResult.cancel) { return; } + if (!mounted) return; final result = await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { @@ -243,6 +247,7 @@ class ChatAccessSettingsController extends State { } Future addAlias() async { + final l10n = L10n.of(context); final domain = room.client.userID?.domain; if (domain == null) { throw Exception('userID or domain is null! This should never happen.'); @@ -250,11 +255,12 @@ class ChatAccessSettingsController extends State { final input = await showTextInputDialog( context: context, - title: L10n.of(context).editRoomAliases, + title: l10n.editRoomAliases, prefixText: '#', suffixText: domain, - hintText: L10n.of(context).alias, + hintText: l10n.alias, ); + if (!mounted) return; final aliasLocalpart = input?.trim(); if (aliasLocalpart == null || aliasLocalpart.isEmpty) return; final alias = '#$aliasLocalpart:$domain'; @@ -264,17 +270,19 @@ class ChatAccessSettingsController extends State { future: () => room.client.setRoomAlias(alias, room.id), ); if (result.error != null) return; + if (!mounted) return; setState(() {}); if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return; final canonicalAliasConsent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).setAsCanonicalAlias, + title: l10n.setAsCanonicalAlias, message: alias, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, + okLabel: l10n.yes, + cancelLabel: l10n.no, ); + if (!mounted) return; final altAliases = room diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index d227e24e..03bb8e61 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -37,69 +37,78 @@ class ChatDetailsController extends State { String? get roomId => widget.roomId; Future setDisplaynameAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final input = await showTextInputDialog( context: context, - title: L10n.of(context).changeTheNameOfTheGroup, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - initialText: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), + title: l10n.changeTheNameOfTheGroup, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + initialText: room.getLocalizedDisplayname(MatrixLocals(l10n)), ); if (input == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => room.setName(input), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.displaynameHasBeenChanged)), ); } } Future setTopicAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final input = await showTextInputDialog( context: context, - title: L10n.of(context).setChatDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).noChatDescriptionYet, + title: l10n.setChatDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.noChatDescriptionYet, initialText: room.topic, minLines: 4, maxLines: 8, ); if (input == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => room.setDescription(input), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).chatDescriptionHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.chatDescriptionHasBeenChanged)), ); } } Future setAvatarAction() async { + final l10n = L10n.of(context); final room = Matrix.of(context).client.getRoomById(roomId!); final actions = [ if (PlatformInfos.isMobile) AdaptiveModalAction( value: AvatarAction.camera, - label: L10n.of(context).openCamera, + label: l10n.openCamera, isDefaultAction: true, icon: const Icon(Icons.camera_alt_outlined), ), AdaptiveModalAction( value: AvatarAction.file, - label: L10n.of(context).openGallery, + label: l10n.openGallery, icon: const Icon(Icons.photo_outlined), ), if (room?.avatar != null) AdaptiveModalAction( value: AvatarAction.remove, - label: L10n.of(context).delete, + label: l10n.delete, isDestructive: true, icon: const Icon(Icons.delete_outlined), ), @@ -108,11 +117,12 @@ class ChatDetailsController extends State { ? actions.single.value : await showModalActionPopup( context: context, - title: L10n.of(context).editRoomAvatar, - cancelLabel: L10n.of(context).cancel, + title: l10n.editRoomAvatar, + cancelLabel: l10n.cancel, actions: actions, ); if (action == null) return; + if (!mounted) return; if (action == AvatarAction.remove) { await showFutureLoadingDialog( context: context, @@ -131,6 +141,7 @@ class ChatDetailsController extends State { if (result == null) return; file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); } else { + if (!mounted) return; final picked = await selectFiles( context, allowMultiple: false, @@ -143,6 +154,7 @@ class ChatDetailsController extends State { name: pickedFile.name, ); } + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => room!.setAvatar(file), diff --git a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart index efcaf751..6e17d92f 100644 --- a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart +++ b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart @@ -30,38 +30,40 @@ class ChatEncryptionSettingsController extends State { } Future enableEncryption(_) async { + final l10n = L10n.of(context); if (room.encrypted) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).disableEncryptionWarning, + title: l10n.sorryThatsNotPossible, + message: l10n.disableEncryptionWarning, ); return; } if (room.joinRules == JoinRules.public) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).noEncryptionForPublicRooms, + title: l10n.sorryThatsNotPossible, + message: l10n.noEncryptionForPublicRooms, ); return; } if (!room.canChangeStateEvent(EventTypes.Encryption)) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).noPermission, + title: l10n.sorryThatsNotPossible, + message: l10n.noPermission, ); return; } final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - message: L10n.of(context).enableEncryptionWarning, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + message: l10n.enableEncryptionWarning, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => room.enableEncryption(), @@ -69,14 +71,16 @@ class ChatEncryptionSettingsController extends State { } Future startVerification() async { + final l10n = L10n.of(context); final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).verifyOtherUser, - message: L10n.of(context).verifyOtherUserDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.verifyOtherUser, + message: l10n.verifyOtherUserDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; + if (!mounted) return; final req = await room.client.userDeviceKeys[room.directChatMatrixID]! .startVerification(); req.onUpdate = () { @@ -84,6 +88,7 @@ class ChatEncryptionSettingsController extends State { setState(() {}); } }; + if (!mounted) return; await KeyVerificationDialog(request: req).show(context); } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index bb00e694..9ac99e30 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -90,6 +90,8 @@ class ChatListController extends State }); Future onChatTap(Room room) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (room.membership == Membership.invite) { final joinResult = await showFutureLoadingDialog( context: context, @@ -105,10 +107,11 @@ class ChatListController extends State ); if (joinResult.error != null) return; } + if (!mounted) return; if (room.membership == Membership.ban) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).youHaveBeenBannedFromThisChat)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.youHaveBeenBannedFromThisChat)), ); return; } @@ -156,23 +159,25 @@ class ChatListController extends State static const String _serverStoreNamespace = 'im.fluffychat.search.server'; Future setServer() async { + final matrix = Matrix.of(context); + final l10n = L10n.of(context); final newServer = await showTextInputDialog( useRootNavigator: false, - title: L10n.of(context).changeTheHomeserver, + title: l10n.changeTheHomeserver, context: context, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, prefixText: 'https://', - hintText: Matrix.of(context).client.homeserver?.host, + hintText: matrix.client.homeserver?.host, initialText: searchServer, keyboardType: TextInputType.url, autocorrect: false, - validator: (server) => server.contains('.') == true - ? null - : L10n.of(context).invalidServerName, + validator: (server) => + server.contains('.') == true ? null : l10n.invalidServerName, ); if (newServer == null) return; - Matrix.of(context).store.setString(_serverStoreNamespace, newServer); + if (!mounted) return; + matrix.store.setString(_serverStoreNamespace, newServer); setState(() { searchServer = newServer; }); @@ -185,6 +190,7 @@ class ChatListController extends State Future _search() async { final client = Matrix.of(context).client; + final scaffoldMessenger = ScaffoldMessenger.of(context); if (!isSearching) { setState(() { isSearching = true; @@ -227,9 +233,10 @@ class ChatListController extends State ); } catch (e, s) { Logs().w('Searching has crashed', e, s); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(e.toLocalizedString(context))), + ); } if (!isSearchMode) return; setState(() { @@ -293,9 +300,8 @@ class ChatListController extends State Future editSpace(BuildContext context, String spaceId) async { await Matrix.of(context).client.getRoomById(spaceId)!.postLoad(); - if (mounted) { - context.push('/rooms/$spaceId/details'); - } + if (!context.mounted) return; + context.push('/rooms/$spaceId/details'); } // Needs to match GroupsSpacesEntry for 'separate group' checking. @@ -742,6 +748,7 @@ class ChatListController extends State .toList(), ); if (space == null) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => space.setSpaceChild(room.id), @@ -767,16 +774,18 @@ class ChatListController extends State } Future setStatus() async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final currentPresence = await client.fetchCurrentPresence(client.userID!); + if (!mounted) return; final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).setStatus, - message: L10n.of(context).leaveEmptyToClearStatus, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).statusExampleMessage, + title: l10n.setStatus, + message: l10n.leaveEmptyToClearStatus, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.statusExampleMessage, maxLines: 6, minLines: 1, maxLength: 255, @@ -904,18 +913,21 @@ class ChatListController extends State if (action == null) return; switch (action) { case EditBundleAction.addToBundle: + if (!mounted) return; final bundle = await showTextInputDialog( context: context, title: l10n.bundleName, hintText: l10n.bundleName, ); if (bundle == null || bundle.isEmpty || bundle.isEmpty) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => client.setAccountBundle(bundle), ); break; case EditBundleAction.removeFromBundle: + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => client.removeFromAccountBundle(activeBundle!), diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 64d7d08f..3e6a4110 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -209,6 +209,7 @@ class ClientChooserButton extends StatelessWidget { cancelLabel: L10n.of(context).cancel, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; context.go('/rooms/settings/addaccount'); break; case SettingsAction.newGroup: diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index b24f1993..bc4804ac 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -170,14 +170,17 @@ class _SpaceViewState extends State { switch (action) { case SpaceActions.settings: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/details'); break; case SpaceActions.invite: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/invite'); break; case SpaceActions.members: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/details/members'); break; case SpaceActions.leave: diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index f7d342fe..c83064b5 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -36,6 +36,7 @@ class ChatPermissionsSettingsController extends State { currentLevel: currentLevel, ); if (newLevel == null) return; + if (!context.mounted) return; final content = Map.from( room.getState(EventTypes.RoomPowerLevels)!.content, ); diff --git a/lib/pages/device_settings/device_settings.dart b/lib/pages/device_settings/device_settings.dart index 05eb1efb..2e6f565e 100644 --- a/lib/pages/device_settings/device_settings.dart +++ b/lib/pages/device_settings/device_settings.dart @@ -41,12 +41,15 @@ class DevicesSettingsController extends State { Future _checkChatBackup() async { final client = Matrix.of(context).client; final state = await client.getCryptoIdentityState(); + if (!mounted) return; setState(() { chatBackupEnabled = state.initialized && !state.connected; }); } Future removeDevicesAction(List devices) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final client = Matrix.of(context).client; final wellKnown = await Result.capture(client.getWellknown()); @@ -57,18 +60,19 @@ class DevicesSettingsController extends State { launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView); return; } + if (!mounted) return; if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).remove, - cancelLabel: L10n.of(context).cancel, - message: L10n.of(context).removeDevicesDescription, + title: l10n.areYouSure, + okLabel: l10n.remove, + cancelLabel: l10n.cancel, + message: l10n.removeDevicesDescription, isDestructive: true, ) == OkCancelResult.cancel) { return; } - final matrix = Matrix.of(context); + if (!mounted) return; final deviceIds = []; for (final userDevice in devices) { deviceIds.add(userDevice.deviceId); @@ -85,19 +89,21 @@ class DevicesSettingsController extends State { } Future renameDeviceAction(Device device) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final displayName = await showTextInputDialog( context: context, - title: L10n.of(context).changeDeviceName, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.changeDeviceName, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, hintText: device.displayName, ); if (displayName == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, - future: () => Matrix.of( - context, - ).client.updateDevice(device.deviceId, displayName: displayName), + future: () => + matrix.client.updateDevice(device.deviceId, displayName: displayName), ); if (success.error == null) { reload(); @@ -105,17 +111,20 @@ class DevicesSettingsController extends State { } Future verifyDeviceAction(Device device) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).verifyOtherDevice, - message: L10n.of(context).verifyOtherDeviceDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.verifyOtherDevice, + message: l10n.verifyOtherDeviceDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; - final req = await Matrix.of(context) + if (!mounted) return; + final req = await matrix .client - .userDeviceKeys[Matrix.of(context).client.userID!]! + .userDeviceKeys[matrix.client.userID!]! .deviceKeys[device.deviceId]! .startVerification(); req.onUpdate = () { @@ -126,6 +135,7 @@ class DevicesSettingsController extends State { setState(() {}); } }; + if (!mounted) return; await KeyVerificationDialog(request: req).show(context); } diff --git a/lib/pages/image_viewer/video_player.dart b/lib/pages/image_viewer/video_player.dart index 7c17f80f..f97b3320 100644 --- a/lib/pages/image_viewer/video_player.dart +++ b/lib/pages/image_viewer/video_player.dart @@ -92,10 +92,12 @@ class EventVideoPlayerState extends State { ); }); } on IOException catch (e) { + if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); } catch (e, s) { + if (!mounted) return; ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s); } } diff --git a/lib/pages/intro/flows/restore_backup_flow.dart b/lib/pages/intro/flows/restore_backup_flow.dart index 2e8e6d00..1db7a9d4 100644 --- a/lib/pages/intro/flows/restore_backup_flow.dart +++ b/lib/pages/intro/flows/restore_backup_flow.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; Future restoreBackupFlow(BuildContext context) async { + final matrix = Matrix.of(context); final picked = await selectFiles(context); final file = picked.firstOrNull; if (file == null) return; @@ -12,9 +13,9 @@ Future restoreBackupFlow(BuildContext context) async { await showFutureLoadingDialog( context: context, future: () async { - final client = await Matrix.of(context).getLoginClient(); + final client = await matrix.getLoginClient(); await client.importDump(String.fromCharCodes(await file.readAsBytes())); - Matrix.of(context).initMatrix(); + matrix.initMatrix(); }, ); } diff --git a/lib/pages/intro/intro_page.dart b/lib/pages/intro/intro_page.dart index f57878a5..49fa0b70 100644 --- a/lib/pages/intro/intro_page.dart +++ b/lib/pages/intro/intro_page.dart @@ -166,6 +166,7 @@ class IntroPage extends StatelessWidget { final client = await Matrix.of( context, ).getLoginClient(); + if (!context.mounted) return; context.go( '${GoRouterState.of(context).uri.path}/login', extra: client, diff --git a/lib/pages/intro/intro_page_presenter.dart b/lib/pages/intro/intro_page_presenter.dart index 27cb86bb..34bddfc7 100644 --- a/lib/pages/intro/intro_page_presenter.dart +++ b/lib/pages/intro/intro_page_presenter.dart @@ -75,7 +75,8 @@ class _IntroPagePresenterState extends State { final client = await Matrix.of(context).getLoginClient(); await client.checkHomeserver(homeserverUrl); await client.oidcLogin(session: session, code: code, state: state); - if (context.mounted) context.go('/backup'); + if (!mounted) return; + context.go('/backup'); } catch (e, s) { Logs().w('Unable to login via OIDC', e, s); if (mounted) { diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index ff769106..e0941233 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -54,6 +54,8 @@ class InvitationSelectionController extends State { String id, String displayname, ) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final success = await showFutureLoadingDialog( @@ -61,10 +63,9 @@ class InvitationSelectionController extends State { future: () => room.invite(id), ); if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context).contactHasBeenInvitedToTheGroup), - ), + if (!context.mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contactHasBeenInvitedToTheGroup)), ); } } @@ -91,6 +92,7 @@ class InvitationSelectionController extends State { try { response = await matrix.client.searchUserDirectory(text, limit: 10); } catch (e) { + if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context)))); diff --git a/lib/pages/key_verification/key_verification_dialog.dart b/lib/pages/key_verification/key_verification_dialog.dart index d1329992..52c8868a 100644 --- a/lib/pages/key_verification/key_verification_dialog.dart +++ b/lib/pages/key_verification/key_verification_dialog.dart @@ -82,6 +82,7 @@ class KeyVerificationPageState extends State { }, ); if (valid.error != null) { + if (!mounted) return; await showOkAlertDialog( useRootNavigator: false, context: context, @@ -178,9 +179,10 @@ class KeyVerificationPageState extends State { ); buttons.add( AdaptiveDialogAction( - onPressed: () => widget.request.rejectVerification().then( - (_) => Navigator.of(context, rootNavigator: false).pop(false), - ), + onPressed: () => widget.request.rejectVerification().then((_) { + if (!context.mounted) return; + Navigator.of(context, rootNavigator: false).pop(false); + }), child: Text( L10n.of(context).reject, style: TextStyle(color: theme.colorScheme.error), diff --git a/lib/pages/login/login.dart b/lib/pages/login/login.dart index 8fb9ec73..4ebd3278 100644 --- a/lib/pages/login/login.dart +++ b/lib/pages/login/login.dart @@ -130,15 +130,19 @@ class LoginController extends State { Logs().v( '$newDomain is not running a homeserver, asking to use $oldHomeserver', ); + if (!mounted) return; + final l10n = L10n.of(context); final dialogResult = await showOkCancelAlertDialog( context: context, useRootNavigator: false, - title: L10n.of( - context, - ).noMatrixServer(newDomain.toString(), oldHomeserver.toString()), - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.noMatrixServer( + newDomain.toString(), + oldHomeserver.toString(), + ), + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); + if (!mounted) return; if (dialogResult == OkCancelResult.ok) { if (mounted) setState(() => usernameError = null); } else { @@ -156,26 +160,30 @@ class LoginController extends State { } } catch (e) { widget.client.homeserver = oldHomeserver; + if (!mounted) return; usernameError = e.toLocalizedString(context); if (mounted) setState(() {}); } } Future passwordForgotten() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).passwordForgotten, - message: L10n.of(context).enterAnEmailAddress, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.passwordForgotten, + message: l10n.enterAnEmailAddress, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, initialText: usernameController.text.isEmail ? usernameController.text : '', - hintText: L10n.of(context).enterAnEmailAddress, + hintText: l10n.enterAnEmailAddress, keyboardType: TextInputType.emailAddress, ); if (input == null) return; + if (!mounted) return; final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, @@ -186,27 +194,30 @@ class LoginController extends State { ), ); if (response.error != null) return; + if (!mounted) return; final password = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).passwordForgotten, - message: L10n.of(context).chooseAStrongPassword, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.passwordForgotten, + message: l10n.chooseAStrongPassword, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, hintText: '******', obscureText: true, minLines: 1, maxLines: 1, ); if (password == null) return; + if (!mounted) return; final ok = await showOkAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).weSentYouAnEmail, - message: L10n.of(context).pleaseClickOnLink, - okLabel: L10n.of(context).iHaveClickedOnLink, + title: l10n.weSentYouAnEmail, + message: l10n.pleaseClickOnLink, + okLabel: l10n.iHaveClickedOnLink, ); if (ok != OkCancelResult.ok) return; + if (!mounted) return; final data = { 'new_password': password, 'logout_devices': false, @@ -226,9 +237,10 @@ class LoginController extends State { data: data, ), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.passwordHasBeenChanged)), ); usernameController.text = input; passwordController.text = password; diff --git a/lib/pages/new_private_chat/new_private_chat.dart b/lib/pages/new_private_chat/new_private_chat.dart index faf30114..6135b473 100644 --- a/lib/pages/new_private_chat/new_private_chat.dart +++ b/lib/pages/new_private_chat/new_private_chat.dart @@ -81,17 +81,19 @@ class NewPrivateChatController extends State { void inviteAction() => FluffyShare.shareInviteLink(context); Future openScannerAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (PlatformInfos.isAndroid) { final info = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; if (info.version.sdkInt < 21) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context).unsupportedAndroidVersionLong), - ), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.unsupportedAndroidVersionLong)), ); return; } } + if (!mounted) return; await showAdaptiveBottomSheet( context: context, builder: (_) => QrScannerModal( @@ -101,12 +103,15 @@ class NewPrivateChatController extends State { } Future copyUserId() async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context); await Clipboard.setData( ClipboardData(text: Matrix.of(context).client.userID!), ); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard))); + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.copiedToClipboard)), + ); } void openUserModal(Profile profile) => diff --git a/lib/pages/new_private_chat/qr_scanner_modal.dart b/lib/pages/new_private_chat/qr_scanner_modal.dart index 53a822b0..1983c1a6 100644 --- a/lib/pages/new_private_chat/qr_scanner_modal.dart +++ b/lib/pages/new_private_chat/qr_scanner_modal.dart @@ -66,6 +66,7 @@ class QrScannerModalState extends State { late StreamSubscription sub; sub = controller.scannedDataStream.listen((scanData) { sub.cancel(); + if (!mounted) return; Navigator.of(context).pop(); final data = scanData.code; if (data != null) widget.onScan(data); diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 2b988d08..e8b21a54 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -35,18 +35,20 @@ class SettingsController extends State { }); Future setDisplaynameAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final profile = await profileFuture; + if (!mounted) return; final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).editDisplayname, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - initialText: - profile?.displayName ?? Matrix.of(context).client.userID!.localpart, + title: l10n.editDisplayname, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + initialText: profile?.displayName ?? matrix.client.userID!.localpart, ); if (input == null) return; - final matrix = Matrix.of(context); + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => matrix.client.setProfileField( @@ -61,19 +63,21 @@ class SettingsController extends State { } Future logoutAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).areYouSureYouWantToLogout, - message: L10n.of(context).noBackupWarning, + title: l10n.areYouSureYouWantToLogout, + message: l10n.noBackupWarning, isDestructive: cryptoIdentityConnected == false, - okLabel: L10n.of(context).logout, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.logout, + cancelLabel: l10n.cancel, ) == OkCancelResult.cancel) { return; } - final matrix = Matrix.of(context); + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => matrix.client.logout(), @@ -81,24 +85,27 @@ class SettingsController extends State { } Future setAvatarAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final profile = await profileFuture; + if (!mounted) return; final actions = [ if (PlatformInfos.isMobile) AdaptiveModalAction( value: AvatarAction.camera, - label: L10n.of(context).openCamera, + label: l10n.openCamera, isDefaultAction: true, icon: const Icon(Icons.camera_alt_outlined), ), AdaptiveModalAction( value: AvatarAction.file, - label: L10n.of(context).openGallery, + label: l10n.openGallery, icon: const Icon(Icons.photo_outlined), ), if (profile?.avatarUrl != null) AdaptiveModalAction( value: AvatarAction.remove, - label: L10n.of(context).removeYourAvatar, + label: l10n.removeYourAvatar, isDestructive: true, icon: const Icon(Icons.delete_outlined), ), @@ -107,12 +114,12 @@ class SettingsController extends State { ? actions.single.value : await showModalActionPopup( context: context, - title: L10n.of(context).changeYourAvatar, - cancelLabel: L10n.of(context).cancel, + title: l10n.changeYourAvatar, + cancelLabel: l10n.cancel, actions: actions, ); if (action == null) return; - final matrix = Matrix.of(context); + if (!mounted) return; if (action == AvatarAction.remove) { final success = await showFutureLoadingDialog( context: context, @@ -134,6 +141,7 @@ class SettingsController extends State { if (result == null) return; file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); } else { + if (!mounted) return; final result = await selectFiles(context, type: FileType.image); final pickedFile = result.firstOrNull; if (pickedFile == null) return; @@ -142,6 +150,7 @@ class SettingsController extends State { name: pickedFile.name, ); } + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => matrix.client.setAvatar(file), @@ -168,6 +177,7 @@ class SettingsController extends State { } final state = await client.getCryptoIdentityState(); + if (!mounted) return; setState(() { cryptoIdentityConnected = state.initialized && state.connected; }); diff --git a/lib/pages/settings_3pid/settings_3pid.dart b/lib/pages/settings_3pid/settings_3pid.dart index 18f89be7..25f738f8 100644 --- a/lib/pages/settings_3pid/settings_3pid.dart +++ b/lib/pages/settings_3pid/settings_3pid.dart @@ -19,41 +19,48 @@ class Settings3Pid extends StatefulWidget { class Settings3PidController extends State { Future add3PidAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).enterAnEmailAddress, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).enterAnEmailAddress, + title: l10n.enterAnEmailAddress, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.enterAnEmailAddress, keyboardType: TextInputType.emailAddress, ); if (input == null) return; + if (!mounted) return; final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.requestTokenToRegisterEmail( + future: () => matrix.client.requestTokenToRegisterEmail( clientSecret, input, Settings3Pid.sendAttempt++, ), ); if (response.error != null) return; + if (!mounted) return; final ok = await showOkAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).weSentYouAnEmail, - message: L10n.of(context).pleaseClickOnLink, - okLabel: L10n.of(context).iHaveClickedOnLink, + title: l10n.weSentYouAnEmail, + message: l10n.pleaseClickOnLink, + okLabel: l10n.iHaveClickedOnLink, ); if (ok != OkCancelResult.ok) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, delay: false, - future: () => Matrix.of(context).client.uiaRequestBackground( - (auth) => Matrix.of( - context, - ).client.add3PID(clientSecret, response.result!.sid, auth: auth), + future: () => matrix.client.uiaRequestBackground( + (auth) => matrix.client.add3PID( + clientSecret, + response.result!.sid, + auth: auth, + ), ), ); if (success.error != null) return; @@ -63,21 +70,25 @@ class Settings3PidController extends State { Future?>? request; Future delete3Pid(ThirdPartyIdentifier identifier) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, ) != OkCancelResult.ok) { return; } + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, - future: () => Matrix.of( - context, - ).client.delete3pidFromAccount(identifier.address, identifier.medium), + future: () => matrix.client.delete3pidFromAccount( + identifier.address, + identifier.medium, + ), ); if (success.error != null) return; setState(() => request = null); diff --git a/lib/pages/settings_emotes/import_archive_dialog.dart b/lib/pages/settings_emotes/import_archive_dialog.dart index 20b1ffde..80b9dcf0 100644 --- a/lib/pages/settings_emotes/import_archive_dialog.dart +++ b/lib/pages/settings_emotes/import_archive_dialog.dart @@ -91,6 +91,7 @@ class _ImportEmoteArchiveDialogState extends State { } Future _addEmotePack() async { + final matrix = Matrix.of(context); setState(() { _loading = true; _progress = 0; @@ -148,7 +149,7 @@ class _ImportEmoteArchiveDialogState extends State { } else { mxcFile = thumbnail; } - final uri = await Matrix.of(context).client.uploadContent( + final uri = await matrix.client.uploadContent( mxcFile.bytes, filename: mxcFile.name, contentType: mxcFile.mimeType, @@ -178,6 +179,7 @@ class _ImportEmoteArchiveDialogState extends State { } } + if (!mounted) return; await widget.controller.save(context); _importMap.removeWhere( (key, value) => successfulUploads.contains(key.name), diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 9825daa9..13cd4819 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -293,6 +293,7 @@ class EmotesSettingsController extends State { } Future createStickers() async { + final matrix = Matrix.of(context); final pickedFiles = await selectFiles( context, type: FileType.image, @@ -315,7 +316,7 @@ class EmotesSettingsController extends State { nativeImplementations: ClientManager.nativeImplementations, ) ?? file; - final uri = await Matrix.of(context).client.uploadContent( + final uri = await matrix.client.uploadContent( file.bytes, filename: file.name, contentType: file.mimeType, @@ -361,6 +362,7 @@ class EmotesSettingsController extends State { final buffer = InputMemoryStream(await result.single.readAsBytes()); final archive = ZipDecoder().decodeStream(buffer); + if (!mounted) return; await showDialog( context: context, @@ -375,7 +377,7 @@ class EmotesSettingsController extends State { Future exportAsZip() async { final client = Matrix.of(context).client; - await showFutureLoadingDialog( + final result = await showFutureLoadingDialog( context: context, future: () async { final pack = _getPack(); @@ -397,11 +399,12 @@ class EmotesSettingsController extends State { '${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip'; final output = ZipEncoder().encode(archive); - MatrixFile( - name: fileName, - bytes: Uint8List.fromList(output), - ).save(context); + return MatrixFile(name: fileName, bytes: Uint8List.fromList(output)); }, ); + final file = result.result; + if (file == null) return; + if (!mounted) return; + file.save(context); } } diff --git a/lib/pages/settings_notifications/settings_notifications.dart b/lib/pages/settings_notifications/settings_notifications.dart index 1f61c33b..8874d1d3 100644 --- a/lib/pages/settings_notifications/settings_notifications.dart +++ b/lib/pages/settings_notifications/settings_notifications.dart @@ -40,6 +40,7 @@ class SettingsNotificationsController extends State { ], ); if (delete != true) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, diff --git a/lib/pages/settings_password/settings_password.dart b/lib/pages/settings_password/settings_password.dart index acceda75..e248e7c0 100644 --- a/lib/pages/settings_password/settings_password.dart +++ b/lib/pages/settings_password/settings_password.dart @@ -24,6 +24,8 @@ class SettingsPasswordController extends State { bool loading = false; Future changePassword() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); setState(() { oldPasswordError = newPassword1Error = newPassword2Error = null; }); @@ -51,13 +53,13 @@ class SettingsPasswordController extends State { loading = true; }); try { - final scaffoldMessenger = ScaffoldMessenger.of(context); await Matrix.of(context).client.changePassword( newPassword1Controller.text, oldPassword: oldPasswordController.text, ); + if (!mounted) return; scaffoldMessenger.showSnackBar( - SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), + SnackBar(content: Text(l10n.passwordHasBeenChanged)), ); if (mounted) context.pop(); } catch (e) { diff --git a/lib/pages/settings_security/settings_security.dart b/lib/pages/settings_security/settings_security.dart index 57ea44f3..a0c5754a 100644 --- a/lib/pages/settings_security/settings_security.dart +++ b/lib/pages/settings_security/settings_security.dart @@ -19,20 +19,21 @@ class SettingsSecurity extends StatefulWidget { class SettingsSecurityController extends State { Future setAppLockAction() async { + final l10n = L10n.of(context); if (AppLock.of(context).isActive) { AppLock.of(context).showLockScreen(); } final newLock = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).pleaseChooseAPasscode, - message: L10n.of(context).pleaseEnter4Digits, - cancelLabel: L10n.of(context).cancel, + title: l10n.pleaseChooseAPasscode, + message: l10n.pleaseEnter4Digits, + cancelLabel: l10n.cancel, validator: (text) { if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) { return null; } - return L10n.of(context).pleaseEnter4Digits; + return l10n.pleaseEnter4Digits; }, keyboardType: TextInputType.number, obscureText: true, @@ -41,53 +42,55 @@ class SettingsSecurityController extends State { maxLength: 4, ); if (newLock != null) { + if (!mounted) return; await AppLock.of(context).changePincode(newLock); } } Future deleteAccountAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).warning, - message: L10n.of(context).deactivateAccountWarning, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.warning, + message: l10n.deactivateAccountWarning, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, isDestructive: true, ) == OkCancelResult.cancel) { return; } - final supposedMxid = Matrix.of(context).client.userID!; + if (!mounted) return; + final supposedMxid = matrix.client.userID!; final mxid = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).confirmMatrixId, - validator: (text) => text == supposedMxid - ? null - : L10n.of(context).supposedMxid(supposedMxid), + title: l10n.confirmMatrixId, + validator: (text) => + text == supposedMxid ? null : l10n.supposedMxid(supposedMxid), isDestructive: true, - okLabel: L10n.of(context).delete, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.delete, + cancelLabel: l10n.cancel, ); if (mxid == null || mxid.isEmpty || mxid != supposedMxid) { return; } + if (!mounted) return; final resp = await showFutureLoadingDialog( context: context, delay: false, - future: () => - Matrix.of(context).client.uiaRequestBackground( - (auth) => Matrix.of( - context, - ).client.deactivateAccount(auth: auth, erase: true), - ), + future: () => matrix.client.uiaRequestBackground( + (auth) => matrix.client.deactivateAccount(auth: auth, erase: true), + ), ); if (!resp.isError) { + if (!mounted) return; await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.logout(), + future: () => matrix.client.logout(), ); } } diff --git a/lib/pages/settings_style/settings_style.dart b/lib/pages/settings_style/settings_style.dart index 0feff1c4..7e8e45f4 100644 --- a/lib/pages/settings_style/settings_style.dart +++ b/lib/pages/settings_style/settings_style.dart @@ -29,6 +29,7 @@ class SettingsStyleController extends State { final picked = await selectFiles(context, type: FileType.image); final pickedFile = picked.firstOrNull; if (pickedFile == null) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, diff --git a/lib/utils/fluffy_share.dart b/lib/utils/fluffy_share.dart index 45699616..7a52d286 100644 --- a/lib/utils/fluffy_share.dart +++ b/lib/utils/fluffy_share.dart @@ -12,6 +12,8 @@ abstract class FluffyShare { BuildContext context, { bool copyOnly = false, }) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (PlatformInfos.isMobile && !copyOnly) { final box = context.findRenderObject() as RenderBox; await SharePlus.instance.share( @@ -24,21 +26,20 @@ abstract class FluffyShare { } await Clipboard.setData(ClipboardData(text: text)); if (!PlatformInfos.isMobile) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - showCloseIcon: true, - content: Text(L10n.of(context).copiedToClipboard), - ), + scaffoldMessenger.showSnackBar( + SnackBar(showCloseIcon: true, content: Text(l10n.copiedToClipboard)), ); } return; } static Future shareInviteLink(BuildContext context) async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final ownProfile = await client.fetchOwnProfile(); + if (!context.mounted) return; await FluffyShare.share( - L10n.of(context).inviteText( + l10n.inviteText( ownProfile.displayName ?? client.userID!, 'https://matrix.to/#/${client.userID}?client=im.fluffychat', ), diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index 84b9f2d8..641898ac 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -25,12 +25,14 @@ extension LocalizedBody on Event { Future saveFile(BuildContext context) async { final matrixFile = await _getFile(context); + if (!context.mounted) return; matrixFile.result?.save(context); } Future shareFile(BuildContext context) async { final matrixFile = await _getFile(context); + if (!context.mounted) return; matrixFile.result?.share(context); } diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index f5addae6..4d4a0dfb 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -50,15 +50,17 @@ abstract class PlatformInfos { } static Future showDialog(BuildContext context) async { + final l10n = L10n.of(context); final version = await PlatformInfos.getVersion(); + if (!context.mounted) return; showAboutDialog( context: context, children: [ - Text(L10n.of(context).versionWithNumber(version)), + Text(l10n.versionWithNumber(version)), TextButton.icon( onPressed: () => launchUrlString(AppConfig.sourceCodeUrl), icon: const Icon(Icons.source_outlined), - label: Text(L10n.of(context).sourceCode), + label: Text(l10n.sourceCode), ), Builder( builder: (innerContext) { @@ -68,7 +70,7 @@ abstract class PlatformInfos { Navigator.of(innerContext).pop(); }, icon: const Icon(Icons.list_outlined), - label: Text(L10n.of(context).logs), + label: Text(l10n.logs), ); }, ), @@ -80,7 +82,7 @@ abstract class PlatformInfos { Navigator.of(innerContext).pop(); }, icon: const Icon(Icons.settings_applications_outlined), - label: Text(L10n.of(context).advancedConfigs), + label: Text(l10n.advancedConfigs), ); }, ), diff --git a/lib/utils/show_update_snackbar.dart b/lib/utils/show_update_snackbar.dart index 8735a941..c0366046 100644 --- a/lib/utils/show_update_snackbar.dart +++ b/lib/utils/show_update_snackbar.dart @@ -10,6 +10,7 @@ abstract class UpdateNotifier { static Future showUpdateSnackBar(BuildContext context) async { final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context); final currentVersion = await PlatformInfos.getVersion(); final store = await SharedPreferences.getInstance(); final storedVersion = store.getString(versionStoreKey); @@ -20,9 +21,9 @@ abstract class UpdateNotifier { SnackBar( duration: const Duration(seconds: 30), showCloseIcon: true, - content: Text(L10n.of(context).updateInstalled(currentVersion)), + content: Text(l10n.updateInstalled(currentVersion)), action: SnackBarAction( - label: L10n.of(context).changelog, + label: l10n.changelog, onPressed: () => launchUrlString(AppConfig.changelogUrl), ), ), diff --git a/lib/utils/sign_in_flows/check_homeserver.dart b/lib/utils/sign_in_flows/check_homeserver.dart index 213ad909..ca77b5ca 100644 --- a/lib/utils/sign_in_flows/check_homeserver.dart +++ b/lib/utils/sign_in_flows/check_homeserver.dart @@ -38,6 +38,7 @@ Future connectToHomeserverFlow( if ((kIsWeb || PlatformInfos.isLinux) && (supportsSso || authMetadata != null || (signUp && regLink != null))) { + if (!context.mounted) return; final consent = await showOkCancelAlertDialog( context: context, title: l10n.appWantsToUseForLogin(homeserverInput), @@ -45,7 +46,9 @@ Future connectToHomeserverFlow( okLabel: l10n.continueText, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; } + if (!context.mounted) return; if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) { await oidcLoginFlow(client, context, signUp); @@ -55,6 +58,7 @@ Future connectToHomeserverFlow( if (signUp && regLink != null) { await launchUrlString(regLink); } + if (!context.mounted) return; final pathSegments = List.of( GoRouter.of(context).routeInformationProvider.value.uri.pathSegments, ); diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index fcc64113..34649cd1 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -27,6 +27,8 @@ class UrlLauncher { const UrlLauncher(this.context, this.url, [this.name]); Future launchUrl() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) || url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || {'#', '@', '!', '+', '\$'}.contains(url![0]) || @@ -36,8 +38,8 @@ class UrlLauncher { final uri = Uri.tryParse(url!); if (uri == null) { // we can't open this thing - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.cantOpenUri(url!))), ); return; } @@ -47,10 +49,10 @@ class UrlLauncher { // that the user can see the actual url before opening the browser. final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).openLinkInBrowser, + title: l10n.openLinkInBrowser, message: url, - okLabel: L10n.of(context).open, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.open, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; } @@ -90,8 +92,8 @@ class UrlLauncher { return; } if (uri.host.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.cantOpenUri(url!))), ); return; } @@ -161,6 +163,7 @@ class UrlLauncher { } } servers.addAll(identityParts.via); + if (!context.mounted) return; if (room != null) { if (room.isSpace) { // TODO: Implement navigate to space @@ -178,6 +181,7 @@ class UrlLauncher { } return; } else { + if (!context.mounted) return; await showAdaptiveDialog( context: context, builder: (c) => @@ -185,6 +189,7 @@ class UrlLauncher { ); } if (roomIdOrAlias.sigil == '!') { + if (!context.mounted) return; if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, @@ -192,6 +197,7 @@ class UrlLauncher { ) == OkCancelResult.ok) { roomId = roomIdOrAlias; + if (!context.mounted) return; final response = await showFutureLoadingDialog( context: context, future: () => matrix.client.joinRoom( @@ -200,11 +206,13 @@ class UrlLauncher { ), ); if (response.error != null) return; + if (!context.mounted) return; // wait for two seconds so that it probably came down /sync await showFutureLoadingDialog( context: context, future: () => Future.delayed(const Duration(seconds: 2)), ); + if (!context.mounted) return; if (event != null) { context.go( Uri( @@ -228,6 +236,7 @@ class UrlLauncher { return Profile(userId: userId); }), ); + if (!context.mounted) return; await UserDialog.show( context: context, profile: profileResult.result!, diff --git a/lib/widgets/adaptive_dialogs/public_room_dialog.dart b/lib/widgets/adaptive_dialogs/public_room_dialog.dart index adb0865d..6149be12 100644 --- a/lib/widgets/adaptive_dialogs/public_room_dialog.dart +++ b/lib/widgets/adaptive_dialogs/public_room_dialog.dart @@ -25,6 +25,7 @@ class PublicRoomDialog extends StatelessWidget { const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via}); Future _joinRoom(BuildContext context) async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final chunk = this.chunk; final knock = chunk?.joinRule == 'knock'; @@ -48,12 +49,13 @@ class PublicRoomDialog extends StatelessWidget { ); final roomId = result.result; if (roomId == null) return; + if (!context.mounted) return; if (knock && client.getRoomById(roomId) == null) { Navigator.of(context).pop(true); await showOkAlertDialog( context: context, - title: L10n.of(context).youHaveKnocked, - message: L10n.of(context).pleaseWaitUntilInvited, + title: l10n.youHaveKnocked, + message: l10n.pleaseWaitUntilInvited, ); return; } @@ -73,6 +75,7 @@ class PublicRoomDialog extends StatelessWidget { bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias; Future _search(BuildContext context) async { + final l10n = L10n.of(context); final chunk = this.chunk; if (chunk != null) return chunk; final query = await Matrix.of(context).client.queryPublicRooms( @@ -80,7 +83,7 @@ class PublicRoomDialog extends StatelessWidget { filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias), ); if (!query.chunk.any(_testRoom)) { - throw (L10n.of(context).noRoomsFound); + throw (l10n.noRoomsFound); } return query.chunk.firstWhere(_testRoom); } @@ -248,6 +251,7 @@ class PublicRoomDialog extends StatelessWidget { hintText: L10n.of(context).reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => Matrix.of(context).client.reportRoom( diff --git a/lib/widgets/adaptive_dialogs/user_dialog.dart b/lib/widgets/adaptive_dialogs/user_dialog.dart index 764a6ed8..4e30a5b3 100644 --- a/lib/widgets/adaptive_dialogs/user_dialog.dart +++ b/lib/widgets/adaptive_dialogs/user_dialog.dart @@ -220,6 +220,7 @@ class UserDialog extends StatelessWidget { hintText: L10n.of(context).reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => Matrix.of( diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 8ba7952c..a33cc911 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -53,16 +53,18 @@ class ChatSettingsPopupMenuState extends State { onSelected: (choice) async { switch (choice) { case ChatPopupMenuActions.leave: + final l10n = L10n.of(context); final router = GoRouter.of(context); final confirmed = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - message: L10n.of(context).archiveRoomDescription, - okLabel: L10n.of(context).leave, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + message: l10n.archiveRoomDescription, + okLabel: l10n.leave, + cancelLabel: l10n.cancel, isDestructive: true, ); if (confirmed != OkCancelResult.ok) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => widget.room.leave(), diff --git a/lib/widgets/future_loading_dialog.dart b/lib/widgets/future_loading_dialog.dart index 4cf2ee6d..9afb7b8f 100644 --- a/lib/widgets/future_loading_dialog.dart +++ b/lib/widgets/future_loading_dialog.dart @@ -5,6 +5,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix_api_lite/utils/logs.dart'; /// Displays a loading dialog which reacts to the given [future]. The dialog /// will be dismissed and the value will be returned when the future completes. @@ -40,6 +41,15 @@ Future> showFutureLoadingDialog({ } } + if (!context.mounted) { + Logs().e( + 'Unable to show loading dialog!', + Exception('The BuildContext is not mounted!'), + StackTrace.current, + ); + return Result.capture(futureExec); + } + final result = await showAdaptiveDialog>( context: context, barrierDismissible: barrierDismissible, diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index 47597286..cf97ad50 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -17,6 +17,7 @@ import 'package:universal_html/html.dart' as html; extension LocalNotificationsExtension on MatrixState { Future showLocalNotification(Event event) async { + final l10n = L10n.of(context); final roomId = event.room.id; if (activeRoomId == roomId) { if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { @@ -114,11 +115,11 @@ extension LocalNotificationsExtension on MatrixState { actions: [ NotificationAction( DesktopNotificationActions.openChat.name, - L10n.of(context).openChat, + l10n.openChat, ), NotificationAction( DesktopNotificationActions.seen.name, - L10n.of(context).markAsRead, + l10n.markAsRead, ), ], hints: hints, diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 2f7a3291..614bdb87 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -265,12 +265,19 @@ class MatrixState extends State with WidgetsBindingObserver { InitWithRestoreExtension.deleteSessionBackup(name); if (loggedInWithMultipleClients) { + final snackbarContext = + FluffyChatApp + .router + .routerDelegate + .navigatorKey + .currentContext ?? + context; + + if (!snackbarContext.mounted) return; + final l10n = L10n.of(snackbarContext); ScaffoldMessenger.of( - FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ?? - context, - ).showSnackBar( - SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)), - ); + snackbarContext, + ).showSnackBar(SnackBar(content: Text(l10n.oneClientLoggedOut))); return; } FluffyChatApp.router.go('/'); @@ -382,15 +389,17 @@ class MatrixState extends State with WidgetsBindingObserver { } Future dehydrateAction(BuildContext context) async { + final l10n = L10n.of(context); final response = await showOkCancelAlertDialog( context: context, isDestructive: true, - title: L10n.of(context).dehydrate, - message: L10n.of(context).dehydrateWarning, + title: l10n.dehydrate, + message: l10n.dehydrateWarning, ); if (response != OkCancelResult.ok) { return; } + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: client.exportDump, @@ -404,6 +413,7 @@ class MatrixState extends State with WidgetsBindingObserver { 'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup'; final file = MatrixFile(bytes: exportBytes, name: exportFileName); + if (!context.mounted) return; file.save(context); } } diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index 46ec8e96..1ae71387 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -14,6 +14,8 @@ Future showMemberActionsPopupMenu({ required User user, void Function()? onMention, }) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final theme = Theme.of(context); final displayname = user.calcDisplayname(); final isMe = user.room.client.userID == user.id; @@ -245,12 +247,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.kick: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).kickUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.kickUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.kick(), @@ -260,12 +263,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.ban: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).banUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.banUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.ban(), @@ -275,20 +279,22 @@ Future showMemberActionsPopupMenu({ case _MemberActions.report: final reason = await showTextInputDialog( context: context, - title: L10n.of(context).whyDoYouWantToReportThis, - okLabel: L10n.of(context).report, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).reason, + title: l10n.whyDoYouWantToReportThis, + okLabel: l10n.report, + cancelLabel: l10n.cancel, + hintText: l10n.reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => user.room.client.reportUser(user.id, reason), ); if (result.error != null) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), + if (!context.mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contentHasBeenReported)), ); return; case _MemberActions.info: @@ -304,12 +310,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.unban: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).unbanUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.unbanUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.unban(),