fix: add new analytics update type for initial data (from local and s… (#4484)

* fix: add new analytics update type for initial data (from local and server), listen for init updates in analytics page to rebuild on language change

* fix: URL encode construct IDs during construct analytics navigation

* chore: add tooltip to level analytics page, update divider in construct analytics details page
This commit is contained in:
ggurdin 2025-10-21 16:04:09 -04:00 committed by GitHub
parent 29c2602adc
commit f53b010105
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 88 additions and 93 deletions

View file

@ -5310,5 +5310,6 @@
"activityAnalyticsTooltipBody": "These are your saved activities for review and practice.",
"numSavedActivities": "Number of saved activities",
"saveActivityTitle": "Save activity",
"saveActivityDesc": "Good job! Save this activity for later review and practice"
"saveActivityDesc": "Good job! Save this activity for later review and practice",
"levelInfoTooltip": "Here you can see all the points youve earned and how!"
}

View file

@ -1,11 +1,7 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_usage_dots.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_use_example_messages.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
@ -43,14 +39,9 @@ class AnalyticsDetailsViewContent extends StatelessWidget {
subtitle,
const SizedBox(height: 16.0),
headerContent,
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.popupDividerFileName}",
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Divider(),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,

View file

@ -164,7 +164,7 @@ class MorphFeatureBox extends StatelessWidget {
morphTag: morphTag,
constructAnalytics: analytics,
onTap: () => context.go(
"/rooms/analytics/${id.type.string}/${id.string}",
"/rooms/analytics/${id.type.string}/${Uri.encodeComponent(id.string)}",
),
);
},

View file

@ -175,7 +175,7 @@ class VocabAnalyticsListView extends StatelessWidget {
final vocabItem = _filteredVocab[index];
return VocabAnalyticsListTile(
onTap: () => context.go(
"/rooms/analytics/${vocabItem.id.type.string}/${vocabItem.id.string}",
"/rooms/analytics/${vocabItem.id.type.string}/${Uri.encodeComponent(vocabItem.id.string)}",
),
constructUse: vocabItem,
emoji: vocabItem.id.userSetEmoji.firstOrNull,

View file

@ -12,7 +12,6 @@ class AnalyticsConstants {
static const String emojiForFlower = "🌸";
static const levelUpAudioFileName = "LevelUp_chime.mp3";
static const levelUpImageFileName = "LvL_Up_Full_Banner.png";
static const popupDividerFileName = "divider.png";
static const vocabIconFileName = "Vocabulary_icon.png";
static const morphIconFileName = "grammar_icon.png";
}

View file

@ -110,7 +110,7 @@ class GetAnalyticsController extends BaseController {
);
} finally {
_updateAnalyticsStream(
type: AnalyticsUpdateType.local,
type: AnalyticsUpdateType.init,
points: 0,
newConstructs: [],
);

View file

@ -15,7 +15,7 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
enum AnalyticsUpdateType { server, local, activities }
enum AnalyticsUpdateType { server, local, activities, init }
/// handles the processing of analytics for
/// 1) messages sent by the user and

View file

@ -7,6 +7,7 @@ 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_misc/put_analytics_controller.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';
@ -15,7 +16,7 @@ import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dar
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/widgets/matrix.dart';
class AnalyticsPage extends StatefulWidget {
class AnalyticsPage extends StatelessWidget {
final ProgressIndicatorEnum? indicator;
final ConstructIdentifier? construct;
final bool isSidebar;
@ -27,84 +28,73 @@ class AnalyticsPage extends StatefulWidget {
this.isSidebar = false,
});
@override
State<AnalyticsPage> createState() => _AnalyticsPageState();
}
class _AnalyticsPageState extends State<AnalyticsPage> {
@override
void initState() {
super.initState();
final analytics = MatrixState.pangeaController.getAnalytics;
// Check if getAnalytics is initialized, if not wait for the first stream entry
if (!analytics.initCompleter.isCompleted) {
analytics.analyticsStream.stream.first.then((_) {
if (mounted) {
setState(() {});
}
});
}
}
@override
Widget build(BuildContext context) {
final analyticsRoomId = GoRouterState.of(context).pathParameters['roomid'];
return Scaffold(
appBar: widget.construct != null ? AppBar() : null,
appBar: construct != null ? AppBar() : null,
body: SafeArea(
child: Padding(
padding: const EdgeInsetsGeometry.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.isSidebar ||
(!FluffyThemes.isColumnMode(context) &&
widget.construct == null))
LearningProgressIndicators(
selected: widget.indicator,
canSelect: widget.indicator != ProgressIndicatorEnum.level,
),
Expanded(
child: () {
if (widget.indicator == ProgressIndicatorEnum.level) {
return const LevelDialogContent();
} else if (widget.indicator ==
ProgressIndicatorEnum.morphsUsed) {
return ConstructAnalyticsView(
construct: widget.construct,
view: ConstructTypeEnum.morph,
);
} else if (widget.indicator ==
ProgressIndicatorEnum.wordsUsed) {
return ConstructAnalyticsView(
construct: widget.construct,
view: ConstructTypeEnum.vocab,
);
} else if (widget.indicator ==
ProgressIndicatorEnum.activities) {
return ActivityArchive(
selectedRoomId: analyticsRoomId,
);
}
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(),
),
),
),
);
}(),
),
],
child: StreamBuilder(
stream: MatrixState
.pangeaController.getAnalytics.analyticsStream.stream
.where(
(u) => u.type == AnalyticsUpdateType.init,
),
builder: (context, snapshot) {
return 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: () {
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 ActivityArchive(
selectedRoomId: analyticsRoomId,
);
}
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(),
),
),
),
);
}(),
),
],
),
);
},
),
),
);

View file

@ -7,6 +7,8 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -72,8 +74,16 @@ class LevelDialogContent extends StatelessWidget {
children: [
Expanded(
child: ListView.builder(
itemCount: uses.length,
itemCount: uses.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.levelAnalytics,
padding: EdgeInsets.symmetric(vertical: 16.0),
);
}
index--;
final use = uses[index];
String lemmaCopy = use.lemma;
if (use.constructType == ConstructTypeEnum.morph) {

View file

@ -168,7 +168,7 @@ class ConstructNotificationOverlayState
void _showDetails() {
context.go(
"/rooms/analytics/${ConstructTypeEnum.morph.string}/${widget.construct.string}",
"/rooms/analytics/${ConstructTypeEnum.morph.string}/${Uri.encodeComponent(widget.construct.string)}",
);
}

View file

@ -22,6 +22,7 @@ enum InstructionsEnum {
analyticsVocabList,
morphAnalyticsList,
activityAnalyticsList,
levelAnalytics,
readingAssistanceOverview,
emptyChatWarning,
activityStatsMenu,
@ -51,6 +52,7 @@ extension InstructionsEnumExtension on InstructionsEnum {
case InstructionsEnum.activityStatsMenu:
case InstructionsEnum.chatListTooltip:
case InstructionsEnum.activityAnalyticsList:
case InstructionsEnum.levelAnalytics:
ErrorHandler.logError(
e: Exception("No title for this instruction"),
m: 'InstructionsEnumExtension.title',
@ -105,6 +107,8 @@ extension InstructionsEnumExtension on InstructionsEnum {
return l10n.activityStatsButtonInstruction;
case InstructionsEnum.chatListTooltip:
return l10n.chatListTooltip;
case InstructionsEnum.levelAnalytics:
return l10n.levelInfoTooltip;
}
}