From d5eee79f4cfd14c5182b2f00e2f645261afaccc7 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 09:08:32 -0400 Subject: [PATCH 1/8] log actual error in message_audio_card logging statements instead of empty exception --- lib/pangea/widgets/chat/message_audio_card.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pangea/widgets/chat/message_audio_card.dart b/lib/pangea/widgets/chat/message_audio_card.dart index 82375bacf..7a003a01c 100644 --- a/lib/pangea/widgets/chat/message_audio_card.dart +++ b/lib/pangea/widgets/chat/message_audio_card.dart @@ -89,8 +89,7 @@ class MessageAudioCardState extends State { // should never happen but just in case debugger(when: kDebugMode); ErrorHandler.logError( - e: Exception(), - m: 'audioFile duration is null in MessageAudioCardState', + e: 'audioFile duration is null in MessageAudioCardState', data: { 'audioFile': audioFile, }, @@ -124,8 +123,7 @@ class MessageAudioCardState extends State { // if we didn't find the token, we should pause if debug and log an error debugger(when: kDebugMode); ErrorHandler.logError( - e: Exception(), - m: 'could not find token for selection in MessageAudioCardState', + e: 'could not find token for selection in MessageAudioCardState', data: { 'selection': selection, 'tokens': tokens, @@ -174,7 +172,7 @@ class MessageAudioCardState extends State { ), ); ErrorHandler.logError( - e: Exception(), + e: e, s: s, m: 'something wrong getting audio in MessageAudioCardState', data: { From 0b2c32904a031693270d0804c21ab25d938b7345 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 09:21:02 -0400 Subject: [PATCH 2/8] only call setState in message_selection_overlay if mounted --- lib/pangea/widgets/chat/message_selection_overlay.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index db421dd15..21c875681 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -106,7 +106,8 @@ class MessageOverlayController extends State void setState(VoidCallback fn) { if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle || SchedulerBinding.instance.schedulerPhase == - SchedulerPhase.postFrameCallbacks) { + SchedulerPhase.postFrameCallbacks && + mounted) { // It's safe to call setState immediately super.setState(fn); } else { From bc1dfc1e0e8f017c9c4032afcd4b18112042d35b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 09:21:44 -0400 Subject: [PATCH 3/8] when inviting tachers to analytics room, request all particpants to ensure teacher isn't already a member --- .../pangea_room_extension/room_analytics_extension.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index 73371b080..b44c40ece 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -99,7 +99,7 @@ extension AnalyticsRoomExtension on Room { await analyticsRoom.requestParticipants(); } - final List participants = analyticsRoom.getParticipants(); + final List participants = await analyticsRoom.requestParticipants(); final List uninvitedTeachers = teachersLocal .where((teacher) => !participants.contains(teacher)) .toList(); @@ -110,8 +110,12 @@ extension AnalyticsRoomExtension on Room { (teacher) => analyticsRoom.invite(teacher.id).catchError((err, s) { ErrorHandler.logError( e: err, - m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", + m: "Failed to invite teacher to analytics room", s: s, + data: { + "teacherId": teacher.id, + "analyticsRoomId": analyticsRoom.id, + }, ); }), ), From 7e9855dcc1eae311a65f8f00a36bfcf87376e2bd Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 09:34:03 -0400 Subject: [PATCH 4/8] better error logging if sourceText is null in getNextTranslationData --- .../choreographer/controllers/it_controller.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 636415b8e..b618386f8 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -4,7 +4,6 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; @@ -180,6 +179,18 @@ class ITController { } Future getNextTranslationData() async { + if (sourceText == null) { + ErrorHandler.logError( + e: Exception("sourceText is null in getNextTranslationData"), + data: { + "sourceText": sourceText, + "currentITStep": currentITStep, + "nextITStep": nextITStep, + }, + ); + return; + } + try { if (completedITSteps.length < goldRouteTracker.continuances.length) { final String currentText = choreographer.currentText; From 5d190cc51e1d0d96e32e232e93ffa0305e060d33 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 10:10:49 -0400 Subject: [PATCH 5/8] check for null content in message translation card --- .../chat/message_translation_card.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 81ed7a2e2..4d8bad28d 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -144,14 +144,18 @@ class MessageTranslationCardState extends State { child: Column( children: [ widget.selection != null - ? Text( - selectionTranslation!, - style: BotStyle.text(context), - ) - : Text( - repEvent!.text, - style: BotStyle.text(context), - ), + ? selectionTranslation != null + ? Text( + selectionTranslation!, + style: BotStyle.text(context), + ) + : const ToolbarContentLoadingIndicator() + : repEvent != null + ? Text( + repEvent!.text, + style: BotStyle.text(context), + ) + : const ToolbarContentLoadingIndicator(), if (notGoingToTranslate && widget.selection == null) InlineTooltip( instructionsEnum: InstructionsEnum.l1Translation, From 3efe3743023c996049caba48fe8b30c074edebd9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 10:12:40 -0400 Subject: [PATCH 6/8] added mounted check before setting state in choice_array --- lib/pangea/choreographer/widgets/choice_array.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/pangea/choreographer/widgets/choice_array.dart b/lib/pangea/choreographer/widgets/choice_array.dart index ff13da78f..32395099e 100644 --- a/lib/pangea/choreographer/widgets/choice_array.dart +++ b/lib/pangea/choreographer/widgets/choice_array.dart @@ -44,17 +44,13 @@ class ChoicesArrayState extends State { void disableInteraction() { WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - interactionDisabled = true; - }); + if (mounted) setState(() => interactionDisabled = true); }); } void enableInteractions() { WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - interactionDisabled = false; - }); + if (mounted) setState(() => interactionDisabled = false); }); } From 696bd0f1299a8805520f476b61c5f811c9327b98 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 10:57:05 -0400 Subject: [PATCH 7/8] in message overlay, wrap any calls to get renderbox or media query in a try catch block to get better error handling --- .../chat/message_selection_overlay.dart | 119 ++++++++++++------ 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 21c875681..7c91a87bc 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar_buttons.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; @@ -261,14 +262,14 @@ class MessageOverlayController extends State @override void didChangeDependencies() { super.didChangeDependencies(); - if (messageSize == null || messageOffset == null) { + if (messageSize == null || messageOffset == null || screenHeight == null) { return; } // position the overlay directly over the underlying message - final headerBottomOffset = screenHeight - headerHeight; + final headerBottomOffset = screenHeight! - headerHeight; final footerBottomOffset = footerHeight; - final currentBottomOffset = screenHeight - + final currentBottomOffset = screenHeight! - messageOffset!.dy - messageSize!.height - belowMessageHeight; @@ -296,7 +297,7 @@ class MessageOverlayController extends State animationEndOffset = midpoint - messageSize!.height - belowMessageHeight; final totalTopOffset = animationEndOffset + messageSize!.height + AppConfig.toolbarMaxHeight; - final remainingSpace = screenHeight - totalTopOffset; + final remainingSpace = screenHeight! - totalTopOffset; if (remainingSpace < headerHeight) { // the overlay could run over the header, so it needs to be shifted down animationEndOffset -= (headerHeight - remainingSpace); @@ -311,7 +312,7 @@ class MessageOverlayController extends State // update the message height to fit the screen. The message is scrollable, so // this will make the both the toolbar box and the toolbar buttons visible. if (animationEndOffset < footerHeight + belowMessageHeight) { - final double remainingSpace = screenHeight - + final double remainingSpace = screenHeight! - AppConfig.toolbarMaxHeight - headerHeight - footerHeight - @@ -349,25 +350,57 @@ class MessageOverlayController extends State super.dispose(); } - RenderBox? get messageRenderBox => MatrixState.pAnyState.getRenderBox( + RenderBox? get messageRenderBox { + try { + return MatrixState.pAnyState.getRenderBox( widget._event.eventId, ); + } catch (e, s) { + ErrorHandler.logError(e: "Error getting message render box: $e", s: s); + return null; + } + } + + Size? get messageSize { + try { + return messageRenderBox?.size; + } catch (e, s) { + ErrorHandler.logError(e: "Error getting message size: $e", s: s); + return null; + } + } + + Offset? get messageOffset { + try { + return messageRenderBox?.localToGlobal(Offset.zero); + } catch (e, s) { + ErrorHandler.logError(e: "Error getting message offset: $e", s: s); + return null; + } + } - Size? get messageSize => messageRenderBox?.size; - Offset? get messageOffset => messageRenderBox?.localToGlobal(Offset.zero); double? adjustedMessageHeight; // height of the reply/forward bar + the reaction picker + contextual padding double get footerHeight => 48 + 56 + (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0); + MediaQueryData? get mediaQuery { + try { + return MediaQuery.of(context); + } catch (e, s) { + ErrorHandler.logError(e: "Error getting media query: $e", s: s); + return null; + } + } + double get headerHeight => (Theme.of(context).appBarTheme.toolbarHeight ?? 56) + - MediaQuery.of(context).padding.top; + (mediaQuery?.padding.top ?? 0); - double get screenHeight => MediaQuery.of(context).size.height; + double? get screenHeight => mediaQuery?.size.height; - double get screenWidth => MediaQuery.of(context).size.width; + double? get screenWidth => mediaQuery?.size.width; @override Widget build(BuildContext context) { @@ -381,13 +414,17 @@ class MessageOverlayController extends State // the default spacing between the side of the screen and the message bubble const double messageMargin = Avatar.defaultSize + 16 + 8; final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; - final chatViewWidth = screenWidth - - (FluffyThemes.isColumnMode(context) - ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth) - : 0); + const totalMaxWidth = (FluffyThemes.columnWidth * 2.5) - messageMargin; - double maxWidth = chatViewWidth - (2 * horizontalPadding) - messageMargin; - if (maxWidth > totalMaxWidth) { + double? maxWidth; + if (screenWidth != null) { + final chatViewWidth = screenWidth! - + (FluffyThemes.isColumnMode(context) + ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth) + : 0); + maxWidth = chatViewWidth - (2 * horizontalPadding) - messageMargin; + } + if (maxWidth == null || maxWidth > totalMaxWidth) { maxWidth = totalMaxWidth; } @@ -450,34 +487,36 @@ class MessageOverlayController extends State ? null : messageOffset!.dx - horizontalPadding - columnOffset; - final double? rightPadding = widget._pangeaMessageEvent.ownMessage - ? screenWidth - - messageOffset!.dx - - messageSize!.width - - horizontalPadding - : null; + final double? rightPadding = + (widget._pangeaMessageEvent.ownMessage && screenWidth != null) + ? screenWidth! - + messageOffset!.dx - + messageSize!.width - + horizontalPadding + : null; - final positionedOverlayMessage = _overlayPositionAnimation == null - ? Positioned( - left: leftPadding, - right: rightPadding, - bottom: screenHeight - - messageOffset!.dy - - messageSize!.height - - belowMessageHeight, - child: overlayMessage, - ) - : AnimatedBuilder( - animation: _overlayPositionAnimation!, - builder: (context, child) { - return Positioned( + final positionedOverlayMessage = + (_overlayPositionAnimation == null || screenHeight == null) + ? Positioned( left: leftPadding, right: rightPadding, - bottom: _overlayPositionAnimation!.value, + bottom: screenHeight! - + messageOffset!.dy - + messageSize!.height - + belowMessageHeight, child: overlayMessage, + ) + : AnimatedBuilder( + animation: _overlayPositionAnimation!, + builder: (context, child) { + return Positioned( + left: leftPadding, + right: rightPadding, + bottom: _overlayPositionAnimation!.value, + child: overlayMessage, + ); + }, ); - }, - ); return Padding( padding: EdgeInsets.only( From f6bab9273340fabaeaaf5766fa6b338ab517c05e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 23 Oct 2024 11:19:30 -0400 Subject: [PATCH 8/8] better error handling for renderbox errors --- .../chat/message_selection_overlay.dart | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 7c91a87bc..48cdfbd47 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -105,17 +105,29 @@ class MessageOverlayController extends State /// This is a workaround to prevent that error @override void setState(VoidCallback fn) { - if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle || - SchedulerBinding.instance.schedulerPhase == - SchedulerPhase.postFrameCallbacks && - mounted) { + final phase = SchedulerBinding.instance.schedulerPhase; + if (mounted && + (phase == SchedulerPhase.idle || + phase == SchedulerPhase.postFrameCallbacks)) { // It's safe to call setState immediately - super.setState(fn); + try { + super.setState(fn); + } catch (e, s) { + ErrorHandler.logError( + e: "Error calling setState in MessageSelectionOverlay: $e", + s: s, + ); + } } else { // Defer the setState call to after the current frame WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - super.setState(fn); + try { + if (mounted) super.setState(fn); + } catch (e, s) { + ErrorHandler.logError( + e: "Error calling setState in MessageSelectionOverlay after postframeCallback: $e", + s: s, + ); } }); } @@ -404,6 +416,8 @@ class MessageOverlayController extends State @override Widget build(BuildContext context) { + if (messageSize == null) return const SizedBox.shrink(); + final bool showDetails = (Matrix.of(context) .store .getBool(SettingKeys.displayChatDetailsColumn) ?? @@ -483,21 +497,25 @@ class MessageOverlayController extends State ? FluffyThemes.columnWidth + FluffyThemes.navRailWidth : 0; - final double? leftPadding = widget._pangeaMessageEvent.ownMessage - ? null - : messageOffset!.dx - horizontalPadding - columnOffset; + final double? leftPadding = + (widget._pangeaMessageEvent.ownMessage || messageOffset == null) + ? null + : messageOffset!.dx - horizontalPadding - columnOffset; - final double? rightPadding = - (widget._pangeaMessageEvent.ownMessage && screenWidth != null) - ? screenWidth! - - messageOffset!.dx - - messageSize!.width - - horizontalPadding - : null; + final double? rightPadding = (widget._pangeaMessageEvent.ownMessage && + screenWidth != null && + messageOffset != null && + messageSize != null) + ? screenWidth! - + messageOffset!.dx - + messageSize!.width - + horizontalPadding + : null; - final positionedOverlayMessage = - (_overlayPositionAnimation == null || screenHeight == null) - ? Positioned( + final positionedOverlayMessage = (_overlayPositionAnimation == null) + ? (screenHeight == null || messageSize == null || messageOffset == null) + ? const SizedBox.shrink() + : Positioned( left: leftPadding, right: rightPadding, bottom: screenHeight! - @@ -506,17 +524,17 @@ class MessageOverlayController extends State belowMessageHeight, child: overlayMessage, ) - : AnimatedBuilder( - animation: _overlayPositionAnimation!, - builder: (context, child) { - return Positioned( - left: leftPadding, - right: rightPadding, - bottom: _overlayPositionAnimation!.value, - child: overlayMessage, - ); - }, + : AnimatedBuilder( + animation: _overlayPositionAnimation!, + builder: (context, child) { + return Positioned( + left: leftPadding, + right: rightPadding, + bottom: _overlayPositionAnimation!.value, + child: overlayMessage, ); + }, + ); return Padding( padding: EdgeInsets.only(