Merge pull request #2760 from krille-chan/krille/enable-buildcontext-async-gap-lint

refactor: Enable lint use_build_context_synchronously
This commit is contained in:
Krille-chan 2026-03-28 08:48:34 +01:00 committed by GitHub
commit e22c33a4b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 490 additions and 293 deletions

View file

@ -36,7 +36,6 @@ analyzer:
- dart_code_linter - dart_code_linter
errors: errors:
todo: ignore todo: ignore
use_build_context_synchronously: ignore
exclude: exclude:
- lib/l10n/*.dart - lib/l10n/*.dart

View file

@ -48,6 +48,7 @@ class ArchiveController extends State<Archive> {
OkCancelResult.ok) { OkCancelResult.ok) {
return; return;
} }
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
futureWithProgress: (onProgress) async { futureWithProgress: (onProgress) async {

View file

@ -382,6 +382,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
).wrongRecoveryKey, ).wrongRecoveryKey,
); );
} catch (e, s) { } catch (e, s) {
if (!context.mounted) return;
ErrorReporter( ErrorReporter(
context, context,
'Unable to open SSSS with recovery key', 'Unable to open SSSS with recovery key',
@ -425,6 +426,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context).cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
final req = await showFutureLoadingDialog( final req = await showFutureLoadingDialog(
context: context, context: context,
delay: false, delay: false,
@ -435,11 +437,12 @@ class BootstrapDialogState extends State<BootstrapDialog> {
}, },
); );
if (req.error != null) return; if (req.error != null) return;
if (!context.mounted) return;
final success = await KeyVerificationDialog( final success = await KeyVerificationDialog(
request: req.result!, request: req.result!,
).show(context); ).show(context);
if (success != true) return; if (success != true) return;
if (!mounted) return; if (!context.mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,

View file

@ -211,6 +211,7 @@ class ChatController extends State<ChatPageWithRoom>
context: context, context: context,
future: room.leave, future: room.leave,
); );
if (!mounted) return;
if (success.error != null) return; if (success.error != null) return;
context.go('/rooms'); context.go('/rooms');
} }
@ -475,8 +476,9 @@ class ChatController extends State<ChatPageWithRoom>
Future<void>? loadTimelineFuture; Future<void>? loadTimelineFuture;
Future<void> _getTimeline({String? eventContextId}) async { Future<void> _getTimeline({String? eventContextId}) async {
await Matrix.of(context).client.roomsLoading; final matrix = Matrix.of(context);
await Matrix.of(context).client.accountDataLoading; await matrix.client.roomsLoading;
await matrix.client.accountDataLoading;
if (eventContextId != null && if (eventContextId != null &&
(!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) { (!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) {
eventContextId = null; eventContextId = null;
@ -632,6 +634,7 @@ class ChatController extends State<ChatPageWithRoom>
Future<void> sendFileAction({FileType type = FileType.any}) async { Future<void> sendFileAction({FileType type = FileType.any}) async {
final files = await selectFiles(context, allowMultiple: true, type: type); final files = await selectFiles(context, allowMultiple: true, type: type);
if (files.isEmpty) return; if (files.isEmpty) return;
if (!mounted) return;
await showAdaptiveDialog( await showAdaptiveDialog(
context: context, context: context,
builder: (c) => SendFileDialog( builder: (c) => SendFileDialog(
@ -663,6 +666,7 @@ class ChatController extends State<ChatPageWithRoom>
FocusScope.of(context).requestFocus(FocusNode()); FocusScope.of(context).requestFocus(FocusNode());
final file = await ImagePicker().pickImage(source: ImageSource.camera); final file = await ImagePicker().pickImage(source: ImageSource.camera);
if (file == null) return; if (file == null) return;
if (!mounted) return;
await showAdaptiveDialog( await showAdaptiveDialog(
context: context, context: context,
@ -684,6 +688,7 @@ class ChatController extends State<ChatPageWithRoom>
maxDuration: const Duration(minutes: 1), maxDuration: const Duration(minutes: 1),
); );
if (file == null) return; if (file == null) return;
if (!mounted) return;
await showAdaptiveDialog( await showAdaptiveDialog(
context: context, context: context,
@ -726,26 +731,27 @@ class ChatController extends State<ChatPageWithRoom>
mimeType: mimeType, mimeType: mimeType,
); );
room try {
.sendFileEvent( await room.sendFileEvent(
file, file,
inReplyTo: replyEvent, inReplyTo: replyEvent,
threadRootEventId: activeThreadId, threadRootEventId: activeThreadId,
extraContent: { extraContent: {
'info': {...file.info, 'duration': duration}, 'info': {...file.info, 'duration': duration},
'org.matrix.msc3245.voice': {}, 'org.matrix.msc3245.voice': {},
'org.matrix.msc1767.audio': { 'org.matrix.msc1767.audio': {
'duration': duration, 'duration': duration,
'waveform': waveform, 'waveform': waveform,
},
}, },
) },
.catchError((e) { );
scaffoldMessenger.showSnackBar( } catch (e) {
SnackBar(content: Text((e as Object).toLocalizedString(context))), if (!mounted) return;
); scaffoldMessenger.showSnackBar(
return null; SnackBar(content: Text(e.toLocalizedString(context))),
}); );
return;
}
setState(() { setState(() {
replyEvent = null; replyEvent = null;
}); });
@ -807,29 +813,30 @@ class ChatController extends State<ChatPageWithRoom>
Future<void> reportEventAction() async { Future<void> reportEventAction() async {
final event = selectedEvents.single; final event = selectedEvents.single;
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final score = await showModalActionPopup<int>( final score = await showModalActionPopup<int>(
context: context, context: context,
title: L10n.of(context).reportMessage, title: l10n.reportMessage,
message: L10n.of(context).howOffensiveIsThisContent, message: l10n.howOffensiveIsThisContent,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
actions: [ actions: [
AdaptiveModalAction( AdaptiveModalAction(value: -100, label: l10n.extremeOffensive),
value: -100, AdaptiveModalAction(value: -50, label: l10n.offensive),
label: L10n.of(context).extremeOffensive, AdaptiveModalAction(value: 0, label: l10n.inoffensive),
),
AdaptiveModalAction(value: -50, label: L10n.of(context).offensive),
AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive),
], ],
); );
if (score == null) return; if (score == null) return;
if (!mounted) return;
final reason = await showTextInputDialog( final reason = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).whyDoYouWantToReportThis, title: l10n.whyDoYouWantToReportThis,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: L10n.of(context).reason, hintText: l10n.reason,
); );
if (reason == null || reason.isEmpty) return; if (reason == null || reason.isEmpty) return;
if (!mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.reportEvent( future: () => Matrix.of(context).client.reportEvent(
@ -840,12 +847,13 @@ class ChatController extends State<ChatPageWithRoom>
), ),
); );
if (result.error != null) return; if (result.error != null) return;
if (!mounted) return;
setState(() { setState(() {
showEmojiPicker = false; showEmojiPicker = false;
selectedEvents.clear(); selectedEvents.clear();
}); });
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), SnackBar(content: Text(l10n.contentHasBeenReported)),
); );
} }
@ -861,6 +869,7 @@ class ChatController extends State<ChatPageWithRoom>
} }
setState(selectedEvents.clear); setState(selectedEvents.clear);
} catch (e, s) { } catch (e, s) {
if (!mounted) return;
ErrorReporter( ErrorReporter(
context, context,
'Error while delete error events action', 'Error while delete error events action',
@ -885,6 +894,7 @@ class ChatController extends State<ChatPageWithRoom>
: null; : null;
if (reasonInput == null) return; if (reasonInput == null) return;
final reason = reasonInput.isEmpty ? null : reasonInput; final reason = reasonInput.isEmpty ? null : reasonInput;
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
futureWithProgress: (onProgress) async { futureWithProgress: (onProgress) async {
@ -1239,6 +1249,7 @@ class ChatController extends State<ChatPageWithRoom>
okLabel: L10n.of(context).unpin, okLabel: L10n.of(context).unpin,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context).cancel,
); );
if (!mounted) return;
if (response == OkCancelResult.ok) { if (response == OkCancelResult.ok) {
final events = room.pinnedEventIds final events = room.pinnedEventIds
..removeWhere((oldEvent) => oldEvent == eventId); ..removeWhere((oldEvent) => oldEvent == eventId);
@ -1328,17 +1339,18 @@ class ChatController extends State<ChatPageWithRoom>
Future<void> onPhoneButtonTap() async { Future<void> onPhoneButtonTap() async {
// VoIP required Android SDK 21 // VoIP required Android SDK 21
if (PlatformInfos.isAndroid) { if (PlatformInfos.isAndroid) {
DeviceInfoPlugin().androidInfo.then((value) { final androidInfo = await DeviceInfoPlugin().androidInfo;
if (value.version.sdkInt < 21) { if (!mounted) return;
Navigator.pop(context); if (androidInfo.version.sdkInt < 21) {
showOkAlertDialog( Navigator.pop(context);
context: context, await showOkAlertDialog(
title: L10n.of(context).unsupportedAndroidVersion, context: context,
message: L10n.of(context).unsupportedAndroidVersionLong, title: L10n.of(context).unsupportedAndroidVersion,
okLabel: L10n.of(context).close, message: L10n.of(context).unsupportedAndroidVersionLong,
); okLabel: L10n.of(context).close,
} );
}); return;
}
} }
final callType = await showModalActionPopup<CallType>( final callType = await showModalActionPopup<CallType>(
context: context, context: context,
@ -1359,11 +1371,13 @@ class ChatController extends State<ChatPageWithRoom>
], ],
); );
if (callType == null) return; if (callType == null) return;
if (!mounted) return;
final voipPlugin = Matrix.of(context).voipPlugin; final voipPlugin = Matrix.of(context).voipPlugin;
try { try {
await voipPlugin!.voip.inviteToCall(room, callType); await voipPlugin!.voip.inviteToCall(room, callType);
} catch (e) { } catch (e) {
if (!mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));

View file

@ -189,6 +189,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
}); });
} catch (e, s) { } catch (e, s) {
Logs().v('Could not download audio file', e, s); Logs().v('Could not download audio file', e, s);
if (!mounted) rethrow;
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
@ -208,6 +209,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
), ),
); );
} }
if (!mounted) return;
audioPlayer.play().onError( audioPlayer.play().onError(
ErrorReporter(context, 'Unable to play audio message').onErrorCallback, ErrorReporter(context, 'Unable to play audio message').onErrorCallback,

View file

@ -50,6 +50,7 @@ class _CuteContentState extends State<CuteContent> {
Future<void> addOverlay() async { Future<void> addOverlay() async {
_isOverlayShown = true; _isOverlayShown = true;
await Future.delayed(const Duration(milliseconds: 50)); await Future.delayed(const Duration(milliseconds: 50));
if (!mounted) return;
OverlayEntry? overlay; OverlayEntry? overlay;
overlay = OverlayEntry( overlay = OverlayEntry(

View file

@ -61,9 +61,11 @@ class MessageContent extends StatelessWidget {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final state = await client.getCryptoIdentityState(); final state = await client.getCryptoIdentityState();
if (!state.connected) { if (!state.connected) {
if (!context.mounted) return;
final success = await context.push('/backup'); final success = await context.push('/backup');
if (success != true) return; if (success != true) return;
} }
if (!context.mounted) return;
event.requestKey(); event.requestKey();
final sender = event.senderFromMemoryOrFallback; final sender = event.senderFromMemoryOrFallback;
await showAdaptiveBottomSheet( await showAdaptiveBottomSheet(

View file

@ -15,6 +15,7 @@ class PinnedEvents extends StatelessWidget {
const PinnedEvents(this.controller, {super.key}); const PinnedEvents(this.controller, {super.key});
Future<void> _displayPinnedEventsDialog(BuildContext context) async { Future<void> _displayPinnedEventsDialog(BuildContext context) async {
final l10n = L10n.of(context);
final eventsResult = await showFutureLoadingDialog( final eventsResult = await showFutureLoadingDialog(
context: context, context: context,
future: () => Future.wait( future: () => Future.wait(
@ -25,13 +26,14 @@ class PinnedEvents extends StatelessWidget {
); );
final events = eventsResult.result; final events = eventsResult.result;
if (events == null) return; if (events == null) return;
if (!context.mounted) return;
final eventId = events.length == 1 final eventId = events.length == 1
? events.single?.eventId ? events.single?.eventId
: await showModalActionPopup<String>( : await showModalActionPopup<String>(
context: context, context: context,
title: L10n.of(context).pin, title: l10n.pin,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
actions: events actions: events
.map( .map(
(event) => AdaptiveModalAction( (event) => AdaptiveModalAction(
@ -39,7 +41,7 @@ class PinnedEvents extends StatelessWidget {
icon: const Icon(Icons.push_pin_outlined), icon: const Icon(Icons.push_pin_outlined),
label: label:
event?.calcLocalizedBodyFallback( event?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)), MatrixLocals(l10n),
withSenderNamePrefix: true, withSenderNamePrefix: true,
hideReply: true, hideReply: true,
) ?? ) ??

View file

@ -44,6 +44,7 @@ class RecordingViewModelState extends State<RecordingViewModel> {
room.client.getConfig(); // Preload server file configuration. room.client.getConfig(); // Preload server file configuration.
if (PlatformInfos.isAndroid) { if (PlatformInfos.isAndroid) {
final info = await DeviceInfoPlugin().androidInfo; final info = await DeviceInfoPlugin().androidInfo;
if (!mounted) return;
if (info.version.sdkInt < 19) { if (info.version.sdkInt < 19) {
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
@ -76,6 +77,7 @@ class RecordingViewModelState extends State<RecordingViewModel> {
final result = await audioRecorder.hasPermission(); final result = await audioRecorder.hasPermission();
if (result != true) { if (result != true) {
if (!mounted) return;
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).oopsSomethingWentWrong, title: L10n.of(context).oopsSomethingWentWrong,
@ -97,10 +99,12 @@ class RecordingViewModelState extends State<RecordingViewModel> {
), ),
path: path ?? '', path: path ?? '',
); );
if (!mounted) return;
setState(() => duration = Duration.zero); setState(() => duration = Duration.zero);
_subscribe(); _subscribe();
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to start voice message recording', e, s); Logs().w('Unable to start voice message recording', e, s);
if (!mounted) return;
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).oopsSomethingWentWrong, title: L10n.of(context).oopsSomethingWentWrong,

View file

@ -146,6 +146,7 @@ class SendFileDialogState extends State<SendFileDialog> {
scaffoldMessenger.clearSnackBars(); scaffoldMessenger.clearSnackBars();
} catch (e) { } catch (e) {
scaffoldMessenger.clearSnackBars(); scaffoldMessenger.clearSnackBars();
if (!mounted || !widget.outerContext.mounted) rethrow;
final theme = Theme.of(context); final theme = Theme.of(context);
scaffoldMessenger.showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar( SnackBar(

View file

@ -81,6 +81,7 @@ class SendLocationDialogState extends State<SendLocationDialog> {
context: context, context: context,
future: () => widget.room.sendLocation(body, uri), future: () => widget.room.sendLocation(body, uri),
); );
if (!mounted) return;
Navigator.of(context, rootNavigator: false).pop(); Navigator.of(context, rootNavigator: false).pop();
} }

View file

@ -44,6 +44,7 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
maxSelections: _allowMultipleAnswers ? _answers.length : 1, maxSelections: _allowMultipleAnswers ? _answers.length : 1,
txid: _txid, txid: _txid,
); );
if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to create poll', e, s); Logs().w('Unable to create poll', e, s);

View file

@ -160,6 +160,7 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
} }
Future<void> updateRoomAction() async { Future<void> updateRoomAction() async {
final l10n = L10n.of(context);
final roomVersion = room final roomVersion = room
.getState(EventTypes.RoomCreate)! .getState(EventTypes.RoomCreate)!
.content .content
@ -170,10 +171,11 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
); );
final capabilities = capabilitiesResult.result; final capabilities = capabilitiesResult.result;
if (capabilities == null) return; if (capabilities == null) return;
if (!mounted) return;
final newVersion = await showModalActionPopup<String>( final newVersion = await showModalActionPopup<String>(
context: context, context: context,
title: L10n.of(context).replaceRoomWithNewerVersion, title: l10n.replaceRoomWithNewerVersion,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
actions: capabilities.mRoomVersions!.available.entries actions: capabilities.mRoomVersions!.available.entries
.where((r) => r.key != roomVersion) .where((r) => r.key != roomVersion)
.map( .map(
@ -185,18 +187,20 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
) )
.toList(), .toList(),
); );
if (newVersion == null || if (newVersion == null) return;
OkCancelResult.cancel == if (!mounted) return;
await showOkCancelAlertDialog( final confirmUpgrade = await showOkCancelAlertDialog(
context: context, context: context,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
message: L10n.of(context).roomUpgradeDescription, message: l10n.roomUpgradeDescription,
isDestructive: true, isDestructive: true,
)) { );
if (confirmUpgrade == OkCancelResult.cancel) {
return; return;
} }
if (!mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
futureWithProgress: (onProgress) async { futureWithProgress: (onProgress) async {
@ -243,6 +247,7 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
} }
Future<void> addAlias() async { Future<void> addAlias() async {
final l10n = L10n.of(context);
final domain = room.client.userID?.domain; final domain = room.client.userID?.domain;
if (domain == null) { if (domain == null) {
throw Exception('userID or domain is null! This should never happen.'); throw Exception('userID or domain is null! This should never happen.');
@ -250,11 +255,12 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).editRoomAliases, title: l10n.editRoomAliases,
prefixText: '#', prefixText: '#',
suffixText: domain, suffixText: domain,
hintText: L10n.of(context).alias, hintText: l10n.alias,
); );
if (!mounted) return;
final aliasLocalpart = input?.trim(); final aliasLocalpart = input?.trim();
if (aliasLocalpart == null || aliasLocalpart.isEmpty) return; if (aliasLocalpart == null || aliasLocalpart.isEmpty) return;
final alias = '#$aliasLocalpart:$domain'; final alias = '#$aliasLocalpart:$domain';
@ -264,17 +270,19 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
future: () => room.client.setRoomAlias(alias, room.id), future: () => room.client.setRoomAlias(alias, room.id),
); );
if (result.error != null) return; if (result.error != null) return;
if (!mounted) return;
setState(() {}); setState(() {});
if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return; if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return;
final canonicalAliasConsent = await showOkCancelAlertDialog( final canonicalAliasConsent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).setAsCanonicalAlias, title: l10n.setAsCanonicalAlias,
message: alias, message: alias,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).no, cancelLabel: l10n.no,
); );
if (!mounted) return;
final altAliases = final altAliases =
room room

View file

@ -37,69 +37,78 @@ class ChatDetailsController extends State<ChatDetails> {
String? get roomId => widget.roomId; String? get roomId => widget.roomId;
Future<void> setDisplaynameAction() async { Future<void> setDisplaynameAction() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final room = Matrix.of(context).client.getRoomById(roomId!)!; final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog( final input = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).changeTheNameOfTheGroup, title: l10n.changeTheNameOfTheGroup,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
initialText: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), initialText: room.getLocalizedDisplayname(MatrixLocals(l10n)),
); );
if (input == null) return; if (input == null) return;
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.setName(input), future: () => room.setName(input),
); );
if (!mounted) return;
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)), SnackBar(content: Text(l10n.displaynameHasBeenChanged)),
); );
} }
} }
Future<void> setTopicAction() async { Future<void> setTopicAction() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final room = Matrix.of(context).client.getRoomById(roomId!)!; final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog( final input = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).setChatDescription, title: l10n.setChatDescription,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: L10n.of(context).noChatDescriptionYet, hintText: l10n.noChatDescriptionYet,
initialText: room.topic, initialText: room.topic,
minLines: 4, minLines: 4,
maxLines: 8, maxLines: 8,
); );
if (input == null) return; if (input == null) return;
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.setDescription(input), future: () => room.setDescription(input),
); );
if (!mounted) return;
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).chatDescriptionHasBeenChanged)), SnackBar(content: Text(l10n.chatDescriptionHasBeenChanged)),
); );
} }
} }
Future<void> setAvatarAction() async { Future<void> setAvatarAction() async {
final l10n = L10n.of(context);
final room = Matrix.of(context).client.getRoomById(roomId!); final room = Matrix.of(context).client.getRoomById(roomId!);
final actions = [ final actions = [
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.camera, value: AvatarAction.camera,
label: L10n.of(context).openCamera, label: l10n.openCamera,
isDefaultAction: true, isDefaultAction: true,
icon: const Icon(Icons.camera_alt_outlined), icon: const Icon(Icons.camera_alt_outlined),
), ),
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.file, value: AvatarAction.file,
label: L10n.of(context).openGallery, label: l10n.openGallery,
icon: const Icon(Icons.photo_outlined), icon: const Icon(Icons.photo_outlined),
), ),
if (room?.avatar != null) if (room?.avatar != null)
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.remove, value: AvatarAction.remove,
label: L10n.of(context).delete, label: l10n.delete,
isDestructive: true, isDestructive: true,
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
), ),
@ -108,11 +117,12 @@ class ChatDetailsController extends State<ChatDetails> {
? actions.single.value ? actions.single.value
: await showModalActionPopup<AvatarAction>( : await showModalActionPopup<AvatarAction>(
context: context, context: context,
title: L10n.of(context).editRoomAvatar, title: l10n.editRoomAvatar,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
actions: actions, actions: actions,
); );
if (action == null) return; if (action == null) return;
if (!mounted) return;
if (action == AvatarAction.remove) { if (action == AvatarAction.remove) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
@ -131,6 +141,7 @@ class ChatDetailsController extends State<ChatDetails> {
if (result == null) return; if (result == null) return;
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
} else { } else {
if (!mounted) return;
final picked = await selectFiles( final picked = await selectFiles(
context, context,
allowMultiple: false, allowMultiple: false,
@ -143,6 +154,7 @@ class ChatDetailsController extends State<ChatDetails> {
name: pickedFile.name, name: pickedFile.name,
); );
} }
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room!.setAvatar(file), future: () => room!.setAvatar(file),

View file

@ -30,38 +30,40 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
} }
Future<void> enableEncryption(_) async { Future<void> enableEncryption(_) async {
final l10n = L10n.of(context);
if (room.encrypted) { if (room.encrypted) {
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).sorryThatsNotPossible, title: l10n.sorryThatsNotPossible,
message: L10n.of(context).disableEncryptionWarning, message: l10n.disableEncryptionWarning,
); );
return; return;
} }
if (room.joinRules == JoinRules.public) { if (room.joinRules == JoinRules.public) {
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).sorryThatsNotPossible, title: l10n.sorryThatsNotPossible,
message: L10n.of(context).noEncryptionForPublicRooms, message: l10n.noEncryptionForPublicRooms,
); );
return; return;
} }
if (!room.canChangeStateEvent(EventTypes.Encryption)) { if (!room.canChangeStateEvent(EventTypes.Encryption)) {
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).sorryThatsNotPossible, title: l10n.sorryThatsNotPossible,
message: L10n.of(context).noPermission, message: l10n.noPermission,
); );
return; return;
} }
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
message: L10n.of(context).enableEncryptionWarning, message: l10n.enableEncryptionWarning,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.enableEncryption(), future: () => room.enableEncryption(),
@ -69,14 +71,16 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
} }
Future<void> startVerification() async { Future<void> startVerification() async {
final l10n = L10n.of(context);
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).verifyOtherUser, title: l10n.verifyOtherUser,
message: L10n.of(context).verifyOtherUserDescription, message: l10n.verifyOtherUserDescription,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
if (!mounted) return;
final req = await room.client.userDeviceKeys[room.directChatMatrixID]! final req = await room.client.userDeviceKeys[room.directChatMatrixID]!
.startVerification(); .startVerification();
req.onUpdate = () { req.onUpdate = () {
@ -84,6 +88,7 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
setState(() {}); setState(() {});
} }
}; };
if (!mounted) return;
await KeyVerificationDialog(request: req).show(context); await KeyVerificationDialog(request: req).show(context);
} }

View file

@ -90,6 +90,8 @@ class ChatListController extends State<ChatList>
}); });
Future<void> onChatTap(Room room) async { Future<void> onChatTap(Room room) async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
if (room.membership == Membership.invite) { if (room.membership == Membership.invite) {
final joinResult = await showFutureLoadingDialog( final joinResult = await showFutureLoadingDialog(
context: context, context: context,
@ -105,10 +107,11 @@ class ChatListController extends State<ChatList>
); );
if (joinResult.error != null) return; if (joinResult.error != null) return;
} }
if (!mounted) return;
if (room.membership == Membership.ban) { if (room.membership == Membership.ban) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).youHaveBeenBannedFromThisChat)), SnackBar(content: Text(l10n.youHaveBeenBannedFromThisChat)),
); );
return; return;
} }
@ -156,23 +159,25 @@ class ChatListController extends State<ChatList>
static const String _serverStoreNamespace = 'im.fluffychat.search.server'; static const String _serverStoreNamespace = 'im.fluffychat.search.server';
Future<void> setServer() async { Future<void> setServer() async {
final matrix = Matrix.of(context);
final l10n = L10n.of(context);
final newServer = await showTextInputDialog( final newServer = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
title: L10n.of(context).changeTheHomeserver, title: l10n.changeTheHomeserver,
context: context, context: context,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
prefixText: 'https://', prefixText: 'https://',
hintText: Matrix.of(context).client.homeserver?.host, hintText: matrix.client.homeserver?.host,
initialText: searchServer, initialText: searchServer,
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
autocorrect: false, autocorrect: false,
validator: (server) => server.contains('.') == true validator: (server) =>
? null server.contains('.') == true ? null : l10n.invalidServerName,
: L10n.of(context).invalidServerName,
); );
if (newServer == null) return; if (newServer == null) return;
Matrix.of(context).store.setString(_serverStoreNamespace, newServer); if (!mounted) return;
matrix.store.setString(_serverStoreNamespace, newServer);
setState(() { setState(() {
searchServer = newServer; searchServer = newServer;
}); });
@ -185,6 +190,7 @@ class ChatListController extends State<ChatList>
Future<void> _search() async { Future<void> _search() async {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final scaffoldMessenger = ScaffoldMessenger.of(context);
if (!isSearching) { if (!isSearching) {
setState(() { setState(() {
isSearching = true; isSearching = true;
@ -227,9 +233,10 @@ class ChatListController extends State<ChatList>
); );
} catch (e, s) { } catch (e, s) {
Logs().w('Searching has crashed', e, s); Logs().w('Searching has crashed', e, s);
ScaffoldMessenger.of( if (!mounted) return;
context, scaffoldMessenger.showSnackBar(
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); SnackBar(content: Text(e.toLocalizedString(context))),
);
} }
if (!isSearchMode) return; if (!isSearchMode) return;
setState(() { setState(() {
@ -293,9 +300,8 @@ class ChatListController extends State<ChatList>
Future<void> editSpace(BuildContext context, String spaceId) async { Future<void> editSpace(BuildContext context, String spaceId) async {
await Matrix.of(context).client.getRoomById(spaceId)!.postLoad(); await Matrix.of(context).client.getRoomById(spaceId)!.postLoad();
if (mounted) { if (!context.mounted) return;
context.push('/rooms/$spaceId/details'); context.push('/rooms/$spaceId/details');
}
} }
// Needs to match GroupsSpacesEntry for 'separate group' checking. // Needs to match GroupsSpacesEntry for 'separate group' checking.
@ -742,6 +748,7 @@ class ChatListController extends State<ChatList>
.toList(), .toList(),
); );
if (space == null) return; if (space == null) return;
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => space.setSpaceChild(room.id), future: () => space.setSpaceChild(room.id),
@ -767,16 +774,18 @@ class ChatListController extends State<ChatList>
} }
Future<void> setStatus() async { Future<void> setStatus() async {
final l10n = L10n.of(context);
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final currentPresence = await client.fetchCurrentPresence(client.userID!); final currentPresence = await client.fetchCurrentPresence(client.userID!);
if (!mounted) return;
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).setStatus, title: l10n.setStatus,
message: L10n.of(context).leaveEmptyToClearStatus, message: l10n.leaveEmptyToClearStatus,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: L10n.of(context).statusExampleMessage, hintText: l10n.statusExampleMessage,
maxLines: 6, maxLines: 6,
minLines: 1, minLines: 1,
maxLength: 255, maxLength: 255,
@ -904,18 +913,21 @@ class ChatListController extends State<ChatList>
if (action == null) return; if (action == null) return;
switch (action) { switch (action) {
case EditBundleAction.addToBundle: case EditBundleAction.addToBundle:
if (!mounted) return;
final bundle = await showTextInputDialog( final bundle = await showTextInputDialog(
context: context, context: context,
title: l10n.bundleName, title: l10n.bundleName,
hintText: l10n.bundleName, hintText: l10n.bundleName,
); );
if (bundle == null || bundle.isEmpty || bundle.isEmpty) return; if (bundle == null || bundle.isEmpty || bundle.isEmpty) return;
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.setAccountBundle(bundle), future: () => client.setAccountBundle(bundle),
); );
break; break;
case EditBundleAction.removeFromBundle: case EditBundleAction.removeFromBundle:
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.removeFromAccountBundle(activeBundle!), future: () => client.removeFromAccountBundle(activeBundle!),

View file

@ -209,6 +209,7 @@ class ClientChooserButton extends StatelessWidget {
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context).cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
context.go('/rooms/settings/addaccount'); context.go('/rooms/settings/addaccount');
break; break;
case SettingsAction.newGroup: case SettingsAction.newGroup:

View file

@ -170,14 +170,17 @@ class _SpaceViewState extends State<SpaceView> {
switch (action) { switch (action) {
case SpaceActions.settings: case SpaceActions.settings:
await space?.postLoad(); await space?.postLoad();
if (!mounted) return;
context.push('/rooms/${widget.spaceId}/details'); context.push('/rooms/${widget.spaceId}/details');
break; break;
case SpaceActions.invite: case SpaceActions.invite:
await space?.postLoad(); await space?.postLoad();
if (!mounted) return;
context.push('/rooms/${widget.spaceId}/invite'); context.push('/rooms/${widget.spaceId}/invite');
break; break;
case SpaceActions.members: case SpaceActions.members:
await space?.postLoad(); await space?.postLoad();
if (!mounted) return;
context.push('/rooms/${widget.spaceId}/details/members'); context.push('/rooms/${widget.spaceId}/details/members');
break; break;
case SpaceActions.leave: case SpaceActions.leave:

View file

@ -36,6 +36,7 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
currentLevel: currentLevel, currentLevel: currentLevel,
); );
if (newLevel == null) return; if (newLevel == null) return;
if (!context.mounted) return;
final content = Map<String, dynamic>.from( final content = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels)!.content, room.getState(EventTypes.RoomPowerLevels)!.content,
); );

View file

@ -41,12 +41,15 @@ class DevicesSettingsController extends State<DevicesSettings> {
Future<void> _checkChatBackup() async { Future<void> _checkChatBackup() async {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final state = await client.getCryptoIdentityState(); final state = await client.getCryptoIdentityState();
if (!mounted) return;
setState(() { setState(() {
chatBackupEnabled = state.initialized && !state.connected; chatBackupEnabled = state.initialized && !state.connected;
}); });
} }
Future<void> removeDevicesAction(List<Device> devices) async { Future<void> removeDevicesAction(List<Device> devices) async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final wellKnown = await Result.capture(client.getWellknown()); final wellKnown = await Result.capture(client.getWellknown());
@ -57,18 +60,19 @@ class DevicesSettingsController extends State<DevicesSettings> {
launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView); launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView);
return; return;
} }
if (!mounted) return;
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
okLabel: L10n.of(context).remove, okLabel: l10n.remove,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
message: L10n.of(context).removeDevicesDescription, message: l10n.removeDevicesDescription,
isDestructive: true, isDestructive: true,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
} }
final matrix = Matrix.of(context); if (!mounted) return;
final deviceIds = <String>[]; final deviceIds = <String>[];
for (final userDevice in devices) { for (final userDevice in devices) {
deviceIds.add(userDevice.deviceId); deviceIds.add(userDevice.deviceId);
@ -85,19 +89,21 @@ class DevicesSettingsController extends State<DevicesSettings> {
} }
Future<void> renameDeviceAction(Device device) async { Future<void> renameDeviceAction(Device device) async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final displayName = await showTextInputDialog( final displayName = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).changeDeviceName, title: l10n.changeDeviceName,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: device.displayName, hintText: device.displayName,
); );
if (displayName == null) return; if (displayName == null) return;
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of( future: () =>
context, matrix.client.updateDevice(device.deviceId, displayName: displayName),
).client.updateDevice(device.deviceId, displayName: displayName),
); );
if (success.error == null) { if (success.error == null) {
reload(); reload();
@ -105,17 +111,20 @@ class DevicesSettingsController extends State<DevicesSettings> {
} }
Future<void> verifyDeviceAction(Device device) async { Future<void> verifyDeviceAction(Device device) async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).verifyOtherDevice, title: l10n.verifyOtherDevice,
message: L10n.of(context).verifyOtherDeviceDescription, message: l10n.verifyOtherDeviceDescription,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
final req = await Matrix.of(context) if (!mounted) return;
final req = await matrix
.client .client
.userDeviceKeys[Matrix.of(context).client.userID!]! .userDeviceKeys[matrix.client.userID!]!
.deviceKeys[device.deviceId]! .deviceKeys[device.deviceId]!
.startVerification(); .startVerification();
req.onUpdate = () { req.onUpdate = () {
@ -126,6 +135,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
setState(() {}); setState(() {});
} }
}; };
if (!mounted) return;
await KeyVerificationDialog(request: req).show(context); await KeyVerificationDialog(request: req).show(context);
} }

View file

@ -92,10 +92,12 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
); );
}); });
} on IOException catch (e) { } on IOException catch (e) {
if (!mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
} catch (e, s) { } catch (e, s) {
if (!mounted) return;
ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s); ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s);
} }
} }

View file

@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Future<void> restoreBackupFlow(BuildContext context) async { Future<void> restoreBackupFlow(BuildContext context) async {
final matrix = Matrix.of(context);
final picked = await selectFiles(context); final picked = await selectFiles(context);
final file = picked.firstOrNull; final file = picked.firstOrNull;
if (file == null) return; if (file == null) return;
@ -12,9 +13,9 @@ Future<void> restoreBackupFlow(BuildContext context) async {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () async { future: () async {
final client = await Matrix.of(context).getLoginClient(); final client = await matrix.getLoginClient();
await client.importDump(String.fromCharCodes(await file.readAsBytes())); await client.importDump(String.fromCharCodes(await file.readAsBytes()));
Matrix.of(context).initMatrix(); matrix.initMatrix();
}, },
); );
} }

View file

@ -166,6 +166,7 @@ class IntroPage extends StatelessWidget {
final client = await Matrix.of( final client = await Matrix.of(
context, context,
).getLoginClient(); ).getLoginClient();
if (!context.mounted) return;
context.go( context.go(
'${GoRouterState.of(context).uri.path}/login', '${GoRouterState.of(context).uri.path}/login',
extra: client, extra: client,

View file

@ -75,7 +75,8 @@ class _IntroPagePresenterState extends State<IntroPagePresenter> {
final client = await Matrix.of(context).getLoginClient(); final client = await Matrix.of(context).getLoginClient();
await client.checkHomeserver(homeserverUrl); await client.checkHomeserver(homeserverUrl);
await client.oidcLogin(session: session, code: code, state: state); await client.oidcLogin(session: session, code: code, state: state);
if (context.mounted) context.go('/backup'); if (!mounted) return;
context.go('/backup');
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to login via OIDC', e, s); Logs().w('Unable to login via OIDC', e, s);
if (mounted) { if (mounted) {

View file

@ -54,6 +54,8 @@ class InvitationSelectionController extends State<InvitationSelection> {
String id, String id,
String displayname, String displayname,
) async { ) async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final room = Matrix.of(context).client.getRoomById(roomId!)!; final room = Matrix.of(context).client.getRoomById(roomId!)!;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
@ -61,10 +63,9 @@ class InvitationSelectionController extends State<InvitationSelection> {
future: () => room.invite(id), future: () => room.invite(id),
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( if (!context.mounted) return;
SnackBar( scaffoldMessenger.showSnackBar(
content: Text(L10n.of(context).contactHasBeenInvitedToTheGroup), SnackBar(content: Text(l10n.contactHasBeenInvitedToTheGroup)),
),
); );
} }
} }
@ -91,6 +92,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
try { try {
response = await matrix.client.searchUserDirectory(text, limit: 10); response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (e) { } catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context)))); ).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context))));

View file

@ -82,6 +82,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
}, },
); );
if (valid.error != null) { if (valid.error != null) {
if (!mounted) return;
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
@ -178,9 +179,10 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
); );
buttons.add( buttons.add(
AdaptiveDialogAction( AdaptiveDialogAction(
onPressed: () => widget.request.rejectVerification().then( onPressed: () => widget.request.rejectVerification().then((_) {
(_) => Navigator.of(context, rootNavigator: false).pop(false), if (!context.mounted) return;
), Navigator.of(context, rootNavigator: false).pop(false);
}),
child: Text( child: Text(
L10n.of(context).reject, L10n.of(context).reject,
style: TextStyle(color: theme.colorScheme.error), style: TextStyle(color: theme.colorScheme.error),

View file

@ -130,15 +130,19 @@ class LoginController extends State<Login> {
Logs().v( Logs().v(
'$newDomain is not running a homeserver, asking to use $oldHomeserver', '$newDomain is not running a homeserver, asking to use $oldHomeserver',
); );
if (!mounted) return;
final l10n = L10n.of(context);
final dialogResult = await showOkCancelAlertDialog( final dialogResult = await showOkCancelAlertDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
title: L10n.of( title: l10n.noMatrixServer(
context, newDomain.toString(),
).noMatrixServer(newDomain.toString(), oldHomeserver.toString()), oldHomeserver.toString(),
okLabel: L10n.of(context).ok, ),
cancelLabel: L10n.of(context).cancel, okLabel: l10n.ok,
cancelLabel: l10n.cancel,
); );
if (!mounted) return;
if (dialogResult == OkCancelResult.ok) { if (dialogResult == OkCancelResult.ok) {
if (mounted) setState(() => usernameError = null); if (mounted) setState(() => usernameError = null);
} else { } else {
@ -156,26 +160,30 @@ class LoginController extends State<Login> {
} }
} catch (e) { } catch (e) {
widget.client.homeserver = oldHomeserver; widget.client.homeserver = oldHomeserver;
if (!mounted) return;
usernameError = e.toLocalizedString(context); usernameError = e.toLocalizedString(context);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
} }
Future<void> passwordForgotten() async { Future<void> passwordForgotten() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).passwordForgotten, title: l10n.passwordForgotten,
message: L10n.of(context).enterAnEmailAddress, message: l10n.enterAnEmailAddress,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
initialText: usernameController.text.isEmail initialText: usernameController.text.isEmail
? usernameController.text ? usernameController.text
: '', : '',
hintText: L10n.of(context).enterAnEmailAddress, hintText: l10n.enterAnEmailAddress,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
); );
if (input == null) return; if (input == null) return;
if (!mounted) return;
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
@ -186,27 +194,30 @@ class LoginController extends State<Login> {
), ),
); );
if (response.error != null) return; if (response.error != null) return;
if (!mounted) return;
final password = await showTextInputDialog( final password = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).passwordForgotten, title: l10n.passwordForgotten,
message: L10n.of(context).chooseAStrongPassword, message: l10n.chooseAStrongPassword,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: '******', hintText: '******',
obscureText: true, obscureText: true,
minLines: 1, minLines: 1,
maxLines: 1, maxLines: 1,
); );
if (password == null) return; if (password == null) return;
if (!mounted) return;
final ok = await showOkAlertDialog( final ok = await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).weSentYouAnEmail, title: l10n.weSentYouAnEmail,
message: L10n.of(context).pleaseClickOnLink, message: l10n.pleaseClickOnLink,
okLabel: L10n.of(context).iHaveClickedOnLink, okLabel: l10n.iHaveClickedOnLink,
); );
if (ok != OkCancelResult.ok) return; if (ok != OkCancelResult.ok) return;
if (!mounted) return;
final data = <String, dynamic>{ final data = <String, dynamic>{
'new_password': password, 'new_password': password,
'logout_devices': false, 'logout_devices': false,
@ -226,9 +237,10 @@ class LoginController extends State<Login> {
data: data, data: data,
), ),
); );
if (!mounted) return;
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), SnackBar(content: Text(l10n.passwordHasBeenChanged)),
); );
usernameController.text = input; usernameController.text = input;
passwordController.text = password; passwordController.text = password;

View file

@ -81,17 +81,19 @@ class NewPrivateChatController extends State<NewPrivateChat> {
void inviteAction() => FluffyShare.shareInviteLink(context); void inviteAction() => FluffyShare.shareInviteLink(context);
Future<void> openScannerAction() async { Future<void> openScannerAction() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
if (PlatformInfos.isAndroid) { if (PlatformInfos.isAndroid) {
final info = await DeviceInfoPlugin().androidInfo; final info = await DeviceInfoPlugin().androidInfo;
if (!mounted) return;
if (info.version.sdkInt < 21) { if (info.version.sdkInt < 21) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar( SnackBar(content: Text(l10n.unsupportedAndroidVersionLong)),
content: Text(L10n.of(context).unsupportedAndroidVersionLong),
),
); );
return; return;
} }
} }
if (!mounted) return;
await showAdaptiveBottomSheet( await showAdaptiveBottomSheet(
context: context, context: context,
builder: (_) => QrScannerModal( builder: (_) => QrScannerModal(
@ -101,12 +103,15 @@ class NewPrivateChatController extends State<NewPrivateChat> {
} }
Future<void> copyUserId() async { Future<void> copyUserId() async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final l10n = L10n.of(context);
await Clipboard.setData( await Clipboard.setData(
ClipboardData(text: Matrix.of(context).client.userID!), ClipboardData(text: Matrix.of(context).client.userID!),
); );
ScaffoldMessenger.of( if (!mounted) return;
context, scaffoldMessenger.showSnackBar(
).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard))); SnackBar(content: Text(l10n.copiedToClipboard)),
);
} }
void openUserModal(Profile profile) => void openUserModal(Profile profile) =>

View file

@ -66,6 +66,7 @@ class QrScannerModalState extends State<QrScannerModal> {
late StreamSubscription sub; late StreamSubscription sub;
sub = controller.scannedDataStream.listen((scanData) { sub = controller.scannedDataStream.listen((scanData) {
sub.cancel(); sub.cancel();
if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
final data = scanData.code; final data = scanData.code;
if (data != null) widget.onScan(data); if (data != null) widget.onScan(data);

View file

@ -35,18 +35,20 @@ class SettingsController extends State<Settings> {
}); });
Future<void> setDisplaynameAction() async { Future<void> setDisplaynameAction() async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final profile = await profileFuture; final profile = await profileFuture;
if (!mounted) return;
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).editDisplayname, title: l10n.editDisplayname,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
initialText: initialText: profile?.displayName ?? matrix.client.userID!.localpart,
profile?.displayName ?? Matrix.of(context).client.userID!.localpart,
); );
if (input == null) return; if (input == null) return;
final matrix = Matrix.of(context); if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => matrix.client.setProfileField( future: () => matrix.client.setProfileField(
@ -61,19 +63,21 @@ class SettingsController extends State<Settings> {
} }
Future<void> logoutAction() async { Future<void> logoutAction() async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSureYouWantToLogout, title: l10n.areYouSureYouWantToLogout,
message: L10n.of(context).noBackupWarning, message: l10n.noBackupWarning,
isDestructive: cryptoIdentityConnected == false, isDestructive: cryptoIdentityConnected == false,
okLabel: L10n.of(context).logout, okLabel: l10n.logout,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
} }
final matrix = Matrix.of(context); if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => matrix.client.logout(), future: () => matrix.client.logout(),
@ -81,24 +85,27 @@ class SettingsController extends State<Settings> {
} }
Future<void> setAvatarAction() async { Future<void> setAvatarAction() async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final profile = await profileFuture; final profile = await profileFuture;
if (!mounted) return;
final actions = [ final actions = [
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.camera, value: AvatarAction.camera,
label: L10n.of(context).openCamera, label: l10n.openCamera,
isDefaultAction: true, isDefaultAction: true,
icon: const Icon(Icons.camera_alt_outlined), icon: const Icon(Icons.camera_alt_outlined),
), ),
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.file, value: AvatarAction.file,
label: L10n.of(context).openGallery, label: l10n.openGallery,
icon: const Icon(Icons.photo_outlined), icon: const Icon(Icons.photo_outlined),
), ),
if (profile?.avatarUrl != null) if (profile?.avatarUrl != null)
AdaptiveModalAction( AdaptiveModalAction(
value: AvatarAction.remove, value: AvatarAction.remove,
label: L10n.of(context).removeYourAvatar, label: l10n.removeYourAvatar,
isDestructive: true, isDestructive: true,
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
), ),
@ -107,12 +114,12 @@ class SettingsController extends State<Settings> {
? actions.single.value ? actions.single.value
: await showModalActionPopup<AvatarAction>( : await showModalActionPopup<AvatarAction>(
context: context, context: context,
title: L10n.of(context).changeYourAvatar, title: l10n.changeYourAvatar,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
actions: actions, actions: actions,
); );
if (action == null) return; if (action == null) return;
final matrix = Matrix.of(context); if (!mounted) return;
if (action == AvatarAction.remove) { if (action == AvatarAction.remove) {
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
@ -134,6 +141,7 @@ class SettingsController extends State<Settings> {
if (result == null) return; if (result == null) return;
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
} else { } else {
if (!mounted) return;
final result = await selectFiles(context, type: FileType.image); final result = await selectFiles(context, type: FileType.image);
final pickedFile = result.firstOrNull; final pickedFile = result.firstOrNull;
if (pickedFile == null) return; if (pickedFile == null) return;
@ -142,6 +150,7 @@ class SettingsController extends State<Settings> {
name: pickedFile.name, name: pickedFile.name,
); );
} }
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => matrix.client.setAvatar(file), future: () => matrix.client.setAvatar(file),
@ -168,6 +177,7 @@ class SettingsController extends State<Settings> {
} }
final state = await client.getCryptoIdentityState(); final state = await client.getCryptoIdentityState();
if (!mounted) return;
setState(() { setState(() {
cryptoIdentityConnected = state.initialized && state.connected; cryptoIdentityConnected = state.initialized && state.connected;
}); });

View file

@ -19,41 +19,48 @@ class Settings3Pid extends StatefulWidget {
class Settings3PidController extends State<Settings3Pid> { class Settings3PidController extends State<Settings3Pid> {
Future<void> add3PidAction() async { Future<void> add3PidAction() async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).enterAnEmailAddress, title: l10n.enterAnEmailAddress,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: L10n.of(context).enterAnEmailAddress, hintText: l10n.enterAnEmailAddress,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
); );
if (input == null) return; if (input == null) return;
if (!mounted) return;
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.requestTokenToRegisterEmail( future: () => matrix.client.requestTokenToRegisterEmail(
clientSecret, clientSecret,
input, input,
Settings3Pid.sendAttempt++, Settings3Pid.sendAttempt++,
), ),
); );
if (response.error != null) return; if (response.error != null) return;
if (!mounted) return;
final ok = await showOkAlertDialog( final ok = await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).weSentYouAnEmail, title: l10n.weSentYouAnEmail,
message: L10n.of(context).pleaseClickOnLink, message: l10n.pleaseClickOnLink,
okLabel: L10n.of(context).iHaveClickedOnLink, okLabel: l10n.iHaveClickedOnLink,
); );
if (ok != OkCancelResult.ok) return; if (ok != OkCancelResult.ok) return;
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
delay: false, delay: false,
future: () => Matrix.of(context).client.uiaRequestBackground( future: () => matrix.client.uiaRequestBackground(
(auth) => Matrix.of( (auth) => matrix.client.add3PID(
context, clientSecret,
).client.add3PID(clientSecret, response.result!.sid, auth: auth), response.result!.sid,
auth: auth,
),
), ),
); );
if (success.error != null) return; if (success.error != null) return;
@ -63,21 +70,25 @@ class Settings3PidController extends State<Settings3Pid> {
Future<List<ThirdPartyIdentifier>?>? request; Future<List<ThirdPartyIdentifier>?>? request;
Future<void> delete3Pid(ThirdPartyIdentifier identifier) async { Future<void> delete3Pid(ThirdPartyIdentifier identifier) async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
) != ) !=
OkCancelResult.ok) { OkCancelResult.ok) {
return; return;
} }
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of( future: () => matrix.client.delete3pidFromAccount(
context, identifier.address,
).client.delete3pidFromAccount(identifier.address, identifier.medium), identifier.medium,
),
); );
if (success.error != null) return; if (success.error != null) return;
setState(() => request = null); setState(() => request = null);

View file

@ -91,6 +91,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
} }
Future<void> _addEmotePack() async { Future<void> _addEmotePack() async {
final matrix = Matrix.of(context);
setState(() { setState(() {
_loading = true; _loading = true;
_progress = 0; _progress = 0;
@ -148,7 +149,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
} else { } else {
mxcFile = thumbnail; mxcFile = thumbnail;
} }
final uri = await Matrix.of(context).client.uploadContent( final uri = await matrix.client.uploadContent(
mxcFile.bytes, mxcFile.bytes,
filename: mxcFile.name, filename: mxcFile.name,
contentType: mxcFile.mimeType, contentType: mxcFile.mimeType,
@ -178,6 +179,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
} }
} }
if (!mounted) return;
await widget.controller.save(context); await widget.controller.save(context);
_importMap.removeWhere( _importMap.removeWhere(
(key, value) => successfulUploads.contains(key.name), (key, value) => successfulUploads.contains(key.name),

View file

@ -293,6 +293,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
} }
Future<void> createStickers() async { Future<void> createStickers() async {
final matrix = Matrix.of(context);
final pickedFiles = await selectFiles( final pickedFiles = await selectFiles(
context, context,
type: FileType.image, type: FileType.image,
@ -315,7 +316,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
nativeImplementations: ClientManager.nativeImplementations, nativeImplementations: ClientManager.nativeImplementations,
) ?? ) ??
file; file;
final uri = await Matrix.of(context).client.uploadContent( final uri = await matrix.client.uploadContent(
file.bytes, file.bytes,
filename: file.name, filename: file.name,
contentType: file.mimeType, contentType: file.mimeType,
@ -361,6 +362,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
final buffer = InputMemoryStream(await result.single.readAsBytes()); final buffer = InputMemoryStream(await result.single.readAsBytes());
final archive = ZipDecoder().decodeStream(buffer); final archive = ZipDecoder().decodeStream(buffer);
if (!mounted) return;
await showDialog( await showDialog(
context: context, context: context,
@ -375,7 +377,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
Future<void> exportAsZip() async { Future<void> exportAsZip() async {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
await showFutureLoadingDialog( final result = await showFutureLoadingDialog<MatrixFile>(
context: context, context: context,
future: () async { future: () async {
final pack = _getPack(); final pack = _getPack();
@ -397,11 +399,12 @@ class EmotesSettingsController extends State<EmotesSettings> {
'${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip'; '${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip';
final output = ZipEncoder().encode(archive); final output = ZipEncoder().encode(archive);
MatrixFile( return MatrixFile(name: fileName, bytes: Uint8List.fromList(output));
name: fileName,
bytes: Uint8List.fromList(output),
).save(context);
}, },
); );
final file = result.result;
if (file == null) return;
if (!mounted) return;
file.save(context);
} }
} }

View file

@ -40,6 +40,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
], ],
); );
if (delete != true) return; if (delete != true) return;
if (!mounted) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,

View file

@ -24,6 +24,8 @@ class SettingsPasswordController extends State<SettingsPassword> {
bool loading = false; bool loading = false;
Future<void> changePassword() async { Future<void> changePassword() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
setState(() { setState(() {
oldPasswordError = newPassword1Error = newPassword2Error = null; oldPasswordError = newPassword1Error = newPassword2Error = null;
}); });
@ -51,13 +53,13 @@ class SettingsPasswordController extends State<SettingsPassword> {
loading = true; loading = true;
}); });
try { try {
final scaffoldMessenger = ScaffoldMessenger.of(context);
await Matrix.of(context).client.changePassword( await Matrix.of(context).client.changePassword(
newPassword1Controller.text, newPassword1Controller.text,
oldPassword: oldPasswordController.text, oldPassword: oldPasswordController.text,
); );
if (!mounted) return;
scaffoldMessenger.showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), SnackBar(content: Text(l10n.passwordHasBeenChanged)),
); );
if (mounted) context.pop(); if (mounted) context.pop();
} catch (e) { } catch (e) {

View file

@ -19,20 +19,21 @@ class SettingsSecurity extends StatefulWidget {
class SettingsSecurityController extends State<SettingsSecurity> { class SettingsSecurityController extends State<SettingsSecurity> {
Future<void> setAppLockAction() async { Future<void> setAppLockAction() async {
final l10n = L10n.of(context);
if (AppLock.of(context).isActive) { if (AppLock.of(context).isActive) {
AppLock.of(context).showLockScreen(); AppLock.of(context).showLockScreen();
} }
final newLock = await showTextInputDialog( final newLock = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).pleaseChooseAPasscode, title: l10n.pleaseChooseAPasscode,
message: L10n.of(context).pleaseEnter4Digits, message: l10n.pleaseEnter4Digits,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
validator: (text) { validator: (text) {
if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) { if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) {
return null; return null;
} }
return L10n.of(context).pleaseEnter4Digits; return l10n.pleaseEnter4Digits;
}, },
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
obscureText: true, obscureText: true,
@ -41,53 +42,55 @@ class SettingsSecurityController extends State<SettingsSecurity> {
maxLength: 4, maxLength: 4,
); );
if (newLock != null) { if (newLock != null) {
if (!mounted) return;
await AppLock.of(context).changePincode(newLock); await AppLock.of(context).changePincode(newLock);
} }
} }
Future<void> deleteAccountAction() async { Future<void> deleteAccountAction() async {
final l10n = L10n.of(context);
final matrix = Matrix.of(context);
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).warning, title: l10n.warning,
message: L10n.of(context).deactivateAccountWarning, message: l10n.deactivateAccountWarning,
okLabel: L10n.of(context).ok, okLabel: l10n.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
isDestructive: true, isDestructive: true,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
} }
final supposedMxid = Matrix.of(context).client.userID!; if (!mounted) return;
final supposedMxid = matrix.client.userID!;
final mxid = await showTextInputDialog( final mxid = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).confirmMatrixId, title: l10n.confirmMatrixId,
validator: (text) => text == supposedMxid validator: (text) =>
? null text == supposedMxid ? null : l10n.supposedMxid(supposedMxid),
: L10n.of(context).supposedMxid(supposedMxid),
isDestructive: true, isDestructive: true,
okLabel: L10n.of(context).delete, okLabel: l10n.delete,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
); );
if (mxid == null || mxid.isEmpty || mxid != supposedMxid) { if (mxid == null || mxid.isEmpty || mxid != supposedMxid) {
return; return;
} }
if (!mounted) return;
final resp = await showFutureLoadingDialog( final resp = await showFutureLoadingDialog(
context: context, context: context,
delay: false, delay: false,
future: () => future: () => matrix.client.uiaRequestBackground<IdServerUnbindResult?>(
Matrix.of(context).client.uiaRequestBackground<IdServerUnbindResult?>( (auth) => matrix.client.deactivateAccount(auth: auth, erase: true),
(auth) => Matrix.of( ),
context,
).client.deactivateAccount(auth: auth, erase: true),
),
); );
if (!resp.isError) { if (!resp.isError) {
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.logout(), future: () => matrix.client.logout(),
); );
} }
} }

View file

@ -29,6 +29,7 @@ class SettingsStyleController extends State<SettingsStyle> {
final picked = await selectFiles(context, type: FileType.image); final picked = await selectFiles(context, type: FileType.image);
final pickedFile = picked.firstOrNull; final pickedFile = picked.firstOrNull;
if (pickedFile == null) return; if (pickedFile == null) return;
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,

View file

@ -12,6 +12,8 @@ abstract class FluffyShare {
BuildContext context, { BuildContext context, {
bool copyOnly = false, bool copyOnly = false,
}) async { }) async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
if (PlatformInfos.isMobile && !copyOnly) { if (PlatformInfos.isMobile && !copyOnly) {
final box = context.findRenderObject() as RenderBox; final box = context.findRenderObject() as RenderBox;
await SharePlus.instance.share( await SharePlus.instance.share(
@ -24,21 +26,20 @@ abstract class FluffyShare {
} }
await Clipboard.setData(ClipboardData(text: text)); await Clipboard.setData(ClipboardData(text: text));
if (!PlatformInfos.isMobile) { if (!PlatformInfos.isMobile) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar( SnackBar(showCloseIcon: true, content: Text(l10n.copiedToClipboard)),
showCloseIcon: true,
content: Text(L10n.of(context).copiedToClipboard),
),
); );
} }
return; return;
} }
static Future<void> shareInviteLink(BuildContext context) async { static Future<void> shareInviteLink(BuildContext context) async {
final l10n = L10n.of(context);
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final ownProfile = await client.fetchOwnProfile(); final ownProfile = await client.fetchOwnProfile();
if (!context.mounted) return;
await FluffyShare.share( await FluffyShare.share(
L10n.of(context).inviteText( l10n.inviteText(
ownProfile.displayName ?? client.userID!, ownProfile.displayName ?? client.userID!,
'https://matrix.to/#/${client.userID}?client=im.fluffychat', 'https://matrix.to/#/${client.userID}?client=im.fluffychat',
), ),

View file

@ -25,12 +25,14 @@ extension LocalizedBody on Event {
Future<void> saveFile(BuildContext context) async { Future<void> saveFile(BuildContext context) async {
final matrixFile = await _getFile(context); final matrixFile = await _getFile(context);
if (!context.mounted) return;
matrixFile.result?.save(context); matrixFile.result?.save(context);
} }
Future<void> shareFile(BuildContext context) async { Future<void> shareFile(BuildContext context) async {
final matrixFile = await _getFile(context); final matrixFile = await _getFile(context);
if (!context.mounted) return;
matrixFile.result?.share(context); matrixFile.result?.share(context);
} }

View file

@ -50,15 +50,17 @@ abstract class PlatformInfos {
} }
static Future<void> showDialog(BuildContext context) async { static Future<void> showDialog(BuildContext context) async {
final l10n = L10n.of(context);
final version = await PlatformInfos.getVersion(); final version = await PlatformInfos.getVersion();
if (!context.mounted) return;
showAboutDialog( showAboutDialog(
context: context, context: context,
children: [ children: [
Text(L10n.of(context).versionWithNumber(version)), Text(l10n.versionWithNumber(version)),
TextButton.icon( TextButton.icon(
onPressed: () => launchUrlString(AppConfig.sourceCodeUrl), onPressed: () => launchUrlString(AppConfig.sourceCodeUrl),
icon: const Icon(Icons.source_outlined), icon: const Icon(Icons.source_outlined),
label: Text(L10n.of(context).sourceCode), label: Text(l10n.sourceCode),
), ),
Builder( Builder(
builder: (innerContext) { builder: (innerContext) {
@ -68,7 +70,7 @@ abstract class PlatformInfos {
Navigator.of(innerContext).pop(); Navigator.of(innerContext).pop();
}, },
icon: const Icon(Icons.list_outlined), 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(); Navigator.of(innerContext).pop();
}, },
icon: const Icon(Icons.settings_applications_outlined), icon: const Icon(Icons.settings_applications_outlined),
label: Text(L10n.of(context).advancedConfigs), label: Text(l10n.advancedConfigs),
); );
}, },
), ),

View file

@ -10,6 +10,7 @@ abstract class UpdateNotifier {
static Future<void> showUpdateSnackBar(BuildContext context) async { static Future<void> showUpdateSnackBar(BuildContext context) async {
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
final l10n = L10n.of(context);
final currentVersion = await PlatformInfos.getVersion(); final currentVersion = await PlatformInfos.getVersion();
final store = await SharedPreferences.getInstance(); final store = await SharedPreferences.getInstance();
final storedVersion = store.getString(versionStoreKey); final storedVersion = store.getString(versionStoreKey);
@ -20,9 +21,9 @@ abstract class UpdateNotifier {
SnackBar( SnackBar(
duration: const Duration(seconds: 30), duration: const Duration(seconds: 30),
showCloseIcon: true, showCloseIcon: true,
content: Text(L10n.of(context).updateInstalled(currentVersion)), content: Text(l10n.updateInstalled(currentVersion)),
action: SnackBarAction( action: SnackBarAction(
label: L10n.of(context).changelog, label: l10n.changelog,
onPressed: () => launchUrlString(AppConfig.changelogUrl), onPressed: () => launchUrlString(AppConfig.changelogUrl),
), ),
), ),

View file

@ -38,6 +38,7 @@ Future<void> connectToHomeserverFlow(
if ((kIsWeb || PlatformInfos.isLinux) && if ((kIsWeb || PlatformInfos.isLinux) &&
(supportsSso || authMetadata != null || (signUp && regLink != null))) { (supportsSso || authMetadata != null || (signUp && regLink != null))) {
if (!context.mounted) return;
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: l10n.appWantsToUseForLogin(homeserverInput), title: l10n.appWantsToUseForLogin(homeserverInput),
@ -45,7 +46,9 @@ Future<void> connectToHomeserverFlow(
okLabel: l10n.continueText, okLabel: l10n.continueText,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
} }
if (!context.mounted) return;
if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) { if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) {
await oidcLoginFlow(client, context, signUp); await oidcLoginFlow(client, context, signUp);
@ -55,6 +58,7 @@ Future<void> connectToHomeserverFlow(
if (signUp && regLink != null) { if (signUp && regLink != null) {
await launchUrlString(regLink); await launchUrlString(regLink);
} }
if (!context.mounted) return;
final pathSegments = List.of( final pathSegments = List.of(
GoRouter.of(context).routeInformationProvider.value.uri.pathSegments, GoRouter.of(context).routeInformationProvider.value.uri.pathSegments,
); );

View file

@ -27,6 +27,8 @@ class UrlLauncher {
const UrlLauncher(this.context, this.url, [this.name]); const UrlLauncher(this.context, this.url, [this.name]);
Future<void> launchUrl() async { Future<void> launchUrl() async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) || if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
{'#', '@', '!', '+', '\$'}.contains(url![0]) || {'#', '@', '!', '+', '\$'}.contains(url![0]) ||
@ -36,8 +38,8 @@ class UrlLauncher {
final uri = Uri.tryParse(url!); final uri = Uri.tryParse(url!);
if (uri == null) { if (uri == null) {
// we can't open this thing // we can't open this thing
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), SnackBar(content: Text(l10n.cantOpenUri(url!))),
); );
return; return;
} }
@ -47,10 +49,10 @@ class UrlLauncher {
// that the user can see the actual url before opening the browser. // that the user can see the actual url before opening the browser.
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).openLinkInBrowser, title: l10n.openLinkInBrowser,
message: url, message: url,
okLabel: L10n.of(context).open, okLabel: l10n.open,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
} }
@ -90,8 +92,8 @@ class UrlLauncher {
return; return;
} }
if (uri.host.isEmpty) { if (uri.host.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), SnackBar(content: Text(l10n.cantOpenUri(url!))),
); );
return; return;
} }
@ -161,6 +163,7 @@ class UrlLauncher {
} }
} }
servers.addAll(identityParts.via); servers.addAll(identityParts.via);
if (!context.mounted) return;
if (room != null) { if (room != null) {
if (room.isSpace) { if (room.isSpace) {
// TODO: Implement navigate to space // TODO: Implement navigate to space
@ -178,6 +181,7 @@ class UrlLauncher {
} }
return; return;
} else { } else {
if (!context.mounted) return;
await showAdaptiveDialog( await showAdaptiveDialog(
context: context, context: context,
builder: (c) => builder: (c) =>
@ -185,6 +189,7 @@ class UrlLauncher {
); );
} }
if (roomIdOrAlias.sigil == '!') { if (roomIdOrAlias.sigil == '!') {
if (!context.mounted) return;
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
@ -192,6 +197,7 @@ class UrlLauncher {
) == ) ==
OkCancelResult.ok) { OkCancelResult.ok) {
roomId = roomIdOrAlias; roomId = roomIdOrAlias;
if (!context.mounted) return;
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
future: () => matrix.client.joinRoom( future: () => matrix.client.joinRoom(
@ -200,11 +206,13 @@ class UrlLauncher {
), ),
); );
if (response.error != null) return; if (response.error != null) return;
if (!context.mounted) return;
// wait for two seconds so that it probably came down /sync // wait for two seconds so that it probably came down /sync
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Future.delayed(const Duration(seconds: 2)), future: () => Future.delayed(const Duration(seconds: 2)),
); );
if (!context.mounted) return;
if (event != null) { if (event != null) {
context.go( context.go(
Uri( Uri(
@ -228,6 +236,7 @@ class UrlLauncher {
return Profile(userId: userId); return Profile(userId: userId);
}), }),
); );
if (!context.mounted) return;
await UserDialog.show( await UserDialog.show(
context: context, context: context,
profile: profileResult.result!, profile: profileResult.result!,

View file

@ -25,6 +25,7 @@ class PublicRoomDialog extends StatelessWidget {
const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via}); const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via});
Future<void> _joinRoom(BuildContext context) async { Future<void> _joinRoom(BuildContext context) async {
final l10n = L10n.of(context);
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final chunk = this.chunk; final chunk = this.chunk;
final knock = chunk?.joinRule == 'knock'; final knock = chunk?.joinRule == 'knock';
@ -48,12 +49,13 @@ class PublicRoomDialog extends StatelessWidget {
); );
final roomId = result.result; final roomId = result.result;
if (roomId == null) return; if (roomId == null) return;
if (!context.mounted) return;
if (knock && client.getRoomById(roomId) == null) { if (knock && client.getRoomById(roomId) == null) {
Navigator.of(context).pop<bool>(true); Navigator.of(context).pop<bool>(true);
await showOkAlertDialog( await showOkAlertDialog(
context: context, context: context,
title: L10n.of(context).youHaveKnocked, title: l10n.youHaveKnocked,
message: L10n.of(context).pleaseWaitUntilInvited, message: l10n.pleaseWaitUntilInvited,
); );
return; return;
} }
@ -73,6 +75,7 @@ class PublicRoomDialog extends StatelessWidget {
bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias; bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias;
Future<PublishedRoomsChunk> _search(BuildContext context) async { Future<PublishedRoomsChunk> _search(BuildContext context) async {
final l10n = L10n.of(context);
final chunk = this.chunk; final chunk = this.chunk;
if (chunk != null) return chunk; if (chunk != null) return chunk;
final query = await Matrix.of(context).client.queryPublicRooms( final query = await Matrix.of(context).client.queryPublicRooms(
@ -80,7 +83,7 @@ class PublicRoomDialog extends StatelessWidget {
filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias), filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias),
); );
if (!query.chunk.any(_testRoom)) { if (!query.chunk.any(_testRoom)) {
throw (L10n.of(context).noRoomsFound); throw (l10n.noRoomsFound);
} }
return query.chunk.firstWhere(_testRoom); return query.chunk.firstWhere(_testRoom);
} }
@ -248,6 +251,7 @@ class PublicRoomDialog extends StatelessWidget {
hintText: L10n.of(context).reason, hintText: L10n.of(context).reason,
); );
if (reason == null || reason.isEmpty) return; if (reason == null || reason.isEmpty) return;
if (!context.mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.reportRoom( future: () => Matrix.of(context).client.reportRoom(

View file

@ -220,6 +220,7 @@ class UserDialog extends StatelessWidget {
hintText: L10n.of(context).reason, hintText: L10n.of(context).reason,
); );
if (reason == null || reason.isEmpty) return; if (reason == null || reason.isEmpty) return;
if (!context.mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of( future: () => Matrix.of(

View file

@ -53,16 +53,18 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
onSelected: (choice) async { onSelected: (choice) async {
switch (choice) { switch (choice) {
case ChatPopupMenuActions.leave: case ChatPopupMenuActions.leave:
final l10n = L10n.of(context);
final router = GoRouter.of(context); final router = GoRouter.of(context);
final confirmed = await showOkCancelAlertDialog( final confirmed = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
message: L10n.of(context).archiveRoomDescription, message: l10n.archiveRoomDescription,
okLabel: L10n.of(context).leave, okLabel: l10n.leave,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
isDestructive: true, isDestructive: true,
); );
if (confirmed != OkCancelResult.ok) return; if (confirmed != OkCancelResult.ok) return;
if (!context.mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => widget.room.leave(), future: () => widget.room.leave(),

View file

@ -5,6 +5,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:flutter/material.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 /// 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. /// will be dismissed and the value will be returned when the future completes.
@ -40,6 +41,15 @@ Future<Result<T>> showFutureLoadingDialog<T>({
} }
} }
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<Result<T>>( final result = await showAdaptiveDialog<Result<T>>(
context: context, context: context,
barrierDismissible: barrierDismissible, barrierDismissible: barrierDismissible,

View file

@ -17,6 +17,7 @@ import 'package:universal_html/html.dart' as html;
extension LocalNotificationsExtension on MatrixState { extension LocalNotificationsExtension on MatrixState {
Future<void> showLocalNotification(Event event) async { Future<void> showLocalNotification(Event event) async {
final l10n = L10n.of(context);
final roomId = event.room.id; final roomId = event.room.id;
if (activeRoomId == roomId) { if (activeRoomId == roomId) {
if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
@ -114,11 +115,11 @@ extension LocalNotificationsExtension on MatrixState {
actions: [ actions: [
NotificationAction( NotificationAction(
DesktopNotificationActions.openChat.name, DesktopNotificationActions.openChat.name,
L10n.of(context).openChat, l10n.openChat,
), ),
NotificationAction( NotificationAction(
DesktopNotificationActions.seen.name, DesktopNotificationActions.seen.name,
L10n.of(context).markAsRead, l10n.markAsRead,
), ),
], ],
hints: hints, hints: hints,

View file

@ -265,12 +265,19 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
InitWithRestoreExtension.deleteSessionBackup(name); InitWithRestoreExtension.deleteSessionBackup(name);
if (loggedInWithMultipleClients) { if (loggedInWithMultipleClients) {
final snackbarContext =
FluffyChatApp
.router
.routerDelegate
.navigatorKey
.currentContext ??
context;
if (!snackbarContext.mounted) return;
final l10n = L10n.of(snackbarContext);
ScaffoldMessenger.of( ScaffoldMessenger.of(
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ?? snackbarContext,
context, ).showSnackBar(SnackBar(content: Text(l10n.oneClientLoggedOut)));
).showSnackBar(
SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)),
);
return; return;
} }
FluffyChatApp.router.go('/'); FluffyChatApp.router.go('/');
@ -382,15 +389,17 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
} }
Future<void> dehydrateAction(BuildContext context) async { Future<void> dehydrateAction(BuildContext context) async {
final l10n = L10n.of(context);
final response = await showOkCancelAlertDialog( final response = await showOkCancelAlertDialog(
context: context, context: context,
isDestructive: true, isDestructive: true,
title: L10n.of(context).dehydrate, title: l10n.dehydrate,
message: L10n.of(context).dehydrateWarning, message: l10n.dehydrateWarning,
); );
if (response != OkCancelResult.ok) { if (response != OkCancelResult.ok) {
return; return;
} }
if (!context.mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: client.exportDump, future: client.exportDump,
@ -404,6 +413,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup'; 'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup';
final file = MatrixFile(bytes: exportBytes, name: exportFileName); final file = MatrixFile(bytes: exportBytes, name: exportFileName);
if (!context.mounted) return;
file.save(context); file.save(context);
} }
} }

View file

@ -14,6 +14,8 @@ Future<void> showMemberActionsPopupMenu({
required User user, required User user,
void Function()? onMention, void Function()? onMention,
}) async { }) async {
final l10n = L10n.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final displayname = user.calcDisplayname(); final displayname = user.calcDisplayname();
final isMe = user.room.client.userID == user.id; final isMe = user.room.client.userID == user.id;
@ -245,12 +247,13 @@ Future<void> showMemberActionsPopupMenu({
case _MemberActions.kick: case _MemberActions.kick:
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).no, cancelLabel: l10n.no,
message: L10n.of(context).kickUserDescription, message: l10n.kickUserDescription,
) == ) ==
OkCancelResult.ok) { OkCancelResult.ok) {
if (!context.mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => user.kick(), future: () => user.kick(),
@ -260,12 +263,13 @@ Future<void> showMemberActionsPopupMenu({
case _MemberActions.ban: case _MemberActions.ban:
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).no, cancelLabel: l10n.no,
message: L10n.of(context).banUserDescription, message: l10n.banUserDescription,
) == ) ==
OkCancelResult.ok) { OkCancelResult.ok) {
if (!context.mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => user.ban(), future: () => user.ban(),
@ -275,20 +279,22 @@ Future<void> showMemberActionsPopupMenu({
case _MemberActions.report: case _MemberActions.report:
final reason = await showTextInputDialog( final reason = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).whyDoYouWantToReportThis, title: l10n.whyDoYouWantToReportThis,
okLabel: L10n.of(context).report, okLabel: l10n.report,
cancelLabel: L10n.of(context).cancel, cancelLabel: l10n.cancel,
hintText: L10n.of(context).reason, hintText: l10n.reason,
); );
if (reason == null || reason.isEmpty) return; if (reason == null || reason.isEmpty) return;
if (!context.mounted) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => user.room.client.reportUser(user.id, reason), future: () => user.room.client.reportUser(user.id, reason),
); );
if (result.error != null) return; if (result.error != null) return;
ScaffoldMessenger.of(context).showSnackBar( if (!context.mounted) return;
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), scaffoldMessenger.showSnackBar(
SnackBar(content: Text(l10n.contentHasBeenReported)),
); );
return; return;
case _MemberActions.info: case _MemberActions.info:
@ -304,12 +310,13 @@ Future<void> showMemberActionsPopupMenu({
case _MemberActions.unban: case _MemberActions.unban:
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).areYouSure, title: l10n.areYouSure,
okLabel: L10n.of(context).yes, okLabel: l10n.yes,
cancelLabel: L10n.of(context).no, cancelLabel: l10n.no,
message: L10n.of(context).unbanUserDescription, message: l10n.unbanUserDescription,
) == ) ==
OkCancelResult.ok) { OkCancelResult.ok) {
if (!context.mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => user.unban(), future: () => user.unban(),