diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 44fb3ac3f..225558353 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -27,8 +27,8 @@ import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; -import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart'; @@ -188,6 +188,7 @@ class ChatController extends State StreamSubscription? _analyticsSubscription; StreamSubscription? _botAudioSubscription; final timelineUpdateNotifier = _TimelineUpdateNotifier(); + late final ActivityChatController activityController; // Pangea# Room get room => sendingClient.getRoomById(roomId) ?? widget.room; @@ -536,6 +537,11 @@ class ChatController extends State _botAudioSubscription = room.client.onSync.stream.listen(_botAudioListener); + activityController = ActivityChatController( + userID: Matrix.of(context).client.userID!, + getAnalytics: room.getActivityAnalytics, + ); + Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; pangeaController.languageController.showDialogOnEmptyLanguage( @@ -782,14 +788,11 @@ class ChatController extends State _storeInputTimeoutTimer?.cancel(); _displayChatDetailsColumn.dispose(); timelineUpdateNotifier.dispose(); - highlightedRole.dispose(); - showInstructions.dispose(); - showActivityDropdown.dispose(); - hasRainedConfetti.dispose(); typingCoolDown?.cancel(); typingTimeout?.cancel(); scrollController.removeListener(_updateScrollController); choreographer.dispose(); + activityController.dispose(); MatrixState.pAnyState.closeAllOverlays(force: true); showToolbarStream.close(); stopMediaStream.close(); @@ -797,7 +800,6 @@ class ChatController extends State _analyticsSubscription?.cancel(); _botAudioSubscription?.cancel(); _router.routeInformationProvider.removeListener(_onRouteChanged); - carouselController.dispose(); scrollController.dispose(); inputFocus.dispose(); TokensUtil.clearNewTokenCache(); @@ -2316,33 +2318,9 @@ class ChatController extends State } } - final ScrollController carouselController = ScrollController(); - - ValueNotifier highlightedRole = ValueNotifier(null); - void highlightRole(ActivityRoleModel role) { - if (mounted) highlightedRole.value = role; - } - - ValueNotifier showInstructions = ValueNotifier(false); - void toggleShowInstructions() { - if (mounted) { - showInstructions.value = !showInstructions.value; - } - } - - ValueNotifier showActivityDropdown = ValueNotifier(false); - void toggleShowDropdown() async { - if (mounted) { - inputFocus.unfocus(); - showActivityDropdown.value = !showActivityDropdown.value; - } - } - - ValueNotifier hasRainedConfetti = ValueNotifier(false); - void setHasRainedConfetti(bool show) { - if (mounted) { - hasRainedConfetti.value = show; - } + void toggleShowDropdown() { + inputFocus.unfocus(); + activityController.toggleShowDropdown(); } // Pangea# diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index bd0bb79ec..da0fb5aeb 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -497,14 +497,16 @@ class ChatView extends StatelessWidget { ActivityStatsMenu(controller), if (controller.room.activitySummary?.summary != null) ValueListenableBuilder( - valueListenable: controller.hasRainedConfetti, + valueListenable: + controller.activityController.hasRainedConfetti, builder: (context, hasRained, __) { return hasRained ? const SizedBox() : StarRainWidget( showBlast: true, - onFinished: () => - controller.setHasRainedConfetti(true), + onFinished: () => controller + .activityController + .setHasRainedConfetti(true), ); }, ), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index e0850fbcb..4dd9ee40f 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -138,7 +138,7 @@ class Message extends StatelessWidget { if (event.type == PangeaEventTypes.activityPlan && event.room.activityPlan != null) { return ValueListenableBuilder( - valueListenable: controller.showInstructions, + valueListenable: controller.activityController.showInstructions, builder: (context, show, __) { return ActivitySummary( activity: event.room.activityPlan!, @@ -147,11 +147,13 @@ class Message extends StatelessWidget { ? event.room.activityRoles?.roles ?? {} : event.room.assignedRoles ?? {}, showInstructions: show, - toggleInstructions: controller.toggleShowInstructions, + toggleInstructions: + controller.activityController.toggleShowInstructions, getParticipantOpacity: (role) => role == null || role.isFinished ? 0.5 : 1.0, isParticipantSelected: (id) => controller.room.ownRoleState?.id == id, + usedVocab: controller.activityController.usedVocab, ); }, ); diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart new file mode 100644 index 000000000..eb2575efc --- /dev/null +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; +import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class ActivityChatController { + final String userID; + final Future Function()? getAnalytics; + + ActivityChatController({ + required this.userID, + required this.getAnalytics, + }) { + init(); + } + + StreamSubscription? _analyticsSubscription; + bool _disposed = false; + + final ScrollController carouselController = ScrollController(); + final ValueNotifier> usedVocab = ValueNotifier({}); + final ValueNotifier highlightedRole = ValueNotifier(null); + final ValueNotifier showInstructions = ValueNotifier(false); + final ValueNotifier showActivityDropdown = ValueNotifier(false); + final ValueNotifier hasRainedConfetti = ValueNotifier(false); + + void init() { + if (getAnalytics != null) { + _updateUsedVocab(); + _analyticsSubscription = MatrixState + .pangeaController.getAnalytics.analyticsStream.stream + .listen((_) { + _updateUsedVocab(); + }); + } + } + + void dispose() { + _disposed = true; + carouselController.dispose(); + _analyticsSubscription?.cancel(); + usedVocab.dispose(); + highlightedRole.dispose(); + showInstructions.dispose(); + showActivityDropdown.dispose(); + hasRainedConfetti.dispose(); + } + + void highlightRole(ActivityRoleModel role) { + if (!_disposed) { + highlightedRole.value = role; + } + } + + void toggleShowInstructions() { + if (!_disposed) { + showInstructions.value = !showInstructions.value; + } + } + + void toggleShowDropdown() { + if (!_disposed) { + showActivityDropdown.value = !showActivityDropdown.value; + } + } + + void setHasRainedConfetti(bool show) { + if (!_disposed) { + hasRainedConfetti.value = show; + } + } + + Future _updateUsedVocab() async { + if (getAnalytics == null) return; + + try { + final analytics = await getAnalytics!.call(); + if (!_disposed) { + usedVocab.value = analytics.constructs[userID] + ?.constructsOfType(ConstructTypeEnum.vocab) + .map((id) => id.lemma.toLowerCase()) + .toSet() ?? + {}; + } + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "message": "Failed to update used vocab in ActivityChatController", + }, + ); + } + } +} diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart index 78658354a..3189ad1e8 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart @@ -3,77 +3,26 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart'; -import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; -import 'package:fluffychat/pangea/common/utils/overlay.dart'; -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -class ActivityStatsMenu extends StatefulWidget { +class ActivityStatsMenu extends StatelessWidget { final ChatController controller; const ActivityStatsMenu( this.controller, { super.key, }); - @override - State createState() => ActivityStatsMenuState(); -} - -class ActivityStatsMenuState extends State { - ActivitySummaryAnalyticsModel? analytics; - Room get room => widget.controller.room; - - StreamSubscription? _analyticsSubscription; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateUsedVocab(); - }); - - _analyticsSubscription = widget - .controller.pangeaController.getAnalytics.analyticsStream.stream - .listen((_) { - _updateUsedVocab(); - }); - } - - @override - void dispose() { - _analyticsSubscription?.cancel(); - super.dispose(); - } - - Set? get _usedVocab => analytics?.constructs[room.client.userID!] - ?.constructsOfType(ConstructTypeEnum.vocab) - .map((id) => id.lemma.toLowerCase()) - .toSet(); - - Future _updateUsedVocab() async { - final analytics = await room.getActivityAnalytics(); - if (mounted) { - setState(() => this.analytics = analytics); - } - } - int _getAssignedRolesCount() { - final assignedRoles = room.assignedRoles; + final assignedRoles = controller.room.assignedRoles; if (assignedRoles == null) return 0; final nonBotRoles = assignedRoles.values.where( (role) => role.userId != BotName.byEnvironment, @@ -83,30 +32,31 @@ class ActivityStatsMenuState extends State { } bool _isBotParticipant() { - final assignedRoles = room.assignedRoles; + final assignedRoles = controller.room.assignedRoles; if (assignedRoles == null) return false; return assignedRoles.values.any( (role) => role.userId == BotName.byEnvironment, ); } - Future _finishActivity({bool forAll = false}) async { + Future _finishActivity( + BuildContext context, { + bool forAll = false, + }) async { await showFutureLoadingDialog( context: context, future: () async { forAll - ? await room.finishActivityForAll() - : await room.finishActivity(); - if (mounted) { - widget.controller.toggleShowDropdown(); - } + ? await controller.room.finishActivityForAll() + : await controller.room.finishActivity(); + controller.toggleShowDropdown(); }, ); } @override Widget build(BuildContext context) { - if (!room.showActivityChatUI) { + if (!controller.room.showActivityChatUI) { return const SizedBox.shrink(); } @@ -114,12 +64,12 @@ class ActivityStatsMenuState extends State { final isColumnMode = FluffyThemes.isColumnMode(context); // Completion status variables - final bool userComplete = room.hasCompletedRole; - final bool activityComplete = room.isActivityFinished; + final bool userComplete = controller.room.hasCompletedRole; + final bool activityComplete = controller.room.isActivityFinished; bool shouldShowEndForAll = true; bool shouldShowImDone = true; - if (!room.isRoomAdmin) { + if (!controller.room.isRoomAdmin) { shouldShowEndForAll = false; } @@ -135,7 +85,7 @@ class ActivityStatsMenuState extends State { } return ValueListenableBuilder( - valueListenable: widget.controller.showActivityDropdown, + valueListenable: controller.activityController.showActivityDropdown, builder: (context, showDropdown, child) { return Positioned( top: 0, @@ -153,7 +103,7 @@ class ActivityStatsMenuState extends State { child: GestureDetector( onPanUpdate: (details) { if (details.delta.dy < -2) { - widget.controller.toggleShowDropdown(); + controller.toggleShowDropdown(); } }, child: child, @@ -163,7 +113,7 @@ class ActivityStatsMenuState extends State { if (showDropdown) Expanded( child: GestureDetector( - onTap: widget.controller.toggleShowDropdown, + onTap: controller.toggleShowDropdown, child: Container(color: Colors.black.withAlpha(100)), ), ), @@ -189,26 +139,21 @@ class ActivityStatsMenuState extends State { icon: Symbols.radar, iconSize: 16.0, child: Text( - room.activityPlan!.learningObjective, + controller.room.activityPlan!.learningObjective, style: const TextStyle(fontSize: 12.0), ), ), ActivitySessionDetailsRow( icon: Symbols.dictionary, iconSize: 16.0, - child: Wrap( - spacing: 4.0, - runSpacing: 4.0, - children: [ - ...room.activityPlan!.vocab.map( - (v) => VocabTile( - vocab: v, - langCode: room.activityPlan!.req.targetLanguage, - isUsed: (_usedVocab ?? {}) - .contains(v.lemma.toLowerCase()), - ), - ), - ], + child: ActivityVocabWidget( + key: ValueKey( + "activity-stats-${controller.room.activityPlan!.activityId}", + ), + vocab: controller.room.activityPlan!.vocab, + langCode: controller.room.activityPlan!.req.targetLanguage, + targetId: "activity-vocab", + usedVocab: controller.activityController.usedVocab, ), ), ], @@ -236,7 +181,7 @@ class ActivityStatsMenuState extends State { foregroundColor: theme.colorScheme.primary, backgroundColor: theme.colorScheme.surface, ), - onPressed: () => _finishActivity(forAll: true), + onPressed: () => _finishActivity(context, forAll: true), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -257,7 +202,7 @@ class ActivityStatsMenuState extends State { vertical: 8.0, ), ), - onPressed: _finishActivity, + onPressed: () => _finishActivity(context), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -277,89 +222,3 @@ class ActivityStatsMenuState extends State { ); } } - -class VocabTile extends StatelessWidget { - final Vocab vocab; - final String langCode; - final bool isUsed; - - const VocabTile({ - super.key, - required this.vocab, - required this.langCode, - required this.isUsed, - }); - - @override - Widget build(BuildContext context) { - final color = isUsed - ? Color.alphaBlend( - Theme.of(context).colorScheme.surface.withAlpha(150), - AppConfig.gold, - ) - : Colors.transparent; - return CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey( - "activity-vocab-${vocab.lemma}", - ) - .link, - child: InkWell( - key: MatrixState.pAnyState - .layerLinkAndKey( - "activity-vocab-${vocab.lemma}", - ) - .key, - borderRadius: BorderRadius.circular( - 24.0, - ), - onTap: () { - OverlayUtil.showPositionedCard( - overlayKey: "activity-vocab-${vocab.lemma}", - context: context, - cardToShow: WordZoomWidget( - token: PangeaTokenText( - content: vocab.lemma, - length: vocab.lemma.characters.length, - offset: 0, - ), - construct: ConstructIdentifier( - lemma: vocab.lemma, - type: ConstructTypeEnum.vocab, - category: vocab.pos, - ), - langCode: langCode, - onClose: () { - MatrixState.pAnyState.closeOverlay( - "activity-vocab-${vocab.lemma}", - ); - }, - ), - transformTargetId: "activity-vocab-${vocab.lemma}", - closePrevOverlay: false, - addBorder: false, - maxWidth: AppConfig.toolbarMinWidth, - maxHeight: AppConfig.toolbarMaxHeight, - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, - ), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(20), - ), - child: Text( - vocab.lemma, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 14.0, - ), - ), - ), - ), - ); - } -} diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart new file mode 100644 index 000000000..5a3278112 --- /dev/null +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class ActivityVocabWidget extends StatelessWidget { + final List vocab; + final String langCode; + final String targetId; + final ValueNotifier>? usedVocab; + + const ActivityVocabWidget({ + super.key, + required this.vocab, + required this.langCode, + required this.targetId, + this.usedVocab, + }); + + @override + Widget build(BuildContext context) { + if (usedVocab == null) { + return _VocabChips( + vocab: vocab, + targetId: targetId, + langCode: langCode, + usedVocab: const {}, + ); + } + + return ValueListenableBuilder( + valueListenable: usedVocab!, + builder: (context, used, __) => _VocabChips( + vocab: vocab, + targetId: targetId, + langCode: langCode, + usedVocab: used, + ), + ); + } +} + +class _VocabChips extends StatelessWidget { + final List vocab; + final String targetId; + final String langCode; + final Set usedVocab; + + const _VocabChips({ + required this.vocab, + required this.targetId, + required this.langCode, + required this.usedVocab, + }); + + void _onTap(Vocab v, BuildContext context) { + final target = "$targetId-${v.lemma}"; + OverlayUtil.showPositionedCard( + overlayKey: target, + context: context, + cardToShow: WordZoomWidget( + token: PangeaTokenText( + content: v.lemma, + length: v.lemma.characters.length, + offset: 0, + ), + construct: ConstructIdentifier( + lemma: v.lemma, + type: ConstructTypeEnum.vocab, + category: v.pos, + ), + langCode: langCode, + onClose: () { + MatrixState.pAnyState.closeOverlay(target); + }, + ), + transformTargetId: target, + closePrevOverlay: false, + addBorder: false, + maxWidth: AppConfig.toolbarMinWidth, + maxHeight: AppConfig.toolbarMaxHeight, + ); + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 4.0, + runSpacing: 4.0, + children: [ + ...vocab.map( + (v) { + final target = "$targetId-${v.lemma}"; + final color = usedVocab.contains(v.lemma.toLowerCase()) + ? Color.alphaBlend( + Theme.of(context).colorScheme.surface.withAlpha(150), + AppConfig.gold, + ) + : Theme.of(context).colorScheme.primary.withAlpha(20); + + final linkAndKey = MatrixState.pAnyState.layerLinkAndKey(target); + + return CompositedTransformTarget( + link: linkAndKey.link, + child: InkWell( + key: linkAndKey.key, + borderRadius: BorderRadius.circular( + 24.0, + ), + onTap: () => _onTap(v, context), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 4.0, + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + v.lemma, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 14.0, + ), + ), + ), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pangea/activity_sessions/activity_summary_widget.dart b/lib/pangea/activity_sessions/activity_summary_widget.dart index 9f6468602..a1ae85035 100644 --- a/lib/pangea/activity_sessions/activity_summary_widget.dart +++ b/lib/pangea/activity_sessions/activity_summary_widget.dart @@ -9,21 +9,15 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/markdown.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_participant_list.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart'; -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart'; -import 'package:fluffychat/widgets/matrix.dart'; class ActivitySummary extends StatelessWidget { final ActivityPlanModel activity; @@ -39,12 +33,15 @@ class ActivitySummary extends StatelessWidget { final bool Function(String)? isParticipantSelected; final double Function(ActivityRoleModel?)? getParticipantOpacity; + final ValueNotifier>? usedVocab; + const ActivitySummary({ super.key, required this.activity, required this.showInstructions, required this.toggleInstructions, required this.assignedRoles, + this.usedVocab, this.onTapParticipant, this.canSelectParticipant, this.isParticipantSelected, @@ -183,75 +180,14 @@ class ActivitySummary extends StatelessWidget { ActivitySessionDetailsRow( icon: Symbols.dictionary, iconSize: 16.0, - child: Wrap( - spacing: 4.0, - runSpacing: 4.0, - children: activity.vocab.map((vocab) { - return CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey( - "activity-summary-vocab-${vocab.lemma}", - ) - .link, - child: InkWell( - key: MatrixState.pAnyState - .layerLinkAndKey( - "activity-summary-vocab-${vocab.lemma}", - ) - .key, - borderRadius: BorderRadius.circular( - 24.0, - ), - onTap: () { - OverlayUtil.showPositionedCard( - overlayKey: - "activity-summary-vocab-${vocab.lemma}", - context: context, - cardToShow: WordZoomWidget( - token: PangeaTokenText( - content: vocab.lemma, - length: vocab.lemma.characters.length, - offset: 0, - ), - construct: ConstructIdentifier( - lemma: vocab.lemma, - type: ConstructTypeEnum.vocab, - category: vocab.pos, - ), - langCode: activity.req.targetLanguage, - onClose: () { - MatrixState.pAnyState.closeOverlay( - "activity-summary-vocab-${vocab.lemma}", - ); - }, - ), - transformTargetId: - "activity-summary-vocab-${vocab.lemma}", - closePrevOverlay: false, - addBorder: false, - maxWidth: AppConfig.toolbarMinWidth, - maxHeight: AppConfig.toolbarMaxHeight, - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, - ), - decoration: BoxDecoration( - color: theme.colorScheme.primary.withAlpha( - 20, - ), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - vocab.lemma, - style: theme.textTheme.bodyMedium, - ), - ), - ), - ); - }).toList(), + child: ActivityVocabWidget( + key: ValueKey( + "activity-summary-${activity.activityId}", + ), + vocab: activity.vocab, + langCode: activity.req.targetLanguage, + targetId: "activity-summary-vocab", + usedVocab: usedVocab, ), ), ], diff --git a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart index 52e8cac63..1c232f6ea 100644 --- a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart +++ b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart @@ -110,7 +110,7 @@ class ButtonControlledCarouselView extends StatelessWidget { height: 270.0, child: ListView( shrinkWrap: true, - controller: controller.carouselController, + controller: controller.activityController.carouselController, scrollDirection: Axis.horizontal, children: userSummaries.mapIndexed((i, p) { final user = room.getParticipants().firstWhereOrNull( @@ -231,7 +231,7 @@ class ButtonControlledCarouselView extends StatelessWidget { ), const SizedBox(height: 12), ValueListenableBuilder( - valueListenable: controller.highlightedRole, + valueListenable: controller.activityController.highlightedRole, builder: (context, highlightedRole, __) { return Row( mainAxisSize: MainAxisSize.min, @@ -250,8 +250,9 @@ class ButtonControlledCarouselView extends StatelessWidget { borderRadius: BorderRadius.circular(4), selected: highlightedRole?.id == userRole.id, onTap: () { - controller.highlightRole(userRole); - controller.carouselController.jumpTo(i * 250.0); + controller.activityController.highlightRole(userRole); + controller.activityController.carouselController + .jumpTo(i * 250.0); }, ); }).toList(),