chore: move construct summary loading into widget
This commit is contained in:
parent
055980005a
commit
540c7b01f0
4 changed files with 188 additions and 214 deletions
|
|
@ -474,100 +474,87 @@ class GetAnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<ConstructSummary?> generateLevelUpAnalytics(
|
||||
Future<ConstructSummary> generateLevelUpAnalytics(
|
||||
final int lowerLevel,
|
||||
final int upperLevel,
|
||||
) async {
|
||||
// generate level up analytics as a construct summary
|
||||
ConstructSummary summary;
|
||||
try {
|
||||
final int maxXP = constructListModel.calculateXpWithLevel(upperLevel);
|
||||
final int minXP = constructListModel.calculateXpWithLevel(lowerLevel);
|
||||
int diffXP = maxXP - minXP;
|
||||
if (diffXP < 0) diffXP = 0;
|
||||
final int maxXP = constructListModel.calculateXpWithLevel(upperLevel);
|
||||
final int minXP = constructListModel.calculateXpWithLevel(lowerLevel);
|
||||
int diffXP = maxXP - minXP;
|
||||
if (diffXP < 0) diffXP = 0;
|
||||
|
||||
// compute construct use of current level
|
||||
final List<OneConstructUse> constructUseOfCurrentLevel = [];
|
||||
int score = 0;
|
||||
for (final use in constructListModel.uses) {
|
||||
constructUseOfCurrentLevel.add(use);
|
||||
score += use.xp;
|
||||
if (score >= diffXP) break;
|
||||
}
|
||||
// compute construct use of current level
|
||||
final List<OneConstructUse> constructUseOfCurrentLevel = [];
|
||||
int score = 0;
|
||||
for (final use in constructListModel.uses) {
|
||||
constructUseOfCurrentLevel.add(use);
|
||||
score += use.xp;
|
||||
if (score >= diffXP) break;
|
||||
}
|
||||
|
||||
// extract construct use message bodies for analytics
|
||||
final Map<String, Set<String>> useEventIds = {};
|
||||
for (final use in constructUseOfCurrentLevel) {
|
||||
if (use.metadata.roomId == null) continue;
|
||||
if (use.metadata.eventId == null) continue;
|
||||
useEventIds[use.metadata.roomId!] ??= {};
|
||||
useEventIds[use.metadata.roomId!]!.add(use.metadata.eventId!);
|
||||
}
|
||||
// extract construct use message bodies for analytics
|
||||
final Map<String, Set<String>> useEventIds = {};
|
||||
for (final use in constructUseOfCurrentLevel) {
|
||||
if (use.metadata.roomId == null) continue;
|
||||
if (use.metadata.eventId == null) continue;
|
||||
useEventIds[use.metadata.roomId!] ??= {};
|
||||
useEventIds[use.metadata.roomId!]!.add(use.metadata.eventId!);
|
||||
}
|
||||
|
||||
final List<String?> constructUseMessageContentBodies = [];
|
||||
for (final entry in useEventIds.entries) {
|
||||
final String roomId = entry.key;
|
||||
final room = _client.getRoomById(roomId);
|
||||
if (room == null) continue;
|
||||
final List<String?> messageBodies = [];
|
||||
for (final eventId in entry.value) {
|
||||
try {
|
||||
final Event? event = await room.getEventById(eventId);
|
||||
if (event?.content["body"] is! String) continue;
|
||||
final String body = event?.content["body"] as String;
|
||||
if (body.isEmpty) continue;
|
||||
messageBodies.add(body);
|
||||
} catch (e, s) {
|
||||
debugPrint("Error getting event by ID: $e");
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': roomId,
|
||||
'eventId': eventId,
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
final List<String?> constructUseMessageContentBodies = [];
|
||||
for (final entry in useEventIds.entries) {
|
||||
final String roomId = entry.key;
|
||||
final room = _client.getRoomById(roomId);
|
||||
if (room == null) continue;
|
||||
final List<String?> messageBodies = [];
|
||||
for (final eventId in entry.value) {
|
||||
try {
|
||||
final Event? event = await room.getEventById(eventId);
|
||||
if (event?.content["body"] is! String) continue;
|
||||
final String body = event?.content["body"] as String;
|
||||
if (body.isEmpty) continue;
|
||||
messageBodies.add(body);
|
||||
} catch (e, s) {
|
||||
debugPrint("Error getting event by ID: $e");
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': roomId,
|
||||
'eventId': eventId,
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
constructUseMessageContentBodies.addAll(messageBodies);
|
||||
}
|
||||
|
||||
final request = ConstructSummaryRequest(
|
||||
constructs: constructUseOfCurrentLevel,
|
||||
constructUseMessageContentBodies: constructUseMessageContentBodies,
|
||||
language: _l1!.langCodeShort,
|
||||
upperLevel: upperLevel,
|
||||
lowerLevel: lowerLevel,
|
||||
);
|
||||
|
||||
final response = await ConstructRepo.generateConstructSummary(request);
|
||||
summary = response.summary;
|
||||
summary.levelVocabConstructs = MatrixState
|
||||
.pangeaController.getAnalytics.constructListModel.vocabLemmas;
|
||||
summary.levelGrammarConstructs = MatrixState
|
||||
.pangeaController.getAnalytics.constructListModel.grammarLemmas;
|
||||
} catch (e) {
|
||||
debugPrint("Error generating level up analytics: $e");
|
||||
ErrorHandler.logError(e: e, data: {'e': e});
|
||||
return null;
|
||||
constructUseMessageContentBodies.addAll(messageBodies);
|
||||
}
|
||||
|
||||
try {
|
||||
final Room? analyticsRoom = await _client.getMyAnalyticsRoom(_l2!);
|
||||
if (analyticsRoom == null) {
|
||||
throw "Analytics room not found for user";
|
||||
}
|
||||
final request = ConstructSummaryRequest(
|
||||
constructs: constructUseOfCurrentLevel,
|
||||
constructUseMessageContentBodies: constructUseMessageContentBodies,
|
||||
language: _l1!.langCodeShort,
|
||||
upperLevel: upperLevel,
|
||||
lowerLevel: lowerLevel,
|
||||
);
|
||||
|
||||
// don't await this, just return the original response
|
||||
_saveConstructSummaryResponseToStateEvent(
|
||||
summary,
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugPrint("Error saving construct summary room: $e");
|
||||
ErrorHandler.logError(e: e, s: s, data: {'e': e});
|
||||
final response = await ConstructRepo.generateConstructSummary(request);
|
||||
final ConstructSummary summary = response.summary;
|
||||
summary.levelVocabConstructs = MatrixState
|
||||
.pangeaController.getAnalytics.constructListModel.vocabLemmas;
|
||||
summary.levelGrammarConstructs = MatrixState
|
||||
.pangeaController.getAnalytics.constructListModel.grammarLemmas;
|
||||
|
||||
final Room? analyticsRoom = await _client.getMyAnalyticsRoom(_l2!);
|
||||
if (analyticsRoom == null) {
|
||||
throw "Analytics room not found for user";
|
||||
}
|
||||
|
||||
// don't await this, just return the original response
|
||||
_saveConstructSummaryResponseToStateEvent(
|
||||
summary,
|
||||
);
|
||||
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_manager.dart'
|
|||
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_popup.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LevelUpConstants {
|
||||
|
|
@ -95,10 +96,15 @@ class LevelUpBannerState extends State<LevelUpBanner>
|
|||
|
||||
bool _showedDetails = false;
|
||||
|
||||
final Completer<ConstructSummary> _constructSummaryCompleter =
|
||||
Completer<ConstructSummary>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_loadConstructSummary();
|
||||
|
||||
LevelUpManager.instance.preloadAnalytics(
|
||||
context,
|
||||
widget.level,
|
||||
|
|
@ -149,10 +155,23 @@ class LevelUpBannerState extends State<LevelUpBanner>
|
|||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => const LevelUpPopup(),
|
||||
builder: (context) => LevelUpPopup(
|
||||
constructSummaryCompleter: _constructSummaryCompleter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadConstructSummary() async {
|
||||
try {
|
||||
final summary = MatrixState.pangeaController.getAnalytics
|
||||
.generateLevelUpAnalytics(widget.prevLevel, widget.level);
|
||||
_constructSummaryCompleter.complete(summary);
|
||||
} catch (e) {
|
||||
debugPrint("Error generating level up analytics: $e");
|
||||
_constructSummaryCompleter.completeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
|
|
|||
|
|
@ -22,13 +22,8 @@ class LevelUpManager {
|
|||
int prevVocab = 0;
|
||||
int nextVocab = 0;
|
||||
|
||||
String? userL2Code;
|
||||
|
||||
ConstructSummary? constructSummary;
|
||||
|
||||
bool hasSeenPopup = false;
|
||||
bool shouldAutoPopup = false;
|
||||
String? error;
|
||||
|
||||
Future<void> preloadAnalytics(
|
||||
BuildContext context,
|
||||
|
|
@ -46,12 +41,6 @@ class LevelUpManager {
|
|||
nextVocab = MatrixState
|
||||
.pangeaController.getAnalytics.constructListModel.vocabLemmas;
|
||||
|
||||
userL2Code = MatrixState.pangeaController.languageController
|
||||
.activeL2Code()
|
||||
?.toUpperCase();
|
||||
|
||||
getConstructFromLevelUp();
|
||||
|
||||
final LanguageModel? l2 =
|
||||
MatrixState.pangeaController.languageController.userL2;
|
||||
final Room? analyticsRoom =
|
||||
|
|
@ -91,28 +80,6 @@ class LevelUpManager {
|
|||
}
|
||||
}
|
||||
|
||||
//for testing, just fetch last level up from saved analytics
|
||||
void getConstructFromButton() {
|
||||
constructSummary = MatrixState.pangeaController.getAnalytics
|
||||
.getConstructSummaryFromStateEvent();
|
||||
debugPrint(
|
||||
"Last saved construct summary from analytics controller function: ${constructSummary?.toJson()}",
|
||||
);
|
||||
}
|
||||
|
||||
//for getting real level up data when leveled up
|
||||
void getConstructFromLevelUp() async {
|
||||
try {
|
||||
constructSummary = await MatrixState.pangeaController.getAnalytics
|
||||
.generateLevelUpAnalytics(
|
||||
prevLevel,
|
||||
level,
|
||||
);
|
||||
} catch (e) {
|
||||
error = e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
void markPopupSeen() {
|
||||
hasSeenPopup = true;
|
||||
shouldAutoPopup = false;
|
||||
|
|
@ -127,7 +94,5 @@ class LevelUpManager {
|
|||
nextGrammar = 0;
|
||||
prevVocab = 0;
|
||||
nextVocab = 0;
|
||||
constructSummary = null;
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,15 @@ import 'package:fluffychat/pangea/analytics_summary/progress_bar/level_bar.dart'
|
|||
import 'package:fluffychat/pangea/analytics_summary/progress_bar/progress_bar_details.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
||||
class LevelUpPopup extends StatelessWidget {
|
||||
final Completer<ConstructSummary> constructSummaryCompleter;
|
||||
const LevelUpPopup({
|
||||
required this.constructSummaryCompleter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -50,6 +53,7 @@ class LevelUpPopup extends StatelessWidget {
|
|||
body: LevelUpPopupContent(
|
||||
prevLevel: LevelUpManager.instance.prevLevel,
|
||||
level: LevelUpManager.instance.level,
|
||||
constructSummaryCompleter: constructSummaryCompleter,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -59,11 +63,13 @@ class LevelUpPopup extends StatelessWidget {
|
|||
class LevelUpPopupContent extends StatefulWidget {
|
||||
final int prevLevel;
|
||||
final int level;
|
||||
final Completer<ConstructSummary> constructSummaryCompleter;
|
||||
|
||||
const LevelUpPopupContent({
|
||||
super.key,
|
||||
required this.prevLevel,
|
||||
required this.level,
|
||||
required this.constructSummaryCompleter,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -72,55 +78,39 @@ class LevelUpPopupContent extends StatefulWidget {
|
|||
|
||||
class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late int _endGrammar;
|
||||
late int _endVocab;
|
||||
final int _startGrammar = LevelUpManager.instance.prevGrammar;
|
||||
final int _startVocab = LevelUpManager.instance.prevVocab;
|
||||
Timer? _summaryPollTimer;
|
||||
final String? _error = LevelUpManager.instance.error;
|
||||
String language = LevelUpManager.instance.userL2Code ?? "N/A";
|
||||
|
||||
late final AnimationController _controller;
|
||||
late final ConfettiController _confettiController;
|
||||
bool _hasBlastedConfetti = false;
|
||||
final Duration _animationDuration = const Duration(seconds: 5);
|
||||
|
||||
Uri? avatarUrl;
|
||||
late final Future<Profile> profile;
|
||||
|
||||
int displayedLevel = -1;
|
||||
late ConstructSummary? _constructSummary;
|
||||
Uri? avatarUrl;
|
||||
bool _hasBlastedConfetti = false;
|
||||
|
||||
String language = MatrixState.pangeaController.languageController
|
||||
.activeL2Code()
|
||||
?.toUpperCase() ??
|
||||
LanguageKeys.unknownLanguage;
|
||||
|
||||
ConstructSummary? _constructSummary;
|
||||
Object? _error;
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadConstructSummary();
|
||||
LevelUpManager.instance.markPopupSeen();
|
||||
displayedLevel = widget.prevLevel;
|
||||
_confettiController =
|
||||
ConfettiController(duration: const Duration(seconds: 1));
|
||||
_endGrammar = LevelUpManager.instance.nextGrammar;
|
||||
_endVocab = LevelUpManager.instance.nextVocab;
|
||||
_constructSummary = LevelUpManager.instance.constructSummary;
|
||||
// Poll for constructSummary if not available
|
||||
if (_constructSummary == null) {
|
||||
_summaryPollTimer =
|
||||
Timer.periodic(const Duration(milliseconds: 300), (timer) {
|
||||
final summary = LevelUpManager.instance.constructSummary;
|
||||
if (summary != null) {
|
||||
setState(() {
|
||||
_constructSummary = summary;
|
||||
});
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
client.fetchOwnProfile().then((profile) {
|
||||
setState(() {
|
||||
avatarUrl = profile.avatarUrl;
|
||||
});
|
||||
setState(() => avatarUrl = profile.avatarUrl);
|
||||
});
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: _animationDuration,
|
||||
duration: const Duration(seconds: 5),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
|
|
@ -135,7 +125,6 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
|
||||
_controller.addListener(() {
|
||||
if (_controller.value >= 0.5 && !_hasBlastedConfetti) {
|
||||
//_confettiController.play();
|
||||
_hasBlastedConfetti = true;
|
||||
rainConfetti(context);
|
||||
}
|
||||
|
|
@ -146,7 +135,6 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_summaryPollTimer?.cancel();
|
||||
_controller.dispose();
|
||||
_confettiController.dispose();
|
||||
LevelUpManager.instance.reset();
|
||||
|
|
@ -154,6 +142,22 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
int get _startGrammar => LevelUpManager.instance.prevGrammar;
|
||||
int get _startVocab => LevelUpManager.instance.prevVocab;
|
||||
|
||||
get _endGrammar => LevelUpManager.instance.nextGrammar;
|
||||
get _endVocab => LevelUpManager.instance.nextVocab;
|
||||
|
||||
Future<void> _loadConstructSummary() async {
|
||||
try {
|
||||
_constructSummary = await widget.constructSummaryCompleter.future;
|
||||
} catch (e) {
|
||||
_error = e;
|
||||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
int _getSkillXP(LearningSkillsEnum skill) {
|
||||
if (_constructSummary == null) return 0;
|
||||
return switch (skill) {
|
||||
|
|
@ -368,52 +372,60 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Skills section
|
||||
AnimatedBuilder(
|
||||
animation: skillsOpacity,
|
||||
builder: (_, __) => Opacity(
|
||||
opacity: skillsOpacity.value,
|
||||
child: _error == null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildSkillsTable(context),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Text(
|
||||
_constructSummary?.textSummary ??
|
||||
L10n.of(context).loadingPleaseWait,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}",
|
||||
width: 400,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
// if error getting construct summary
|
||||
: Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: L10n.of(context).oopsSomethingWentWrong,
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_loading)
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
width: 50,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
color: AppConfig.goldLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
L10n.of(context).oopsSomethingWentWrong,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_constructSummary != null)
|
||||
// Skills section
|
||||
AnimatedBuilder(
|
||||
animation: skillsOpacity,
|
||||
builder: (_, __) => Opacity(
|
||||
opacity: skillsOpacity.value,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildSkillsTable(context),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Text(
|
||||
_constructSummary!.textSummary,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}",
|
||||
width: 400,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Share button, currently no functionality
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
|
|
@ -500,24 +512,15 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
(_constructSummary != null)
|
||||
? Text(
|
||||
'+ ${_getSkillXP(skill)} XP',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppConfig.gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 6.0,
|
||||
width: 6.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
color: AppConfig.gold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'+ ${_getSkillXP(skill)} XP',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppConfig.gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue