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

@ -90,10 +90,16 @@ class GetAnalyticsController extends BaseController {
await _pangeaController.putAnalytics.lastUpdatedCompleter.future; await _pangeaController.putAnalytics.lastUpdatedCompleter.future;
await _getConstructs(); await _getConstructs();
constructListModel.updateConstructs([
final offset =
_pangeaController.userController.publicProfile?.xpOffset ?? 0;
constructListModel.updateConstructs(
[
...(_getConstructsLocal() ?? []), ...(_getConstructsLocal() ?? []),
..._locallyCachedConstructs, ..._locallyCachedConstructs,
]); ],
offset,
);
} catch (err, s) { } catch (err, s) {
ErrorHandler.logError( ErrorHandler.logError(
e: err, e: err,
@ -125,12 +131,16 @@ class GetAnalyticsController extends BaseController {
) async { ) async {
if (analyticsUpdate.isLogout) return; if (analyticsUpdate.isLogout) return;
final oldLevel = constructListModel.level; 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) { if (analyticsUpdate.type == AnalyticsUpdateType.server) {
await _getConstructs(forceUpdate: true); await _getConstructs(forceUpdate: true);
} }
_updateAnalyticsStream(origin: analyticsUpdate.origin);
if (oldLevel < constructListModel.level) _onLevelUp(); if (oldLevel < constructListModel.level) _onLevelUp();
if (oldLevel > constructListModel.level) await _onLevelDown(oldLevel);
_updateAnalyticsStream(origin: analyticsUpdate.origin);
} }
void _updateAnalyticsStream({ void _updateAnalyticsStream({
@ -146,6 +156,16 @@ class GetAnalyticsController extends BaseController {
setState({'level_up': constructListModel.level}); 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. /// 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 /// 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 /// 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 analytics = "analytics";
static const String level = "level"; static const String level = "level";
static const String xpOffset = "xp_offset";
} }

@ -184,12 +184,22 @@ class UserController extends BaseController {
publicProfile!.setLevel(targetLanguage, level); publicProfile!.setLevel(targetLanguage, level);
} }
await client.setUserProfile( 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!, client.userID!,
PangeaEventTypes.profileAnalytics, PangeaEventTypes.profileAnalytics,
publicProfile!.toJson(), publicProfile!.toJson(),
); );
}
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
bool needNewJWT(String token) => Jwt.isExpired(token); bool needNewJWT(String token) => Jwt.isExpired(token);

@ -27,7 +27,9 @@ class PublicProfileModel {
final lang = PangeaLanguage.byLangCode(entry.key); final lang = PangeaLanguage.byLangCode(entry.key);
if (lang == null) continue; if (lang == null) continue;
final level = entry.value[ModelKey.level]; 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 = {}; final analytics = {};
if (languageAnalytics != null && languageAnalytics!.isNotEmpty) { if (languageAnalytics != null && languageAnalytics!.isNotEmpty) {
for (final entry in languageAnalytics!.entries) { 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) { void setLevel(LanguageModel language, int level) {
languageAnalytics ??= {}; languageAnalytics ??= {};
languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0); languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0, 0);
languageAnalytics![language]!.level = level; 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 level => languageAnalytics?[targetLanguage]?.level;
int? get xpOffset => languageAnalytics?[targetLanguage]?.xpOffset;
} }
class LanguageAnalyticsProfileEntry { class LanguageAnalyticsProfileEntry {
int level; int level;
int xpOffset = 0;
LanguageAnalyticsProfileEntry(this.level); LanguageAnalyticsProfileEntry(this.level, this.xpOffset);
} }

Loading…
Cancel
Save