refactor: Enable lint use_build_context_synchronously

This commit is contained in:
Christian Kußowski 2026-03-25 10:42:17 +01:00
commit 3296c0d92d
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
50 changed files with 490 additions and 293 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix_api_lite/utils/logs.dart';
/// Displays a loading dialog which reacts to the given [future]. The dialog
/// will be dismissed and the value will be returned when the future completes.
@ -40,6 +41,15 @@ Future<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>>(
context: context,
barrierDismissible: barrierDismissible,

View file

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

View file

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

View file

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