feat: added XP offset data to public profile to prevent user from ever going down a level (#1731)

pull/1605/head
ggurdin 9 months ago committed by GitHub
parent f4ab6f7458
commit f9e2b3d9c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -52,8 +52,9 @@ class ConstructListModel {
ConstructListModel({
required List<OneConstructUse> uses,
int offset = 0,
}) {
updateConstructs(uses);
updateConstructs(uses, offset);
}
int get totalLemmas => vocabLemmasList.length + grammarLemmasList.length;
@ -63,13 +64,13 @@ class ConstructListModel {
/// Given a list of new construct uses, update the map of construct
/// IDs to ConstructUses and re-sort the list of ConstructUses
void updateConstructs(List<OneConstructUse> newUses) {
void updateConstructs(List<OneConstructUse> newUses, int offset) {
try {
_updateUsesList(newUses);
_updateConstructMap(newUses);
_updateConstructList();
_updateCategoriesToUses();
_updateMetrics();
_updateMetrics(offset);
} catch (err, s) {
ErrorHandler.logError(
e: "Failed to update analytics: $err",
@ -148,7 +149,7 @@ class ConstructListModel {
}
}
void _updateMetrics() {
void _updateMetrics(int offset) {
vocabLemmasList = constructList(type: ConstructTypeEnum.vocab)
.map((e) => e.lemma)
.toSet()
@ -160,10 +161,11 @@ class ConstructListModel {
.toList();
prevXP = totalXP;
totalXP = _constructList.fold<int>(
0,
(total, construct) => total + construct.points,
);
totalXP = (_constructList.fold<int>(
0,
(total, construct) => total + construct.points,
)) +
offset;
if (totalXP < 0) {
totalXP = 0;

@ -90,10 +90,16 @@ class GetAnalyticsController extends BaseController {
await _pangeaController.putAnalytics.lastUpdatedCompleter.future;
await _getConstructs();
constructListModel.updateConstructs([
...(_getConstructsLocal() ?? []),
..._locallyCachedConstructs,
]);
final offset =
_pangeaController.userController.publicProfile?.xpOffset ?? 0;
constructListModel.updateConstructs(
[
...(_getConstructsLocal() ?? []),
..._locallyCachedConstructs,
],
offset,
);
} catch (err, s) {
ErrorHandler.logError(
e: err,
@ -125,12 +131,16 @@ class GetAnalyticsController extends BaseController {
) async {
if (analyticsUpdate.isLogout) return;
final oldLevel = constructListModel.level;
constructListModel.updateConstructs(analyticsUpdate.newConstructs);
final offset =
_pangeaController.userController.publicProfile?.xpOffset ?? 0;
constructListModel.updateConstructs(analyticsUpdate.newConstructs, offset);
if (analyticsUpdate.type == AnalyticsUpdateType.server) {
await _getConstructs(forceUpdate: true);
}
_updateAnalyticsStream(origin: analyticsUpdate.origin);
if (oldLevel < constructListModel.level) _onLevelUp();
if (oldLevel > constructListModel.level) await _onLevelDown(oldLevel);
_updateAnalyticsStream(origin: analyticsUpdate.origin);
}
void _updateAnalyticsStream({
@ -146,6 +156,16 @@ class GetAnalyticsController extends BaseController {
setState({'level_up': constructListModel.level});
}
Future<void> _onLevelDown(final prevLevel) async {
final offset =
_calculateMinXpForLevel(prevLevel) - constructListModel.totalXP;
await _pangeaController.userController.addXPOffset(offset);
constructListModel.updateConstructs(
[],
_pangeaController.userController.publicProfile!.xpOffset!,
);
}
/// A local cache of eventIds and construct uses for messages sent since the last update.
/// It's a map of eventIDs to a list of OneConstructUses. Not just a list of OneConstructUses
/// because, with practice activity constructs, we might need to add to the list for a given

@ -155,4 +155,5 @@ class ModelKey {
static const String analytics = "analytics";
static const String level = "level";
static const String xpOffset = "xp_offset";
}

@ -184,13 +184,23 @@ class UserController extends BaseController {
publicProfile!.setLevel(targetLanguage, level);
}
await client.setUserProfile(
client.userID!,
PangeaEventTypes.profileAnalytics,
publicProfile!.toJson(),
);
await _savePublicProfile();
}
Future<void> addXPOffset(int offset) async {
final targetLanguage = _pangeaController.languageController.userL2;
if (targetLanguage == null || publicProfile == null) return;
publicProfile!.addXPOffset(targetLanguage, offset);
await _savePublicProfile();
}
Future<void> _savePublicProfile() async => client.setUserProfile(
client.userID!,
PangeaEventTypes.profileAnalytics,
publicProfile!.toJson(),
);
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
bool needNewJWT(String token) => Jwt.isExpired(token);

@ -27,7 +27,9 @@ class PublicProfileModel {
final lang = PangeaLanguage.byLangCode(entry.key);
if (lang == null) continue;
final level = entry.value[ModelKey.level];
languageAnalytics[lang] = LanguageAnalyticsProfileEntry(level);
final xpOffset = entry.value[ModelKey.xpOffset] ?? 0;
languageAnalytics[lang] =
LanguageAnalyticsProfileEntry(level, xpOffset);
}
}
@ -47,7 +49,10 @@ class PublicProfileModel {
final analytics = {};
if (languageAnalytics != null && languageAnalytics!.isNotEmpty) {
for (final entry in languageAnalytics!.entries) {
analytics[entry.key.langCode] = {ModelKey.level: entry.value.level};
analytics[entry.key.langCode] = {
ModelKey.level: entry.value.level,
ModelKey.xpOffset: entry.value.xpOffset,
};
}
}
@ -61,15 +66,24 @@ class PublicProfileModel {
void setLevel(LanguageModel language, int level) {
languageAnalytics ??= {};
languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0);
languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0, 0);
languageAnalytics![language]!.level = level;
}
void addXPOffset(LanguageModel language, int xpOffset) {
languageAnalytics ??= {};
languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0, 0);
languageAnalytics![language]!.xpOffset += xpOffset;
}
int? get level => languageAnalytics?[targetLanguage]?.level;
int? get xpOffset => languageAnalytics?[targetLanguage]?.xpOffset;
}
class LanguageAnalyticsProfileEntry {
int level;
int xpOffset = 0;
LanguageAnalyticsProfileEntry(this.level);
LanguageAnalyticsProfileEntry(this.level, this.xpOffset);
}

Loading…
Cancel
Save