diff --git a/lib/src/channels/video_compression.channel.dart b/lib/src/channels/video_compression.channel.dart index f2f72993..58790b59 100644 --- a/lib/src/channels/video_compression.channel.dart +++ b/lib/src/channels/video_compression.channel.dart @@ -36,7 +36,7 @@ abstract class VideoCompressionChannel { }); return outputPath; } on PlatformException catch (e) { - Log.error('Failed to compress video: $e'); + Log.warn('Failed to compress video: $e'); return null; } finally { _currentProgressCallback = null; diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index a54deb20..0644ae31 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -30,11 +30,15 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { .getMessageById(messageId) .getSingleOrNull(); if (msg == null) { - Log.error('updateReaction: Message $messageId not found!'); + Log.warn('updateReaction: Message $messageId not found!'); return; } if (msg.groupId != groupId) { - Log.error('updateReaction: Message groupId ${msg.groupId} != $groupId'); + Log.warn('updateReaction: Message groupId ${msg.groupId} != $groupId'); + Log.error( + 'updateReaction: Message groupId mismatch', + onlyIfSentryEnabled: true, + ); return; } diff --git a/lib/src/database/daos/receipts.dao.dart b/lib/src/database/daos/receipts.dao.dart index 8e5ea2fb..f79a2281 100644 --- a/lib/src/database/daos/receipts.dao.dart +++ b/lib/src/database/daos/receipts.dao.dart @@ -53,7 +53,7 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { )) .go(); } - + Future deleteReceiptsByMessageId(String messageId) async { await (delete(receipts)..where( (t) => t.messageId.equals(messageId), @@ -201,8 +201,8 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { ); final updatedReceipt = await getReceiptById(newReceiptId); if (updatedReceipt == null) { - Log.error( - 'Tried to change the receipt ID, but could not get the updated receipt...', + Log.warn( + '[$oldReceiptId] Tried to change the receipt ID to $newReceiptId, but could not get the updated receipt...', ); } return updatedReceipt; @@ -253,6 +253,9 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { Future gotReceipt(String receiptId) async { await into( receivedReceipts, - ).insert(ReceivedReceiptsCompanion(receiptId: Value(receiptId))); + ).insert( + ReceivedReceiptsCompanion(receiptId: Value(receiptId)), + mode: InsertMode.insertOrIgnore, + ); } } diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 2e2bbc61..09531df5 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -684,11 +684,11 @@ class ApiService { final req = createClientToServerFromHandshake(handshake); final result = await sendRequestSync(req, authenticated: false); if (result.isError) { - Log.error('could not request proof of work params', result); + Log.error('could not request proof of work params', error: result); if (result.error == ErrorCode.RegistrationDisabled) { return (null, true); } - Log.error('could not request proof of work params', result); + Log.error('could not request proof of work params', error: result); return (null, false); } return (result.value.proofOfWork as Response_ProofOfWork, false); diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index 2079475e..4547385b 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -17,7 +17,7 @@ Future handleErrorMessage( String receiptId, { String? groupId, }) async { - Log.error('[$receiptId] Got error from $fromUserId: $error'); + Log.warn('[$receiptId] Got error from $fromUserId: $error'); switch (error.type) { case EncryptedContent_ErrorMessages_Type @@ -38,14 +38,14 @@ Future handleErrorMessage( break; // The other user initiated a new signal session, so ignore the error in this case, as the new session works... case EncryptedContent_ErrorMessages_Type.GROUP_NOT_FOUND_OR_NOT_A_MEMBER: if (groupId == null) { - Log.error( + Log.warn( '[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but groupId is null.', ); return; } final group = await twonlyDB.groupsDao.getGroup(groupId); if (group == null) { - Log.error( + Log.warn( '[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but group $groupId is not found in database.', ); return; diff --git a/lib/src/services/api/mediafiles/download.api.dart b/lib/src/services/api/mediafiles/download.api.dart index 0218275d..e3fef202 100644 --- a/lib/src/services/api/mediafiles/download.api.dart +++ b/lib/src/services/api/mediafiles/download.api.dart @@ -143,8 +143,12 @@ Future handleDownloadStatusUpdate(TaskStatusUpdate update) async { failed = false; } else { failed = true; + Log.warn( + '[$mediaId] Got invalid response status code: ${update.responseStatusCode}', + ); Log.error( 'Got invalid response status code: ${update.responseStatusCode}', + onlyIfSentryEnabled: true, ); } } else { @@ -227,7 +231,7 @@ Future startDownloadMedia(MediaFile media, bool force) async { try { await downloadFileFast(media, apiUrl, mediaService.encryptedPath); } catch (e) { - Log.error('Fast download failed: $e'); + Log.warn('Fast download failed: $e'); await FileDownloader().enqueue(task); } } catch (e) { @@ -251,9 +255,13 @@ Future downloadFileFast( return; } else { if (response.statusCode == 404 || response.statusCode == 403) { - Log.error( + Log.warn( 'Got ${response.statusCode} from server for media ID ${media.mediaId}. Requesting upload again', ); + Log.error( + 'Got ${response.statusCode} from server for media ID.', + onlyIfSentryEnabled: true, + ); // Message was deleted from the server. Requesting it again from the sender to upload it again... await requestMediaReupload(media.mediaId); return; @@ -293,7 +301,7 @@ Future handleEncryptedFile(String mediaId) async { action: () async { final mediaService = await MediaFileService.fromMediaId(mediaId); if (mediaService == null) { - Log.error('Media file not found in database.'); + Log.warn('[$mediaId] Media file not found in database.'); return; } diff --git a/lib/src/services/api/mediafiles/media_background.api.dart b/lib/src/services/api/mediafiles/media_background.api.dart index d736e2d1..92afafc1 100644 --- a/lib/src/services/api/mediafiles/media_background.api.dart +++ b/lib/src/services/api/mediafiles/media_background.api.dart @@ -80,9 +80,13 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async { await markUploadAsSuccessful(media); return; } - Log.error( + Log.warn( 'Got HTTP error ${update.responseStatusCode} for $mediaId', ); + Log.error( + 'Got HTTP error ${update.responseStatusCode} for media.', + onlyIfSentryEnabled: true, + ); } if (update.status == TaskStatus.notFound) { @@ -118,9 +122,13 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async { if (update.status == TaskStatus.failed || update.status == TaskStatus.canceled) { - Log.error( + Log.warn( 'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ', ); + Log.error( + 'Background upload failed with status ${update.status} and ${update.responseStatusCode}.', + onlyIfSentryEnabled: true, + ); final mediaService = MediaFileService(media); // in case the media file is already uploaded to not reqtry diff --git a/lib/src/services/api/server_messages.api.dart b/lib/src/services/api/server_messages.api.dart index 1a3b1275..90b13f26 100644 --- a/lib/src/services/api/server_messages.api.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -252,7 +252,11 @@ Future _handleClient2ClientMessage( await twonlyDB.receiptsDao.gotReceipt(receiptId); Log.info('[$receiptId] Finished processing'); } catch (e) { - Log.error('[$receiptId] Error marking message as received: $e'); + Log.warn('[$receiptId] Error marking message as received: $e'); + Log.error( + 'Error marking message as received: $e', + onlyIfSentryEnabled: true, + ); } } @@ -426,7 +430,14 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( } if (!content.hasGroupId()) { - Log.error('[$receiptId] Messages should have a groupId $fromUserId.'); + final type = _getEncryptedContentType(content); + Log.warn( + '[$receiptId] Messages should have a groupId $fromUserId. Type: $type', + ); + Log.error( + 'Messages should have a groupId. Type: $type', + onlyIfSentryEnabled: true, + ); return (null, null); } @@ -453,7 +464,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( ); if (contact == null || !contact.accepted || contact.deletedByUser) { await handleNewContactRequest(fromUserId); - Log.error( + Log.warn( '[$receiptId] User tries to send message to direct chat while the user does not exist!', ); return ( @@ -478,7 +489,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( ); } else { if (content.hasGroupJoin()) { - Log.error( + Log.warn( '[$receiptId] Got group join message, but group does not exist yet, retry later. As probably the GroupCreate was not yet received.', ); // In case the group join was received before the GroupCreate the sender should send it later again. @@ -489,7 +500,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( ); } - Log.error( + Log.warn( '[$receiptId] User $fromUserId tried to access group ${content.groupId}. Sending GROUP_NOT_FOUND_OR_NOT_A_MEMBER error.', ); return ( @@ -598,3 +609,26 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( return (null, null); } + +String _getEncryptedContentType(EncryptedContent content) { + if (content.hasMessageUpdate()) return 'messageUpdate'; + if (content.hasMedia()) return 'media'; + if (content.hasMediaUpdate()) return 'mediaUpdate'; + if (content.hasContactUpdate()) return 'contactUpdate'; + if (content.hasContactRequest()) return 'contactRequest'; + if (content.hasFlameSync()) return 'flameSync'; + if (content.hasPushKeys()) return 'pushKeys'; + if (content.hasReaction()) return 'reaction'; + if (content.hasTextMessage()) return 'textMessage'; + if (content.hasGroupCreate()) return 'groupCreate'; + if (content.hasGroupJoin()) return 'groupJoin'; + if (content.hasGroupUpdate()) return 'groupUpdate'; + if (content.hasResendGroupPublicKey()) return 'resendGroupPublicKey'; + if (content.hasErrorMessages()) return 'errorMessages'; + if (content.hasAdditionalDataMessage()) return 'additionalDataMessage'; + if (content.hasTypingIndicator()) return 'typingIndicator'; + if (content.hasUserDiscoveryRequest()) return 'userDiscoveryRequest'; + if (content.hasUserDiscoveryUpdate()) return 'userDiscoveryUpdate'; + if (content.hasKeyVerificationProof()) return 'keyVerificationProof'; + return 'unknown'; +} diff --git a/lib/src/services/backup.service.dart b/lib/src/services/backup.service.dart index 952869d8..454655aa 100644 --- a/lib/src/services/backup.service.dart +++ b/lib/src/services/backup.service.dart @@ -105,8 +105,9 @@ class BackupService { ))) { final backupId = await RustBackupIdentity.getBackupId(); if (backupId == null) { - Log.error('No backup password was set by the user.'); + Log.warn('No backup password was set by the user.'); backup.identityState = LastBackupUploadState.failed; + await UserService.update((u) => u.isBackupEnabled = false); } else { Log.info('Performing a identity backup.'); final encryptedBackup = @@ -162,7 +163,7 @@ class BackupService { (backupDownloadToken, backupArchive) = await RustBackupArchive.createBackupArchive(); } catch (e) { - Log.error(e); + Log.warn('Creating archive backup failed: $e'); return; } Log.info( @@ -337,7 +338,7 @@ class BackupService { }, ); } catch (e) { - Log.error('Error fetching backup: $e'); + Log.warn('Error fetching backup: $e'); return (null, RecoveryError.noInternet); } diff --git a/lib/src/services/user_study.service.dart b/lib/src/services/user_study.service.dart index 0a28c893..ff738051 100644 --- a/lib/src/services/user_study.service.dart +++ b/lib/src/services/user_study.service.dart @@ -1,4 +1,6 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:http/http.dart' as http; import 'package:twonly/locator.dart'; @@ -128,6 +130,12 @@ Future handleUserStudyUpload() async { }); } } catch (e) { - Log.error(e); + if (e is http.ClientException || + e is SocketException || + e is TimeoutException) { + Log.warn('Error uploading user study data: $e'); + } else { + Log.error(e); + } } } diff --git a/lib/src/utils/log.dart b/lib/src/utils/log.dart index 63a73b36..9af0d49e 100644 --- a/lib/src/utils/log.dart +++ b/lib/src/utils/log.dart @@ -41,10 +41,14 @@ class Log { } static void error( - Object? messageInput, [ + Object? messageInput, { Object? error, StackTrace? stackTrace, - ]) { + bool onlyIfSentryEnabled = false, + }) { + if (!AppState.allowErrorTrackingViaSentry && onlyIfSentryEnabled) { + return; + } final message = filterLogMessage('$messageInput'); if (AppState.allowErrorTrackingViaSentry) { try { diff --git a/lib/src/visual/helpers/screenshot.helper.dart b/lib/src/visual/helpers/screenshot.helper.dart index 654de721..7ded4f3b 100644 --- a/lib/src/visual/helpers/screenshot.helper.dart +++ b/lib/src/visual/helpers/screenshot.helper.dart @@ -28,7 +28,7 @@ class ScreenshotImageHelper { try { return imageBytes = await imageBytesFuture; } catch (e) { - Log.error('Could not resolve imageBytesFuture: $e'); + Log.warn('Could not resolve imageBytesFuture: $e'); return null; } } @@ -36,14 +36,14 @@ class ScreenshotImageHelper { try { return imageBytes = await file!.readAsBytes(); } catch (e) { - Log.error('Could not read bytes from file: $e'); + Log.warn('Could not read bytes from file: $e'); return null; } } if (image == null) return null; final img = await image!.toByteData(format: io.ImageByteFormat.png); if (img == null) { - Log.error('Got no image'); + Log.warn('Got no image'); return null; } return imageBytes = img.buffer.asUint8List(); @@ -94,7 +94,7 @@ class ScreenshotController { }); return completer.future; } - Log.error(e); + Log.warn('Could not capture screenshot: $e'); } return null; } diff --git a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart index 49810100..e6533042 100644 --- a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart +++ b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart @@ -289,12 +289,18 @@ class _CameraPreviewViewState extends State { mc.cameraController == null) { return; } - await mc.cameraController?.setZoomLevel( - newScale.clamp( - mc.selectedCameraDetails.minAvailableZoom, - mc.selectedCameraDetails.maxAvailableZoom, - ), - ); + try { + if (mc.cameraController!.value.isInitialized) { + await mc.cameraController!.setZoomLevel( + newScale.clamp( + mc.selectedCameraDetails.minAvailableZoom, + mc.selectedCameraDetails.maxAvailableZoom, + ), + ); + } + } on CameraException catch (e) { + Log.warn('Failed to set zoom level: $e'); + } setState(() { mc.selectedCameraDetails.scaleFactor = newScale; }); @@ -491,9 +497,16 @@ class _CameraPreviewViewState extends State { (_basePanY - (details.localPosition.dy as double)) / 30) .clamp(1, mc.selectedCameraDetails.maxAvailableZoom); }); - await mc.cameraController!.setZoomLevel( - mc.selectedCameraDetails.scaleFactor, - ); + try { + if (mc.cameraController != null && + mc.cameraController!.value.isInitialized) { + await mc.cameraController!.setZoomLevel( + mc.selectedCameraDetails.scaleFactor, + ); + } + } on CameraException catch (e) { + Log.warn('Failed to set zoom level: $e'); + } if (!userService.currentUser.hasZoomed) { await UserService.update((u) => u.hasZoomed = true); } diff --git a/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart b/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart index fe90a189..9249020f 100644 --- a/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart +++ b/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart @@ -81,6 +81,7 @@ class PermissionHandlerViewState extends State } Future _checkAndTriggerSuccess() async { + if (!mounted) return; if (_isSuccessTriggered) return; final route = ModalRoute.of(context); if (route != null && !route.isCurrent) { diff --git a/lib/src/visual/views/camera/share_image_editor.view.dart b/lib/src/visual/views/camera/share_image_editor.view.dart index afa4382e..423b472b 100644 --- a/lib/src/visual/views/camera/share_image_editor.view.dart +++ b/lib/src/visual/views/camera/share_image_editor.view.dart @@ -499,7 +499,7 @@ class _ShareImageEditorView extends State { pixelRatio: pixelRatio, ); if (image == null) { - Log.error('screenshotController did not return image bytes'); + Log.warn('screenshotController did not return image bytes'); return null; } @@ -544,7 +544,7 @@ class _ShareImageEditorView extends State { if (image == null) return null; final bytes = await image.getBytes(); if (bytes == null) { - Log.error('imageBytes are empty'); + Log.warn('imageBytes are empty'); return null; } if (media.type == MediaType.image || media.type == MediaType.gif) { diff --git a/lib/src/visual/views/camera/share_image_editor_components/layers/filters/stickers.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/stickers.dart index 946d0408..7d3eb4ee 100644 --- a/lib/src/visual/views/camera/share_image_editor_components/layers/filters/stickers.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/stickers.dart @@ -50,7 +50,13 @@ Future> getStickerIndex() async { return res; } } catch (e) { - Log.error('$e'); + if (e is http.ClientException || + e is SocketException || + e is TimeoutException) { + Log.warn('Could not load stickers index: $e'); + } else { + Log.error('Could not load stickers index: $e'); + } return res; } } diff --git a/lib/src/visual/views/chats/chat_list_components/typing_indicator_subtitle.comp.dart b/lib/src/visual/views/chats/chat_list_components/typing_indicator_subtitle.comp.dart index c11e9084..40c38bcb 100644 --- a/lib/src/visual/views/chats/chat_list_components/typing_indicator_subtitle.comp.dart +++ b/lib/src/visual/views/chats/chat_list_components/typing_indicator_subtitle.comp.dart @@ -20,9 +20,9 @@ class _TypingIndicatorSubtitleCompState extends State { List _groupMembers = []; - late StreamSubscription> membersSub; + StreamSubscription>? membersSub; - late Timer _periodicUpdate; + Timer? _periodicUpdate; @override void initState() { @@ -50,8 +50,8 @@ class _TypingIndicatorSubtitleCompState @override void dispose() { - membersSub.cancel(); - _periodicUpdate.cancel(); + membersSub?.cancel(); + _periodicUpdate?.cancel(); super.dispose(); } diff --git a/lib/src/visual/views/chats/chat_messages.view.dart b/lib/src/visual/views/chats/chat_messages.view.dart index f3daa1de..0d39fb2e 100644 --- a/lib/src/visual/views/chats/chat_messages.view.dart +++ b/lib/src/visual/views/chats/chat_messages.view.dart @@ -99,6 +99,7 @@ class _ChatMessagesViewState extends State // } bool _isViewActive() { + if (!mounted) return false; return !AppState.isAppInBackground && (ModalRoute.of(context)?.isCurrent ?? false); } diff --git a/lib/src/visual/views/onboarding/register.view.dart b/lib/src/visual/views/onboarding/register.view.dart index e3a4dd59..d30926f1 100644 --- a/lib/src/visual/views/onboarding/register.view.dart +++ b/lib/src/visual/views/onboarding/register.view.dart @@ -169,7 +169,7 @@ class _RegisterViewState extends State { await apiService.authenticate(); widget.callbackOnSuccess(); } catch (e, stack) { - Log.error('Error creating new user', e, stack); + Log.error('Error creating new user', error: e, stackTrace: stack); if (mounted) { setState(() { _isTryingToRegister = false; diff --git a/lib/src/visual/views/settings/subscription/subscription.view.dart b/lib/src/visual/views/settings/subscription/subscription.view.dart index 12c36e69..587e3f4c 100644 --- a/lib/src/visual/views/settings/subscription/subscription.view.dart +++ b/lib/src/visual/views/settings/subscription/subscription.view.dart @@ -46,6 +46,7 @@ class _SubscriptionViewState extends State { additionalOwnerName = ownerId.toString(); } } + if (!mounted) return; setState(() {}); await apiService.forceIpaCheck(); } @@ -224,6 +225,7 @@ class _PlanCardState extends State { }); await context.read().buy(product); await widget.onPurchase!(); + if (!mounted) return; setState(() { _isLoading = null; });