diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 7f44188a6..08e36316d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4068,6 +4068,25 @@ "hintTitle": "Hint:", "speechToTextBody": "See how well you did by looking at your Accuracy and Words Per Minute scores", "previous": "Previous", + "versionNotFound": "Version Not Found", + "fetchingVersion": "Fetching version...", + "versionFetchError": "Error fetching version", + "connectedToStaging": "Connected to Staging", + "versionText": "Version: {version}+{buildNumber}", + "@versionText": { + "description": "Text displaying the app version and build number.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "The current version of the app." + }, + "buildNumber": { + "type": "String", + "description": "The build number of the app." + } + } + }, "languageButtonLabel": "Language: {currentLanguage}", "@languageButtonLabel": { "type": "text", diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index ac6b30c76..684a418e3 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4717,5 +4717,25 @@ "addChatToSpaceDesc": "Añadir un chat a un espacio hará que el chat aparezca dentro del espacio para los estudiantes y les dará acceso.", "addSpaceToSpaceDesc": "Añadir un espacio a otro espacio hará que el espacio hijo aparezca dentro del espacio padre para los estudiantes y les dará acceso.", "spaceAnalytics": "Analítica espacial", - "changeAnalyticsLanguage": "Cambiar el lenguaje analítico" + "changeAnalyticsLanguage": "Cambiar el lenguaje analítico", + "versionNotFound": "Versión no encontrada", + "fetchingVersion": "Obteniendo versión...", + "versionFetchError": "Error al obtener la versión", + "connectedToStaging": "Conectado al entorno de pruebas", + "connectedToStaging": "Conectado al entorno de pruebas", + "versionText": "Versión: {version}+{buildNumber}", + "@versionText": { + "description": "Texto que muestra la versión y el número de compilación de la aplicación.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "La versión actual de la aplicación." + }, + "buildNumber": { + "type": "String", + "description": "El número de compilación de la aplicación." + } + } + } } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5629d809e..f998b62b3 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -43,6 +43,7 @@ import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; @@ -497,7 +498,10 @@ class ChatController extends State if (kIsWeb && !Matrix.of(context).webHasFocus) return; // #Pangea } catch (err, s) { - ErrorHandler.logError(e: err, s: s); + ErrorHandler.logError( + e: PangeaWarningError("Web focus error: $err"), + s: s, + ); return; } // Pangea# @@ -507,7 +511,15 @@ class ChatController extends State } final timeline = this.timeline; - if (timeline == null || timeline.events.isEmpty) return; + if (timeline == null || timeline.events.isEmpty) { + // #Pangea + ErrorHandler.logError( + e: PangeaWarningError("Timeline is null or empty"), + s: StackTrace.current, + ); + // Pangea# + return; + } Logs().d('Set read marker...', eventId); // ignore: unawaited_futures @@ -518,7 +530,28 @@ class ChatController extends State ) .then((_) { _setReadMarkerFuture = null; + }) + // #Pangea + .catchError((e, s) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to set read marker: $e"), + s: s, + m: 'Failed to set read marker for eventId: $eventId', + ); + Sentry.captureException( + e, + stackTrace: s, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Failed during setReadMarker with eventId: $eventId', + ); + scope.setTag('where', 'setReadMarker'); + }, + ); }); + // Pangea# + if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { Matrix.of(context).backgroundPush?.cancelNotification(roomId); } diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 9ff495328..c7e9d6e64 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; +import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version import 'package:url_launcher/url_launcher_string.dart'; import 'settings.dart'; @@ -17,6 +18,14 @@ class SettingsView extends StatelessWidget { const SettingsView(this.controller, {super.key}); + // #Pangea + Future getAppVersion(BuildContext context) async { + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return L10n.of(context)! + .versionText(packageInfo.version, packageInfo.buildNumber); + } + // Pangea# + @override Widget build(BuildContext context) { // #Pangea @@ -251,6 +260,30 @@ class SettingsView extends StatelessWidget { onTap: () => launchUrlString(AppConfig.termsOfServiceUrl), trailing: const Icon(Icons.open_in_new_outlined), ), + FutureBuilder( + future: getAppVersion(context), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return ListTile( + leading: const Icon(Icons.info_outline), + title: Text( + snapshot.data ?? L10n.of(context)!.versionNotFound, + ), + ); + } else if (snapshot.hasError) { + return ListTile( + leading: const Icon(Icons.error_outline), + title: Text(L10n.of(context)!.versionFetchError), + ); + } else { + return ListTile( + leading: const CircularProgressIndicator(), + title: Text(L10n.of(context)!.fetchingVersion), + ); + } + }, + ), + // Conditional ListTile based on the environment (staging or not) if (Environment.isStaging) ListTile( leading: const Icon(Icons.bug_report_outlined), diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 535af6b8b..f7017ad0e 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart' import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../extensions/client_extension/client_extension.dart'; import '../extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -113,20 +114,39 @@ class MyAnalyticsController { // adds an event ID to the cache of un-added event IDs // if the event IDs isn't already added void addMessageSinceUpdate(String eventId) { - final List currentCache = messagesSinceUpdate; - if (!currentCache.contains(eventId)) { - currentCache.add(eventId); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - currentCache, - local: true, - ); - } + try { + final List currentCache = messagesSinceUpdate; + if (!currentCache.contains(eventId)) { + currentCache.add(eventId); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + currentCache, + local: true, + ); + } - // if the cached has reached if max-length, update analytics - if (messagesSinceUpdate.length > _maxMessagesCached) { - debugPrint("reached max messages, updating"); - updateAnalytics(); + // if the cached has reached if max-length, update analytics + if (messagesSinceUpdate.length > _maxMessagesCached) { + debugPrint("reached max messages, updating"); + updateAnalytics(); + } + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to add message since update: $exception"), + s: stackTrace, + m: 'Failed to add message since update for eventId: $eventId', + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Failed during addMessageSinceUpdate with eventId: $eventId', + ); + scope.setTag('where', 'addMessageSinceUpdate'); + }, + ); } } @@ -143,22 +163,41 @@ class MyAnalyticsController { // it's possible for this cache to be invalid or deleted // It's a proxy measure for messages sent since last update List get messagesSinceUpdate { - final dynamic locallySaved = _pangeaController.pStoreService.read( - PLocalKey.messagesSinceUpdate, - local: true, - ); - if (locallySaved == null) { - _pangeaController.pStoreService.save( + try { + Logs().d('Reading messages since update from local storage'); + final dynamic locallySaved = _pangeaController.pStoreService.read( PLocalKey.messagesSinceUpdate, - [], local: true, ); - return []; - } - - try { - return locallySaved as List; - } catch (err) { + if (locallySaved == null) { + Logs().d('No locally saved messages found, initializing empty list.'); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + [], + local: true, + ); + return []; + } + return locallySaved.cast(); + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError( + "Failed to get messages since update: $exception", + ), + s: stackTrace, + m: 'Failed to retrieve messages since update', + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Error during messagesSinceUpdate getter', + ); + scope.setTag('where', 'messagesSinceUpdate'); + }, + ); _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], diff --git a/lib/pangea/utils/error_handler.dart b/lib/pangea/utils/error_handler.dart index 2144e9f87..65f73c15b 100644 --- a/lib/pangea/utils/error_handler.dart +++ b/lib/pangea/utils/error_handler.dart @@ -11,6 +11,9 @@ import 'package:sentry_flutter/sentry_flutter.dart'; class PangeaWarningError implements Exception { final String message; PangeaWarningError(message) : message = "Pangea Warning Error: $message"; + + @override + String toString() => message; } class ErrorHandler { @@ -53,8 +56,13 @@ class ErrorHandler { Map? data, SentryLevel level = SentryLevel.error, }) async { - if (m != null) debugPrint("error message: $m"); - if ((e ?? m) != null) debugPrint("error to string: ${e?.toString() ?? m}"); + if (e is PangeaWarningError) { + // Custom handling for PangeaWarningError + debugPrint("PangeaWarningError: ${e.message}"); + } else { + if (m != null) debugPrint("error message: $m"); + } + if (data != null) { Sentry.addBreadcrumb(Breadcrumb.fromJson(data)); debugPrint(data.toString()); diff --git a/pubspec.lock b/pubspec.lock index 3764281a1..7a4059d66 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -349,10 +349,10 @@ packages: dependency: "direct main" description: name: emoji_picker_flutter - sha256: "839200a2bd1af9a65d71133a5a246dbf5b24f7e4f6f4c5390130c2e0ed5f85af" + sha256: "7c6681783e06710608df27be0e38aa4ba73ca1ccac370bb0e7a1320723ae4bca" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.1" emoji_proposal: dependency: "direct main" description: @@ -2756,4 +2756,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.3" + flutter: ">=3.19.0"