feat: added XP offset data to public profile to prevent user from ever going down a level (#1731)
This commit is contained in:
parent
f4ab6f7458
commit
f9e2b3d9c0
5 changed files with 70 additions and 23 deletions
|
|
@ -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…
Add table
Reference in a new issue