From 2a1a68de22f15097c3d05d1be45107a535cb6406 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 16 May 2023 18:45:39 +0200 Subject: [PATCH] feat: Implement new error reporting tool when critical features break like playing audio or video messages or opening a chat --- assets/l10n/intl_en.arb | 6 ++- lib/config/app_config.dart | 5 +++ lib/pages/chat/chat.dart | 21 ++++++++-- lib/pages/chat/events/audio_player.dart | 13 +++--- lib/pages/chat/events/video_player.dart | 8 +--- lib/utils/date_time_extension.dart | 2 +- lib/utils/error_reporter.dart | 55 +++++++++++++++++++++++++ 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 lib/utils/error_reporter.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index ea26b088f..da7d85b69 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2461,5 +2461,7 @@ "jumpToLastReadMessage": "Jump to last read message", "readUpToHere": "Read up to here", "jump": "Jump", - "openLinkInBrowser": "Open link in browser" -} \ No newline at end of file + "openLinkInBrowser": "Open link in browser", + "reportErrorDescription": "Oh no. Something went wrong. Please try again later. If you want, you can report the bug to the developers.", + "report": "report" +} diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 2458d44b5..28e1cd54a 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -33,6 +33,11 @@ abstract class AppConfig { static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat'; static const String supportUrl = 'https://gitlab.com/famedly/fluffychat/issues'; + static final Uri newIssueUrl = Uri( + scheme: 'https', + host: 'gitlab.com', + path: '/famedly/fluffychat/-/issues/new', + ); static const bool enableSentry = true; static const String sentryDns = 'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143'; diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 4ae011c28..fe5f139af 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -23,6 +23,7 @@ import 'package:fluffychat/pages/chat/chat_view.dart'; import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; +import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/ios_badge_client_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -274,7 +275,10 @@ class ChatController extends State { super.initState(); sendingClient = Matrix.of(context).client; readMarkerEventId = room.fullyRead; - loadTimelineFuture = _getTimeline(eventContextId: readMarkerEventId); + loadTimelineFuture = + _getTimeline(eventContextId: readMarkerEventId).onError( + ErrorReporter(context, 'Unable to load timeline').onErrorCallback, + ); } void updateView() { @@ -380,7 +384,12 @@ class ChatController extends State { } // then cancel the old timeline // fixes bug with read reciepts and quick switching - loadTimelineFuture = _getTimeline(eventContextId: room.fullyRead); + loadTimelineFuture = _getTimeline(eventContextId: room.fullyRead).onError( + ErrorReporter( + context, + 'Unable to load timeline after changing sending Client', + ).onErrorCallback, + ); // then set the new sending client setState(() => sendingClient = c); @@ -811,6 +820,9 @@ class ChatController extends State { loadTimelineFuture = _getTimeline( eventContextId: eventId, timeout: const Duration(seconds: 30), + ).onError( + ErrorReporter(context, 'Unable to load timeline after scroll to ID') + .onErrorCallback, ); }); await loadTimelineFuture; @@ -831,7 +843,10 @@ class ChatController extends State { setState(() { timeline = null; _scrolledUp = false; - loadTimelineFuture = _getTimeline(); + loadTimelineFuture = _getTimeline().onError( + ErrorReporter(context, 'Unable to load timeline after scroll down') + .onErrorCallback, + ); }); await loadTimelineFuture; setReadMarker(eventId: timeline!.events.first.eventId); diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 689340226..cdc6ef346 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -10,6 +10,7 @@ import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import '../../../utils/matrix_sdk_extensions/event_extension.dart'; @@ -132,14 +133,10 @@ class AudioPlayerState extends State { } else { await audioPlayer.setAudioSource(MatrixFileAudioSource(matrixFile!)); } - audioPlayer.play().catchError((e, s) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.oopsSomethingWentWrong), - ), - ); - Logs().w('Error while playing audio', e, s); - }); + audioPlayer.play().onError( + ErrorReporter(context, 'Unable to play audio message') + .onErrorCallback, + ); } static const double buttonSize = 36; diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 8d31e5a7b..f95715b59 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -14,6 +14,7 @@ import 'package:video_player/video_player.dart'; import 'package:fluffychat/pages/chat/events/image_bubble.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import '../../../utils/error_reporter.dart'; class EventVideoPlayer extends StatefulWidget { final Event event; @@ -69,12 +70,7 @@ class EventVideoPlayerState extends State { ), ); } catch (e, s) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(e.toLocalizedString(context)), - ), - ); - Logs().w('Error while playing video', e, s); + ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s); } finally { // Workaround for Chewie needs time to get the aspectRatio await Future.delayed(const Duration(milliseconds: 100)); diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index 48244b70c..f6cfecf1f 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:intl/intl.dart'; /// Provides extra functionality for formatting the time. extension DateTimeExtension on DateTime { diff --git a/lib/utils/error_reporter.dart b/lib/utils/error_reporter.dart new file mode 100644 index 000000000..43aefb2eb --- /dev/null +++ b/lib/utils/error_reporter.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; + +class ErrorReporter { + final BuildContext context; + final String? message; + + const ErrorReporter(this.context, [this.message]); + + void onErrorCallback(Object error, [StackTrace? stackTrace]) async { + Logs().e(message ?? 'Error caught', error, stackTrace); + final consent = await showOkCancelAlertDialog( + context: context, + title: error.toLocalizedString(context), + message: L10n.of(context)!.reportErrorDescription, + okLabel: L10n.of(context)!.report, + cancelLabel: L10n.of(context)!.close, + ); + if (consent != OkCancelResult.ok) return; + final os = kIsWeb ? 'web' : Platform.operatingSystem; + final version = await PlatformInfos.getVersion(); + final description = ''' +- Operating system: $os +- Version: $version + +### Exception +$error + +### StackTrace +$stackTrace +'''; + launchUrl( + AppConfig.newIssueUrl.resolveUri( + Uri( + queryParameters: { + 'issue[title]': '[BUG]: ${message ?? error.toString()}', + 'issue[description]': description, + }, + ), + ), + mode: LaunchMode.externalApplication, + ); + } +}