diff --git a/lib/notification/notification_service.dart b/lib/notification/notification_service.dart index dd5819f..bdd4953 100644 --- a/lib/notification/notification_service.dart +++ b/lib/notification/notification_service.dart @@ -25,7 +25,12 @@ class NotificationService { // iOS Talk category mirrors the Android inline reply + mark-as-read actions // so both platforms expose the same quick actions. The actual delivery of // these while the app is terminated is handled by the (Phase 3) NSE. + // The plugin defaults to prompting here, which would block startup on the + // OS dialog; the prompt belongs to maybePromptTalkNotifications instead. final iosSettings = DarwinInitializationSettings( + requestAlertPermission: false, + requestSoundPermission: false, + requestBadgePermission: false, notificationCategories: [ DarwinNotificationCategory( PushRenderer.iosTalkCategory, diff --git a/lib/state/app/modules/chat_list/bloc/chat_list_bloc.dart b/lib/state/app/modules/chat_list/bloc/chat_list_bloc.dart index c1cd1f5..24128f8 100644 --- a/lib/state/app/modules/chat_list/bloc/chat_list_bloc.dart +++ b/lib/state/app/modules/chat_list/bloc/chat_list_bloc.dart @@ -129,7 +129,7 @@ class ChatListBloc /// A background poll that returns the same rooms would otherwise rebuild the /// list and re-persist the whole state every 15 s. bool _isUnchanged(GetRoomResponse rooms) { - final encoded = jsonEncode(rooms.data); + final encoded = jsonEncode(rooms.data.toList()); final unchanged = encoded == _lastRoomsJson && innerState?.rooms != null && diff --git a/lib/view/pages/files/files_upload_dialog.dart b/lib/view/pages/files/files_upload_dialog.dart index 0288c78..2b63619 100644 --- a/lib/view/pages/files/files_upload_dialog.dart +++ b/lib/view/pages/files/files_upload_dialog.dart @@ -79,6 +79,7 @@ class _FilesUploadDialogState extends State { }); final webdavClient = await WebdavApi.webdav; + if (!mounted) return; if (!override) { List result; @@ -148,6 +149,7 @@ class _FilesUploadDialogState extends State { ), ); + if (!mounted) return; if (replaceFiles != true) { setState(() { _resetProgress(); @@ -230,8 +232,8 @@ class _FilesUploadDialogState extends State { uploadetFilePaths.add(fullRemotePath); } - setState(_resetProgress); if (!mounted) return; + setState(_resetProgress); Navigator.of(context).pop(); widget.onUploadFinished(uploadetFilePaths); } diff --git a/lib/widget/avatar_actions_sheet.dart b/lib/widget/avatar_actions_sheet.dart index 5e1a1e0..901975e 100644 --- a/lib/widget/avatar_actions_sheet.dart +++ b/lib/widget/avatar_actions_sheet.dart @@ -42,7 +42,7 @@ Future showAvatarActionsSheet( onTap: () async { final bytes = await _pickAndCrop( sheetContext, - () => FilePick.singleGalleryPick(sheetContext), + () => FilePick.singleGalleryPick(sheetContext, forCrop: true), ); if (bytes == null || !sheetContext.mounted) return; result = AvatarUploadResult(bytes); @@ -55,7 +55,7 @@ Future showAvatarActionsSheet( onTap: () async { final bytes = await _pickAndCrop( sheetContext, - () => FilePick.cameraPick(sheetContext), + () => FilePick.cameraPick(sheetContext, forCrop: true), ); if (bytes == null || !sheetContext.mounted) return; result = AvatarUploadResult(bytes); diff --git a/lib/widget/chat_background_picker_sheet.dart b/lib/widget/chat_background_picker_sheet.dart index 6f29474..cbae7d0 100644 --- a/lib/widget/chat_background_picker_sheet.dart +++ b/lib/widget/chat_background_picker_sheet.dart @@ -22,7 +22,7 @@ Future showChatBackgroundPickerSheet(BuildContext context) async { title: const Text('Aus Galerie wählen'), onTap: () async { final bytes = await _pickRaw( - () => FilePick.singleGalleryPick(sheetContext), + () => FilePick.singleGalleryPick(sheetContext, forCrop: true), ); if (bytes == null || !sheetContext.mounted) return; result = bytes; @@ -33,7 +33,9 @@ Future showChatBackgroundPickerSheet(BuildContext context) async { leading: const Icon(Icons.photo_camera_outlined), title: const Text('Foto aufnehmen'), onTap: () async { - final bytes = await _pickRaw(() => FilePick.cameraPick(sheetContext)); + final bytes = await _pickRaw( + () => FilePick.cameraPick(sheetContext, forCrop: true), + ); if (bytes == null || !sheetContext.mounted) return; result = bytes; Navigator.of(sheetContext).pop(); diff --git a/lib/widget/file_pick.dart b/lib/widget/file_pick.dart index 40be7e0..7f2578e 100644 --- a/lib/widget/file_pick.dart +++ b/lib/widget/file_pick.dart @@ -23,11 +23,31 @@ class FilePick { return pickedImages.isNotEmpty ? pickedImages : null; }); - static Future singleGalleryPick(BuildContext context) => - _guarded(context, () => _picker.pickImage(source: ImageSource.gallery)); + static Future singleGalleryPick( + BuildContext context, { + bool forCrop = false, + }) => _pickImage(context, ImageSource.gallery, forCrop: forCrop); - static Future cameraPick(BuildContext context) => - _guarded(context, () => _picker.pickImage(source: ImageSource.camera)); + static Future cameraPick( + BuildContext context, { + bool forCrop = false, + }) => _pickImage(context, ImageSource.camera, forCrop: forCrop); + + /// [forCrop] makes the picker re-encode to a bounded JPEG: the Dart cropper + /// can't decode HEIC (iOS gallery default) and is slow on full-size photos. + static Future _pickImage( + BuildContext context, + ImageSource source, { + required bool forCrop, + }) => _guarded( + context, + () => _picker.pickImage( + source: source, + maxWidth: forCrop ? 2048 : null, + maxHeight: forCrop ? 2048 : null, + imageQuality: forCrop ? 90 : null, + ), + ); static Future?> documentPick() async { final result = await FilePicker.pickFiles(allowMultiple: true);