From 916da50bd4acda7cf553e8bc0070309eb965be53 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:48:26 -0400 Subject: [PATCH] 3871 activity session issues feedback (#3874) * fix: add header when fetching image from CMS * fix: only show activity start page is all roles have never been full * chore: disable archive button until summary loads * chore: still save activity analytics summary even if there's a choreo error when fetching summary --- lib/pages/chat/chat_event_list.dart | 11 +-- lib/pages/chat/chat_view.dart | 4 + .../activity_planner_builder.dart | 13 +++- .../activity_room_extension.dart | 12 ++- .../activity_finished_status_message.dart | 62 ++++++++------- .../activity_user_summaries_widget.dart | 2 +- lib/widgets/mxc_image.dart | 78 +++++++++++++------ 7 files changed, 116 insertions(+), 66 deletions(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index dc9f282ce..a58e86413 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart'; -import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_user_summaries_widget.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/utils/account_config.dart'; @@ -95,7 +94,7 @@ class ChatEventList extends StatelessWidget { // Request history button or progress indicator: // #Pangea // if (i == events.length + 1) { - if (i == events.length + 3) { + if (i == events.length + 2) { // Pangea# if (timeline.isRequestingHistory) { return const Center( @@ -126,17 +125,13 @@ class ChatEventList extends StatelessWidget { // #Pangea if (i == 1) { - return ActivityFinishedStatusMessage(controller: controller); - } - - if (i == 2) { return ActivityUserSummaries(controller: controller); } // Pangea# // #Pangea // i--; - i = i - 3; + i = i - 2; // Pangea# // The message at this index: @@ -214,7 +209,7 @@ class ChatEventList extends StatelessWidget { }, // #Pangea // childCount: events.length + 2, - childCount: events.length + 4, + childCount: events.length + 3, // Pangea# findChildIndexCallback: (key) => controller.findChildIndexCallback(key, thisEventsKeyMap), diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index b73d5956f..5e263738c 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -14,6 +14,7 @@ import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_pinned_message.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/load_activity_summary_widget.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart'; @@ -437,6 +438,9 @@ class ChatView extends StatelessWidget { ), if (controller.room.activityIsFinished) LoadActivitySummaryWidget(room: controller.room), + ActivityFinishedStatusMessage( + controller: controller, + ), // Pangea# ], ), diff --git a/lib/pangea/activity_planner/activity_planner_builder.dart b/lib/pangea/activity_planner/activity_planner_builder.dart index b9c6364b2..a6d14fb01 100644 --- a/lib/pangea/activity_planner/activity_planner_builder.dart +++ b/lib/pangea/activity_planner/activity_planner_builder.dart @@ -237,7 +237,18 @@ class ActivityPlannerBuilderState extends State { mxcUri.pathSegments.last, ); } else { - final Response response = await http.get(Uri.parse(url)); + final Response response = await http.get( + Uri.parse(url), + headers: { + 'Authorization': + 'Bearer ${MatrixState.pangeaController.userController.accessToken}', + }, + ); + if (response.statusCode != 200) { + throw Exception( + "Failed to download image from URL: ${response.statusCode}", + ); + } avatar = response.bodyBytes; filename = Uri.encodeComponent( Uri.parse(url).pathSegments.last, diff --git a/lib/pangea/activity_sessions/activity_room_extension.dart b/lib/pangea/activity_sessions/activity_room_extension.dart index 049ebb61a..f79603ee8 100644 --- a/lib/pangea/activity_sessions/activity_room_extension.dart +++ b/lib/pangea/activity_sessions/activity_room_extension.dart @@ -121,7 +121,7 @@ extension ActivityRoomExtension on Room { final events = await getAllEvents(); final List messages = []; final ActivitySummaryAnalyticsModel analytics = - ActivitySummaryAnalyticsModel(); + activitySummary?.analytics ?? ActivitySummaryAnalyticsModel(); final timeline = this.timeline ?? await getTimeline(); for (final event in events) { @@ -148,7 +148,10 @@ extension ActivityRoomExtension on Room { ); messages.add(activityMessage); - analytics.addConstructs(pangeaMessage); + + if (activitySummary?.analytics == null) { + analytics.addConstructs(pangeaMessage); + } } try { @@ -182,6 +185,7 @@ extension ActivityRoomExtension on Room { await setActivitySummary( ActivitySummaryModel( errorAt: DateTime.now(), + analytics: analytics, ), ); } @@ -274,7 +278,9 @@ extension ActivityRoomExtension on Room { powerForChangingStateEvent(PangeaEventTypes.activitySummary) == 0; } - bool get activityHasStarted => remainingRoles == 0; + bool get activityHasStarted => + (activityPlan?.roles.length ?? 0) - (activityRoles?.roles.length ?? 0) <= + 0; bool get isActiveInActivity { if (!showActivityChatUI) return false; diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart index f15cce367..9cf0529f3 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/saved_activity_analytics_dialog.dart'; +import 'package:fluffychat/pangea/activity_summary/activity_summary_model.dart'; import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/course_plans/course_plans_repo.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -20,6 +21,30 @@ class ActivityFinishedStatusMessage extends StatelessWidget { required this.controller, }); + Future _onArchive(BuildContext context) async { + { + final resp = await showFutureLoadingDialog( + context: context, + future: () => _archiveToAnalytics(context), + ); + + if (!resp.isError) { + final navigate = await showDialog( + context: context, + builder: (context) { + return const SavedActivityAnalyticsDialog(); + }, + ); + + if (navigate == true) { + context.go( + "/rooms/analytics?mode=activities", + ); + } + } + } + } + Future _archiveToAnalytics(BuildContext context) async { await controller.room.archiveActivity(); await MatrixState.pangeaController.putAnalytics @@ -44,6 +69,11 @@ class ActivityFinishedStatusMessage extends StatelessWidget { await courseParent.finishCourseActivity(activityId, topicId); } + ActivitySummaryModel? get summary => controller.room.activitySummary; + + bool get _enableArchive => + summary?.summary != null || summary?.hasError == true; + @override Widget build(BuildContext context) { if (!controller.room.showActivityChatUI || @@ -53,18 +83,13 @@ class ActivityFinishedStatusMessage extends StatelessWidget { } final theme = Theme.of(context); - final summary = controller.room.activitySummary; - return AnimatedSize( duration: FluffyThemes.animationDuration, child: Container( margin: const EdgeInsets.only(top: 20.0), - padding: const EdgeInsets.only( - top: 12.0, - left: 12.0, - right: 12.0, - ), + padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( + color: theme.colorScheme.surface, border: Border( top: BorderSide(color: theme.dividerColor), ), @@ -123,27 +148,8 @@ class ActivityFinishedStatusMessage extends StatelessWidget { theme.colorScheme.onPrimaryContainer, backgroundColor: theme.colorScheme.primaryContainer, ), - onPressed: () async { - final resp = await showFutureLoadingDialog( - context: context, - future: () => _archiveToAnalytics(context), - ); - - if (!resp.isError) { - final navigate = await showDialog( - context: context, - builder: (context) { - return const SavedActivityAnalyticsDialog(); - }, - ); - - if (navigate == true) { - context.go( - "/rooms/analytics?mode=activities", - ); - } - } - }, + onPressed: + _enableArchive ? () => _onArchive(context) : null, child: Row( spacing: 12.0, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart index ae8a17e04..dfcc7e74f 100644 --- a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart +++ b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart @@ -156,7 +156,7 @@ class ButtonControlledCarouselView extends StatelessWidget { child: SingleChildScrollView( child: Text( p.feedback, - style: const TextStyle(fontSize: 8.0), + style: const TextStyle(fontSize: 12.0), ), ), ), diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 13b1fe823..33af91abf 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -65,6 +65,10 @@ class _MxcImageState extends State { : _imageDataCache[cacheKey] = data; } + // #Pangea + Object? _error; + // Pangea# + Future _load() async { if (!mounted) return; final client = @@ -112,12 +116,22 @@ class _MxcImageState extends State { return; } try { + // #Pangea + setState(() => _error = null); + // Pangea# await _load(); } on IOException catch (_) { if (!mounted) return; await Future.delayed(widget.retryDuration); _tryLoad(); } + // #Pangea + catch (e) { + if (mounted) { + setState(() => _error = e); + } + } + // Pangea# } @override @@ -153,37 +167,51 @@ class _MxcImageState extends State { return AnimatedCrossFade( crossFadeState: - hasData ? CrossFadeState.showSecond : CrossFadeState.showFirst, + // #Pangea + // hasData ? CrossFadeState.showSecond : CrossFadeState.showFirst, + hasData || _error != null + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + // Pangea# duration: const Duration(milliseconds: 128), firstChild: placeholder(context), - secondChild: hasData - ? Image.memory( - data, + // #Pangea + // secondChild: hasData + secondChild: _error != null + ? SizedBox( width: widget.width, height: widget.height, - fit: widget.fit, - filterQuality: - widget.isThumbnail ? FilterQuality.low : FilterQuality.medium, - errorBuilder: (context, e, s) { - Logs().d('Unable to render mxc image', e, s); - return SizedBox( + ) + : hasData + // Pangea# + ? Image.memory( + data, width: widget.width, height: widget.height, - child: Material( - color: Theme.of(context).colorScheme.surfaceContainer, - child: Icon( - Icons.broken_image_outlined, - size: min(widget.height ?? 64, 64), - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ); - }, - ) - : SizedBox( - width: widget.width, - height: widget.height, - ), + fit: widget.fit, + filterQuality: widget.isThumbnail + ? FilterQuality.low + : FilterQuality.medium, + errorBuilder: (context, e, s) { + Logs().d('Unable to render mxc image', e, s); + return SizedBox( + width: widget.width, + height: widget.height, + child: Material( + color: Theme.of(context).colorScheme.surfaceContainer, + child: Icon( + Icons.broken_image_outlined, + size: min(widget.height ?? 64, 64), + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ); + }, + ) + : SizedBox( + width: widget.width, + height: widget.height, + ), ); } }