Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added XP offset data to public profile to prevent user from eve… #1731

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions lib/pangea/analytics_misc/construct_list_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand Down Expand Up @@ -148,7 +149,7 @@ class ConstructListModel {
}
}

void _updateMetrics() {
void _updateMetrics(int offset) {
vocabLemmasList = constructList(type: ConstructTypeEnum.vocab)
.map((e) => e.lemma)
.toSet()
Expand All @@ -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;
Expand Down
32 changes: 26 additions & 6 deletions lib/pangea/analytics_misc/get_analytics_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/pangea/common/constants/model_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,5 @@ class ModelKey {

static const String analytics = "analytics";
static const String level = "level";
static const String xpOffset = "xp_offset";
}
20 changes: 15 additions & 5 deletions lib/pangea/user/controllers/user_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 18 additions & 4 deletions lib/pangea/user/models/profile_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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,
};
}
}

Expand All @@ -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