diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d584c6729..40d0ce5df 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -29,6 +29,7 @@ import 'package:fluffychat/pages/settings_password/settings_password.dart'; import 'package:fluffychat/pages/settings_security/settings_security.dart'; import 'package:fluffychat/pages/settings_style/settings_style.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_page/analytics_page.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selection.dart'; @@ -344,29 +345,112 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - AnalyticsPage( - selectedIndicator: ProgressIndicatorEnum.fromString( - state.uri.queryParameters['mode'] ?? 'vocab', - ), - constructZoom: state.extra is ConstructIdentifier - ? state.extra as ConstructIdentifier - : null, - ), + const AnalyticsPage(), ), routes: [ GoRoute( - path: ':roomid', + path: ConstructTypeEnum.morph.string, pageBuilder: (context, state) => defaultPageBuilder( context, state, - ChatPage( - roomId: state.pathParameters['roomid']!, - eventId: state.uri.queryParameters['event'], - backButton: BackButton( - onPressed: () => context.go( - "/rooms/analytics?mode=activities", + AnalyticsPage( + indicator: FluffyThemes.isColumnMode(context) + ? null + : ProgressIndicatorEnum.morphsUsed, + ), + ), + redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: ':construct', + pageBuilder: (context, state) { + final construct = ConstructIdentifier.fromString( + state.pathParameters['construct']!, + ); + return defaultPageBuilder( + context, + state, + AnalyticsPage( + indicator: ProgressIndicatorEnum.morphsUsed, + construct: construct, + ), + ); + }, + ), + ], + ), + GoRoute( + path: ConstructTypeEnum.vocab.string, + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + AnalyticsPage( + indicator: FluffyThemes.isColumnMode(context) + ? null + : ProgressIndicatorEnum.wordsUsed, + ), + ), + redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: ':construct', + pageBuilder: (context, state) { + final construct = ConstructIdentifier.fromString( + state.pathParameters['construct']!, + ); + return defaultPageBuilder( + context, + state, + AnalyticsPage( + indicator: ProgressIndicatorEnum.wordsUsed, + construct: construct, + ), + ); + }, + ), + ], + ), + GoRoute( + path: 'activities', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + AnalyticsPage( + indicator: FluffyThemes.isColumnMode(context) + ? null + : ProgressIndicatorEnum.activities, + ), + ), + redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: ':roomid', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + ChatPage( + roomId: state.pathParameters['roomid']!, + eventId: state.uri.queryParameters['event'], + backButton: BackButton( + onPressed: () => context.go( + "/rooms/analytics/activities", + ), + ), ), ), + redirect: loggedOutRedirect, + ), + ], + ), + GoRoute( + path: 'level', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + AnalyticsPage( + indicator: FluffyThemes.isColumnMode(context) + ? null + : ProgressIndicatorEnum.level, ), ), redirect: loggedOutRedirect, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index c130e2efc..3fc73a929 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -21,7 +21,6 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart import 'package:fluffychat/pangea/chat_settings/utils/delete_room.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/delete_space_dialog.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/subscription/widgets/subscription_snackbar.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; @@ -1164,8 +1163,6 @@ class ChatListController extends State // #Pangea void _initPangeaControllers(Client client) { - GoogleAnalytics.analyticsUserUpdate(client.userID); - MatrixState.pangeaController.initControllers(); if (mounted) { MatrixState.pangeaController.classController.joinCachedSpaceCode(context); } diff --git a/lib/pangea/analytics_details_popup/analytics_details_popup.dart b/lib/pangea/analytics_details_popup/analytics_details_popup.dart index 79f5054e5..2ec51e746 100644 --- a/lib/pangea/analytics_details_popup/analytics_details_popup.dart +++ b/lib/pangea/analytics_details_popup/analytics_details_popup.dart @@ -4,7 +4,6 @@ import 'package:fluffychat/pangea/analytics_details_popup/morph_analytics_list_v import 'package:fluffychat/pangea/analytics_details_popup/morph_details_view.dart'; import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_details_view.dart'; import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_view.dart'; -import 'package:fluffychat/pangea/analytics_downloads/analytics_download_button.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; @@ -14,57 +13,38 @@ import 'package:fluffychat/pangea/morphs/morph_models.dart'; import 'package:fluffychat/pangea/morphs/morph_repo.dart'; import 'package:fluffychat/widgets/matrix.dart'; -class AnalyticsPopupWrapper extends StatefulWidget { - const AnalyticsPopupWrapper({ +class ConstructAnalyticsView extends StatefulWidget { + const ConstructAnalyticsView({ super.key, - this.constructZoom, required this.view, - this.backButtonOverride, + this.construct, }); final ConstructTypeEnum view; - final ConstructIdentifier? constructZoom; - final Widget? backButtonOverride; + final ConstructIdentifier? construct; @override - AnalyticsPopupWrapperState createState() => AnalyticsPopupWrapperState(); + ConstructAnalyticsViewState createState() => ConstructAnalyticsViewState(); } -class AnalyticsPopupWrapperState extends State { - ConstructIdentifier? localConstructZoom; - ConstructTypeEnum localView = ConstructTypeEnum.vocab; +class ConstructAnalyticsViewState extends State { + final TextEditingController searchController = TextEditingController(); MorphFeaturesAndTags morphs = defaultMorphMapping; List features = defaultMorphMapping.displayFeatures; bool isSearching = false; - final TextEditingController searchController = TextEditingController(); ConstructLevelEnum? selectedConstructLevel; @override void initState() { super.initState(); - localView = widget.view; - setConstructZoom(widget.constructZoom); _setMorphs(); searchController.addListener(() { if (mounted) setState(() {}); }); } - @override - void didUpdateWidget(covariant AnalyticsPopupWrapper oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.constructZoom != oldWidget.constructZoom) { - setConstructZoom(widget.constructZoom); - } - if (widget.view != oldWidget.view) { - localView = widget.view; - localConstructZoom = null; - setState(() {}); - } - } - @override void dispose() { searchController.dispose(); @@ -92,14 +72,6 @@ class AnalyticsPopupWrapperState extends State { } } - void setConstructZoom(ConstructIdentifier? id) { - if (id != null && id.type != localView) { - localView = id.type; - } - localConstructZoom = id; - setState(() => {}); - } - void setSelectedConstructLevel(ConstructLevelEnum level) { setState(() { selectedConstructLevel = selectedConstructLevel == level ? null : level; @@ -116,34 +88,12 @@ class AnalyticsPopupWrapperState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - localConstructZoom != null - ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - setConstructZoom(null); - }, - ) - : const SizedBox(), - const DownloadAnalyticsButton(), - ], - ), - Expanded( - child: localView == ConstructTypeEnum.morph - ? localConstructZoom == null - ? MorphAnalyticsListView(controller: this) - : MorphDetailsView(constructId: localConstructZoom!) - : localConstructZoom == null - ? VocabAnalyticsListView(controller: this) - : VocabDetailsView(constructId: localConstructZoom!), - ), - ], - ), - ); + return widget.view == ConstructTypeEnum.morph + ? widget.construct == null + ? MorphAnalyticsListView(controller: this) + : MorphDetailsView(constructId: widget.construct!) + : widget.construct == null + ? VocabAnalyticsListView(controller: this) + : VocabDetailsView(constructId: widget.construct!); } } diff --git a/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart b/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart index a4804166a..86edecb6a 100644 --- a/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart @@ -1,10 +1,13 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; +import 'package:fluffychat/pangea/analytics_downloads/analytics_download_button.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; @@ -18,7 +21,7 @@ import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/widgets/matrix.dart'; class MorphAnalyticsListView extends StatelessWidget { - final AnalyticsPopupWrapperState controller; + final ConstructAnalyticsViewState controller; const MorphAnalyticsListView({ required this.controller, @@ -40,6 +43,13 @@ class MorphAnalyticsListView extends StatelessWidget { ), if (!InstructionsEnum.morphAnalyticsList.isToggledOff) const SizedBox(height: 16.0), + if (kIsWeb) + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DownloadAnalyticsButton(), + ], + ), Expanded( child: ListView.builder( key: const PageStorageKey('morph-analytics'), @@ -55,7 +65,6 @@ class MorphAnalyticsListView extends StatelessWidget { .getDisplayTags(feature.feature) .map((tag) => tag.toLowerCase()) .toSet(), - onConstructZoom: controller.setConstructZoom, ), ) : const SizedBox.shrink(); @@ -71,13 +80,11 @@ class MorphAnalyticsListView extends StatelessWidget { class MorphFeatureBox extends StatelessWidget { final String morphFeature; final Set allTags; - final void Function(ConstructIdentifier) onConstructZoom; const MorphFeatureBox({ super.key, required this.morphFeature, required this.allTags, - required this.onConstructZoom, }); MorphFeaturesEnum get feature => @@ -148,7 +155,9 @@ class MorphFeatureBox extends StatelessWidget { morphFeature: morphFeature, morphTag: morphTag, constructAnalytics: analytics, - onTap: () => onConstructZoom(id), + onTap: () => context.go( + "/rooms/analytics/${id.type.string}/${id.string}", + ), ); }, ) diff --git a/lib/pangea/analytics_details_popup/morph_details_view.dart b/lib/pangea/analytics_details_popup/morph_details_view.dart index 473a5c314..15cacb55c 100644 --- a/lib/pangea/analytics_details_popup/morph_details_view.dart +++ b/lib/pangea/analytics_details_popup/morph_details_view.dart @@ -36,87 +36,11 @@ class MorphDetailsView extends StatelessWidget { morphTag: _morphTag, textColor: textColor, ), - headerContent: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: MorphMeaningWidget( - feature: _morphFeature, - tag: _morphTag, - style: Theme.of(context).textTheme.bodyLarge, - // leading: TextSpan( - // text: L10n.of(context).meaningSectionHeader, - // style: const TextStyle( - // fontWeight: FontWeight.bold, - // ), - // ), - ), - ), - ], - ), + headerContent: MorphMeaningWidget( + feature: _morphFeature, + tag: _morphTag, + style: Theme.of(context).textTheme.bodyLarge, ), - // headerContent: Padding( - // padding: const EdgeInsets.all(25.0), - // child: Align( - // alignment: Alignment.topLeft, - // child: FutureBuilder( - // future: _getDefinition(context), - // builder: ( - // BuildContext context, - // AsyncSnapshot snapshot, - // ) { - // if (snapshot.hasData) { - // return MorphMeaningWidget( - // feature: _morphFeature, - // tag: _morphTag, - // style: Theme.of(context).textTheme.bodyLarge, - // leading: TextSpan( - // text: L10n.of(context).meaningSectionHeader, - // style: const TextStyle( - // fontWeight: FontWeight.bold, - // ), - // ), - // ); - // } else if (snapshot.hasError) { - // return Wrap( - // children: [ - // Text( - // L10n.of(context).meaningSectionHeader, - // style: Theme.of(context).textTheme.bodyLarge?.copyWith( - // fontWeight: FontWeight.bold, - // ), - // ), - // const SizedBox( - // width: 10, - // ), - // Text( - // L10n.of(context).meaningNotFound, - // style: Theme.of(context).textTheme.bodyLarge, - // ), - // ], - // ); - // } else { - // return Wrap( - // children: [ - // Text( - // L10n.of(context).meaningSectionHeader, - // style: Theme.of(context).textTheme.bodyLarge?.copyWith( - // fontWeight: FontWeight.bold, - // ), - // ), - // const SizedBox( - // width: 10, - // ), - // const TextLoadingShimmer(width: 100), - // ], - // ); - // } - // }, - // ), - // ), - // ), xpIcon: ConstructXpWidget(id: constructId), constructId: constructId, ); diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart index 1ac4aa672..c57c5fcbe 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart @@ -1,10 +1,13 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_tile.dart'; +import 'package:fluffychat/pangea/analytics_downloads/analytics_download_button.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/constructs/construct_level_enum.dart'; @@ -15,7 +18,7 @@ import 'package:fluffychat/widgets/matrix.dart'; /// Displays vocab analytics, sorted into categories /// (flowers, greens, and seeds) by points class VocabAnalyticsListView extends StatelessWidget { - final AnalyticsPopupWrapperState controller; + final ConstructAnalyticsViewState controller; const VocabAnalyticsListView({ super.key, @@ -79,6 +82,10 @@ class VocabAnalyticsListView extends StatelessWidget { ), ); + if (kIsWeb) { + filters.add(const DownloadAnalyticsButton()); + } + return Column( children: [ const InstructionsInlineTooltip( @@ -123,7 +130,7 @@ class VocabAnalyticsListView extends StatelessWidget { ) : Row( spacing: FluffyThemes.isColumnMode(context) ? 16.0 : 4.0, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, key: const ValueKey('filters'), children: filters, ), @@ -143,7 +150,9 @@ class VocabAnalyticsListView extends StatelessWidget { itemBuilder: (context, index) { final vocabItem = _filteredVocab[index]; return VocabAnalyticsListTile( - onTap: () => controller.setConstructZoom(vocabItem.id), + onTap: () => context.go( + "/rooms/analytics/${vocabItem.id.type.string}/${vocabItem.id.string}", + ), constructUse: vocabItem, emoji: vocabItem.id.userSetEmoji.firstOrNull ?? vocabItem.id.getLemmaInfoCached()?.emoji.firstOrNull, diff --git a/lib/pangea/analytics_page/activity_archive.dart b/lib/pangea/analytics_page/activity_archive.dart index 9b355d6ce..59cd26ef9 100644 --- a/lib/pangea/analytics_page/activity_archive.dart +++ b/lib/pangea/analytics_page/activity_archive.dart @@ -1,29 +1,108 @@ import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pangea/analytics_page/activity_archive_view.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; +import 'package:fluffychat/widgets/hover_builder.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import '../../config/themes.dart'; +import '../../widgets/avatar.dart'; -class ActivityArchive extends StatefulWidget { +class ActivityArchive extends StatelessWidget { const ActivityArchive({super.key}); - @override - ActivityArchiveState createState() => ActivityArchiveState(); -} - -class ActivityArchiveState extends State { List get archive => MatrixState.pangeaController.getAnalytics.archivedActivities; - Future removeArchivedChat(Room room) async { - await room.leave(); - await MatrixState.pangeaController.putAnalytics - .removeActivityAnalytics(room.id); - - setState(() {}); + @override + Widget build(BuildContext context) { + return MaxWidthBody( + withScrolling: false, + child: Builder( + builder: (BuildContext context) { + if (archive.isEmpty) { + return const Center( + child: Icon(Icons.archive_outlined, size: 80), + ); + } + return ListView.builder( + itemCount: archive.length, + itemBuilder: (BuildContext context, int i) => AnalyticsActivityItem( + room: archive[i], + ), + ); + }, + ), + ); } +} + +class AnalyticsActivityItem extends StatelessWidget { + final Room room; + const AnalyticsActivityItem({ + super.key, + required this.room, + }); @override - Widget build(BuildContext context) => ActivityArchiveView(controller: this); + Widget build(BuildContext context) { + final objective = room.activityPlan?.learningObjective ?? ''; + final cefrLevel = room.activitySummary?.summary?.participants + .firstWhereOrNull( + (p) => p.participantId == room.client.userID, + ) + ?.cefrLevel; + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + visualDensity: const VisualDensity(vertical: -0.5), + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + leading: HoverBuilder( + builder: (context, hovered) => AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: hovered ? 1.1 : 1.0, + child: Avatar( + borderRadius: BorderRadius.circular(4.0), + mxContent: room.avatar, + name: room.getLocalizedDisplayname(), + ), + ), + ), + title: Text( + objective, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12.0), + ), + trailing: cefrLevel != null + ? Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Text( + cefrLevel.toUpperCase(), + style: const TextStyle(fontSize: 14.0), + ), + ) + : null, + onTap: () => context.go( + '/rooms/analytics/activities/${room.id}', + ), + ), + ), + ); + } } diff --git a/lib/pangea/analytics_page/activity_archive_view.dart b/lib/pangea/analytics_page/activity_archive_view.dart deleted file mode 100644 index ac0aee44d..000000000 --- a/lib/pangea/analytics_page/activity_archive_view.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:go_router/go_router.dart'; - -import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/pangea/analytics_page/activity_archive.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; - -class ActivityArchiveView extends StatelessWidget { - final ActivityArchiveState controller; - const ActivityArchiveView({ - super.key, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return MaxWidthBody( - withScrolling: false, - child: Builder( - builder: (BuildContext context) { - if (controller.archive.isEmpty) { - return const Center( - child: Icon(Icons.archive_outlined, size: 80), - ); - } - return ListView.builder( - itemCount: controller.archive.length, - itemBuilder: (BuildContext context, int i) => ChatListItem( - controller.archive[i], - onForget: () { - showFutureLoadingDialog( - context: context, - future: () => controller.removeArchivedChat( - controller.archive[i], - ), - ); - }, - onTap: () => - context.go('/rooms/analytics/${controller.archive[i].id}'), - ), - ); - }, - ), - ); - } -} diff --git a/lib/pangea/analytics_page/analytics_page.dart b/lib/pangea/analytics_page/analytics_page.dart index 95265411e..86f1f393e 100644 --- a/lib/pangea/analytics_page/analytics_page.dart +++ b/lib/pangea/analytics_page/analytics_page.dart @@ -1,46 +1,89 @@ import 'package:flutter/material.dart'; -import 'package:fluffychat/pangea/analytics_page/analytics_page_view.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/analytics_page/activity_archive.dart'; +import 'package:fluffychat/pangea/analytics_page/analytics_page_constants.dart'; +import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart'; +import 'package:fluffychat/pangea/analytics_summary/level_dialog_content.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/widgets/matrix.dart'; -class AnalyticsPage extends StatefulWidget { - final ProgressIndicatorEnum? selectedIndicator; - final ConstructIdentifier? constructZoom; +class AnalyticsPage extends StatelessWidget { + final ProgressIndicatorEnum? indicator; + final ConstructIdentifier? construct; + final bool isSidebar; + const AnalyticsPage({ super.key, - this.selectedIndicator, - this.constructZoom, + this.indicator, + this.construct, + this.isSidebar = false, }); @override - AnalyticsPageState createState() => AnalyticsPageState(); -} - -class AnalyticsPageState extends State { - ProgressIndicatorEnum? selectedIndicator = ProgressIndicatorEnum.wordsUsed; - - @override - void initState() { - super.initState(); - // init the analytics controllers - MatrixState.pangeaController.initControllers(); - selectedIndicator = widget.selectedIndicator ?? - ProgressIndicatorEnum.wordsUsed; // Default to wordsUsed if not set - } + Widget build(BuildContext context) { + return Scaffold( + appBar: construct != null ? AppBar() : null, + body: SafeArea( + child: Padding( + padding: const EdgeInsetsGeometry.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isSidebar || + (!FluffyThemes.isColumnMode(context) && construct == null)) + LearningProgressIndicators( + selected: indicator, + canSelect: indicator != ProgressIndicatorEnum.level, + ), + Expanded( + child: StreamBuilder( + stream: MatrixState + .pangeaController.getAnalytics.analyticsStream.stream, + builder: (context, _) { + if (indicator == ProgressIndicatorEnum.level) { + return const LevelDialogContent(); + } else if (indicator == ProgressIndicatorEnum.morphsUsed) { + return ConstructAnalyticsView( + construct: construct, + view: ConstructTypeEnum.morph, + ); + } else if (indicator == ProgressIndicatorEnum.wordsUsed) { + return ConstructAnalyticsView( + construct: construct, + view: ConstructTypeEnum.vocab, + ); + } else if (indicator == ProgressIndicatorEnum.activities) { + return const ActivityArchive(); + } - @override - void didUpdateWidget(covariant AnalyticsPage oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.selectedIndicator != widget.selectedIndicator && - widget.selectedIndicator != null) { - setState( - () => selectedIndicator = widget.selectedIndicator!, - ); // Update to new value - } + return Center( + child: SizedBox( + width: 250.0, + child: CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${AnalyticsPageConstants.dinoBotFileName}", + errorWidget: (context, url, error) => + const SizedBox(), + placeholder: (context, url) => const Center( + child: CircularProgressIndicator.adaptive(), + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); } - - @override - Widget build(BuildContext context) => AnalyticsPageView(controller: this); } diff --git a/lib/pangea/analytics_page/analytics_page_view.dart b/lib/pangea/analytics_page/analytics_page_view.dart deleted file mode 100644 index 818d13233..000000000 --- a/lib/pangea/analytics_page/analytics_page_view.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/analytics_page/activity_archive.dart'; -import 'package:fluffychat/pangea/analytics_page/analytics_page.dart'; -import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart'; -import 'package:fluffychat/pangea/analytics_summary/level_dialog_content.dart'; -import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class AnalyticsPageView extends StatelessWidget { - final AnalyticsPageState controller; - const AnalyticsPageView({ - super.key, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Padding( - padding: const EdgeInsetsGeometry.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LearningProgressIndicators( - selected: controller.selectedIndicator, - canSelect: - controller.selectedIndicator != ProgressIndicatorEnum.level, - ), - Expanded( - child: StreamBuilder( - stream: MatrixState - .pangeaController.getAnalytics.analyticsStream.stream, - builder: (context, _) { - if (controller.selectedIndicator == - ProgressIndicatorEnum.level) { - return const LevelDialogContent(); - } else if (controller.selectedIndicator == - ProgressIndicatorEnum.morphsUsed) { - return AnalyticsPopupWrapper( - constructZoom: controller.widget.constructZoom, - view: ConstructTypeEnum.morph, - ); - } else if (controller.selectedIndicator == - ProgressIndicatorEnum.wordsUsed) { - return AnalyticsPopupWrapper( - constructZoom: controller.widget.constructZoom, - view: ConstructTypeEnum.vocab, - ); - } else if (controller.selectedIndicator == - ProgressIndicatorEnum.activities) { - return const ActivityArchive(); - } - - return const SizedBox(); - }, - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/analytics_summary/learning_progress_indicators.dart b/lib/pangea/analytics_summary/learning_progress_indicators.dart index 6385e9cb6..3b2b7f14c 100644 --- a/lib/pangea/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/analytics_summary/learning_progress_indicators.dart @@ -123,7 +123,7 @@ class LearningProgressIndicatorsState selected: widget.selected == c.indicator, onPressed: () { context.go( - "/rooms/analytics?mode=${c.string}", + "/rooms/analytics/${c.string}", ); }, child: ProgressIndicatorBadge( @@ -138,7 +138,7 @@ class LearningProgressIndicatorsState ProgressIndicatorEnum.activities, onPressed: () { context.go( - "/rooms/analytics?mode=activities", + "/rooms/analytics/activities", ); }, child: Tooltip( @@ -232,7 +232,7 @@ class LearningProgressIndicatorsState child: GestureDetector( onTap: widget.canSelect ? () { - context.go("/rooms/analytics?mode=level"); + context.go("/rooms/analytics/level"); } : null, child: Row( diff --git a/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart b/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart index 3253676fc..3d007f768 100644 --- a/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart +++ b/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart @@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.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/morphs/get_grammar_copy.dart'; @@ -167,8 +168,7 @@ class ConstructNotificationOverlayState void _showDetails() { context.go( - "/rooms/analytics?mode=morph", - extra: widget.construct, + "/rooms/analytics/${ConstructTypeEnum.morph.string}/${widget.construct.string}", ); } diff --git a/lib/pangea/choreographer/widgets/igc/message_analytics_feedback.dart b/lib/pangea/choreographer/widgets/igc/message_analytics_feedback.dart index 569ad84ad..da458b4c9 100644 --- a/lib/pangea/choreographer/widgets/igc/message_analytics_feedback.dart +++ b/lib/pangea/choreographer/widgets/igc/message_analytics_feedback.dart @@ -102,11 +102,11 @@ class MessageAnalyticsFeedbackState extends State void _showAnalyticsDialog(ConstructTypeEnum? type) { switch (type) { case ConstructTypeEnum.morph: - context.go("/rooms/analytics?mode=morph"); + context.go("/rooms/analytics/${ConstructTypeEnum.morph.string}"); break; case ConstructTypeEnum.vocab: default: - context.go("/rooms/analytics?mode=vocab"); + context.go("/rooms/analytics/${ConstructTypeEnum.vocab.string}"); break; } } diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index d051a2dbc..58fe56c44 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -62,6 +62,7 @@ class PangeaController { PangeaController({required this.matrix, required this.matrixState}) { _setup(); _setLanguageSubscription(); + _initControllers(); randomint = Random().nextInt(2000); } @@ -74,12 +75,10 @@ class PangeaController { /// While many of these functions are asynchronous, they are not awaited here, /// because of order of execution does not matter, /// and running them at the same times speeds them up. - void initControllers() { - putAnalytics.initialize(); - getAnalytics.initialize(); + void _initControllers() { + _initAnalyticsControllers(); subscriptionController.initialize(); setPangeaPushRules(); - TtsController.setAvailableLanguages(); } @@ -181,8 +180,7 @@ class PangeaController { case LoginState.loggedOut: case LoginState.softLoggedOut: // Reset cached analytics data - putAnalytics.dispose(); - getAnalytics.dispose(); + _disposeAnalyticsControllers(); userController.clear(); _languageStream?.cancel(); _languageStream = null; @@ -190,8 +188,7 @@ class PangeaController { break; case LoginState.loggedIn: // Initialize analytics data - putAnalytics.initialize(); - getAnalytics.initialize(); + _initControllers(); _setLanguageSubscription(); userController.reinitialize().then((_) { @@ -213,13 +210,21 @@ class PangeaController { GoogleAnalytics.analyticsUserUpdate(userID); } - Future resetAnalytics() async { - putAnalytics.dispose(); - getAnalytics.dispose(); + Future _initAnalyticsControllers() async { putAnalytics.initialize(); await getAnalytics.initialize(); } + void _disposeAnalyticsControllers() { + putAnalytics.dispose(); + getAnalytics.dispose(); + } + + Future resetAnalytics() async { + _disposeAnalyticsControllers(); + await _initAnalyticsControllers(); + } + void _setLanguageSubscription() { _languageStream?.cancel(); _languageStream = userController.languageStream.stream.listen( @@ -228,6 +233,7 @@ class PangeaController { } Future setPangeaPushRules() async { + if (!matrixState.client.isLogged()) return; final List analyticsRooms = matrixState.client.rooms.where((room) => room.isAnalyticsRoom).toList(); diff --git a/lib/pangea/constructs/construct_identifier.dart b/lib/pangea/constructs/construct_identifier.dart index b3c023701..d6d0fd301 100644 --- a/lib/pangea/constructs/construct_identifier.dart +++ b/lib/pangea/constructs/construct_identifier.dart @@ -119,6 +119,28 @@ class ConstructIdentifier { return "$lemma:${type.string}-$category".toLowerCase(); } + static ConstructIdentifier? fromString(String s) { + final parts = s.split(':'); + if (parts.length != 2) return null; + final lemma = parts[0]; + final typeAndCategory = parts[1].split('-'); + if (typeAndCategory.length != 2) return null; + final typeString = typeAndCategory[0]; + final category = typeAndCategory[1]; + + final type = ConstructTypeEnum.values.firstWhereOrNull( + (e) => e.string == typeString, + ); + + if (type == null) return null; + + return ConstructIdentifier( + lemma: lemma, + type: type, + category: category, + ); + } + String get partialKey => "$lemma-${type.string}"; ConstructUses get constructUses => diff --git a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart index 579c65e1c..15529cad5 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart @@ -279,8 +279,7 @@ class MorphMeaningPopupState extends State { ConstructXpWidget( id: widget.cId, onTap: () => context.go( - "/rooms/analytics?mode=morph", - extra: widget.cId, + "/rooms/analytics/${ConstructTypeEnum.morph.string}/${widget.cId.string}", ), ), ], diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index f6c55df45..1a7ac9df4 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; @@ -107,8 +108,7 @@ class WordZoomWidget extends StatelessWidget { ConstructXpWidget( id: token.vocabConstructID, onTap: () => context.go( - "/rooms/analytics?mode=vocab", - extra: token.vocabConstructID, + "/rooms/analytics/${ConstructTypeEnum.vocab.string}/${token.vocabConstructID.string}", ), ), ], diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 9086b835a..0b5b538eb 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -7,7 +7,9 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/settings/settings.dart'; -import 'package:fluffychat/pangea/analytics_page/analytics_page_constants.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/analytics_page/analytics_page.dart'; +import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/find_your_people/find_your_people_constants.dart'; import 'package:fluffychat/widgets/navigation_rail.dart'; @@ -37,7 +39,7 @@ class TwoColumnLayout extends StatelessWidget { final spaceID = state.pathParameters['spaceid']; if (roomID == null && spaceID == null) { - showNavRail = !["newcourse"].any( + showNavRail = !["newcourse", ":construct"].any( (p) => state.fullPath?.contains(p) ?? false, ); } else if (roomID == null) { @@ -100,17 +102,6 @@ class _MainView extends StatelessWidget { required this.state, }); - String get _asset { - const defaultAsset = FindYourPeopleConstants.sideBearFileName; - if (state.fullPath == null || state.fullPath!.isEmpty) return defaultAsset; - - if (state.fullPath!.contains('analytics')) { - return AnalyticsPageConstants.dinoBotFileName; - } - - return defaultAsset; - } - @override Widget build(BuildContext context) { final path = state.fullPath; @@ -121,16 +112,35 @@ class _MainView extends StatelessWidget { ); } + if (path.contains("analytics")) { + ProgressIndicatorEnum indicator = ProgressIndicatorEnum.wordsUsed; + if (path.contains("analytics/level")) { + indicator = ProgressIndicatorEnum.level; + } else if (path.contains("analytics/${ConstructTypeEnum.morph.string}")) { + indicator = ProgressIndicatorEnum.morphsUsed; + } else if (path.contains("analytics/${ConstructTypeEnum.vocab.string}")) { + indicator = ProgressIndicatorEnum.wordsUsed; + } else if (path.contains("analytics/activities")) { + indicator = ProgressIndicatorEnum.activities; + } + + return AnalyticsPage( + indicator: indicator, + isSidebar: true, + ); + } + if (path.contains("settings")) { return Settings(key: state.pageKey); } - if (['communities', 'analytics'].any((p) => path.contains(p))) { + if (path.contains('communities')) { return Center( child: SizedBox( width: 250.0, child: CachedNetworkImage( - imageUrl: "${AppConfig.assetsBaseURL}/$_asset", + imageUrl: + "${AppConfig.assetsBaseURL}/${FindYourPeopleConstants.sideBearFileName}", errorWidget: (context, url, error) => const SizedBox(), placeholder: (context, url) => const Center( child: CircularProgressIndicator.adaptive(),