fix: add missing dependency (#5707)
This commit is contained in:
parent
20b1619126
commit
c2472bd2a4
5 changed files with 176 additions and 73 deletions
|
|
@ -153,9 +153,7 @@ extension BotClientExtension on Client {
|
|||
onError: (e, s) => ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'userSettings': userSettings.toJson(),
|
||||
},
|
||||
data: {'userSettings': userSettings.toJson()},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class _PracticeSelectionCacheEntry {
|
||||
final PracticeSelection selection;
|
||||
final DateTime timestamp;
|
||||
|
||||
_PracticeSelectionCacheEntry({required this.selection, required this.timestamp});
|
||||
_PracticeSelectionCacheEntry({
|
||||
required this.selection,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
bool get isExpired => DateTime.now().difference(timestamp).inDays > 1;
|
||||
|
||||
Map<String, dynamic> toJson() => {'selection': selection.toJson(), 'timestamp': timestamp.toIso8601String()};
|
||||
Map<String, dynamic> toJson() => {
|
||||
'selection': selection.toJson(),
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
};
|
||||
|
||||
factory _PracticeSelectionCacheEntry.fromJson(Map<String, dynamic> json) {
|
||||
return _PracticeSelectionCacheEntry(
|
||||
|
|
@ -27,7 +34,11 @@ class _PracticeSelectionCacheEntry {
|
|||
class PracticeSelectionRepo {
|
||||
static final GetStorage _storage = GetStorage('practice_selection_cache');
|
||||
|
||||
static Future<PracticeSelection?> get(String eventId, String messageLanguage, List<PangeaToken> tokens) async {
|
||||
static Future<PracticeSelection?> get(
|
||||
String eventId,
|
||||
String messageLanguage,
|
||||
List<PangeaToken> tokens,
|
||||
) async {
|
||||
final userL2 = MatrixState.pangeaController.userController.userL2;
|
||||
if (userL2?.langCodeShort != messageLanguage.split("-").first) {
|
||||
return null;
|
||||
|
|
@ -42,8 +53,12 @@ class PracticeSelectionRepo {
|
|||
return newEntry;
|
||||
}
|
||||
|
||||
static Future<PracticeSelection> _fetch({required List<PangeaToken> tokens, required String langCode}) async {
|
||||
if (langCode.split("-")[0] != MatrixState.pangeaController.userController.userL2?.langCodeShort) {
|
||||
static Future<PracticeSelection> _fetch({
|
||||
required List<PangeaToken> tokens,
|
||||
required String langCode,
|
||||
}) async {
|
||||
if (langCode.split("-")[0] !=
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort) {
|
||||
return PracticeSelection({});
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +66,10 @@ class PracticeSelectionRepo {
|
|||
if (eligibleTokens.isEmpty) {
|
||||
return PracticeSelection({});
|
||||
}
|
||||
final queue = await _fillActivityQueue(eligibleTokens, langCode.split('-')[0]);
|
||||
final queue = await _fillActivityQueue(
|
||||
eligibleTokens,
|
||||
langCode.split('-')[0],
|
||||
);
|
||||
final selection = PracticeSelection(queue);
|
||||
return selection;
|
||||
}
|
||||
|
|
@ -60,7 +78,9 @@ class PracticeSelectionRepo {
|
|||
try {
|
||||
final keys = List.from(_storage.getKeys());
|
||||
for (final String key in keys) {
|
||||
final cacheEntry = _PracticeSelectionCacheEntry.fromJson(_storage.read(key));
|
||||
final cacheEntry = _PracticeSelectionCacheEntry.fromJson(
|
||||
_storage.read(key),
|
||||
);
|
||||
if (cacheEntry.isExpired) {
|
||||
_storage.remove(key);
|
||||
}
|
||||
|
|
@ -74,7 +94,9 @@ class PracticeSelectionRepo {
|
|||
if (entry == null) return null;
|
||||
|
||||
try {
|
||||
return _PracticeSelectionCacheEntry.fromJson(_storage.read(eventId)).selection;
|
||||
return _PracticeSelectionCacheEntry.fromJson(
|
||||
_storage.read(eventId),
|
||||
).selection;
|
||||
} catch (e) {
|
||||
_storage.remove(eventId);
|
||||
return null;
|
||||
|
|
@ -82,7 +104,10 @@ class PracticeSelectionRepo {
|
|||
}
|
||||
|
||||
static void _setCached(String eventId, PracticeSelection entry) {
|
||||
final cachedEntry = _PracticeSelectionCacheEntry(selection: entry, timestamp: DateTime.now());
|
||||
final cachedEntry = _PracticeSelectionCacheEntry(
|
||||
selection: entry,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
_storage.write(eventId, cachedEntry.toJson());
|
||||
}
|
||||
|
||||
|
|
@ -97,9 +122,19 @@ class PracticeSelectionRepo {
|
|||
return queue;
|
||||
}
|
||||
|
||||
static int _sortTokens(PangeaToken a, PangeaToken b, int aScore, int bScore) => bScore.compareTo(aScore);
|
||||
static int _sortTokens(
|
||||
PangeaToken a,
|
||||
PangeaToken b,
|
||||
int aScore,
|
||||
int bScore,
|
||||
) => bScore.compareTo(aScore);
|
||||
|
||||
static int _sortMorphTargets(PracticeTarget a, PracticeTarget b, int aScore, int bScore) => bScore.compareTo(aScore);
|
||||
static int _sortMorphTargets(
|
||||
PracticeTarget a,
|
||||
PracticeTarget b,
|
||||
int aScore,
|
||||
int bScore,
|
||||
) => bScore.compareTo(aScore);
|
||||
|
||||
static List<PracticeTarget> _tokenToMorphTargets(PangeaToken t) {
|
||||
return t.morphsBasicallyEligibleForPracticeByPriority
|
||||
|
|
@ -136,7 +171,11 @@ class PracticeSelectionRepo {
|
|||
return [];
|
||||
}
|
||||
|
||||
final scores = await _fetchPriorityScores(practiceTokens, activityType, language);
|
||||
final scores = await _fetchPriorityScores(
|
||||
practiceTokens,
|
||||
activityType,
|
||||
language,
|
||||
);
|
||||
|
||||
practiceTokens.sort((a, b) => _sortTokens(a, b, scores[a]!, scores[b]!));
|
||||
practiceTokens = practiceTokens.take(8).toList();
|
||||
|
|
@ -150,11 +189,25 @@ class PracticeSelectionRepo {
|
|||
];
|
||||
}
|
||||
|
||||
static Future<List<PracticeTarget>> _buildMorphActivity(List<PangeaToken> tokens, String language) async {
|
||||
static Future<List<PracticeTarget>> _buildMorphActivity(
|
||||
List<PangeaToken> tokens,
|
||||
String language,
|
||||
) async {
|
||||
final List<PangeaToken> practiceTokens = List<PangeaToken>.from(tokens);
|
||||
final candidates = practiceTokens.expand(_tokenToMorphTargets).toList();
|
||||
final scores = await _fetchPriorityScores(practiceTokens, ActivityTypeEnum.morphId, language);
|
||||
candidates.sort((a, b) => _sortMorphTargets(a, b, scores[a.tokens.first]!, scores[b.tokens.first]!));
|
||||
final scores = await _fetchPriorityScores(
|
||||
practiceTokens,
|
||||
ActivityTypeEnum.morphId,
|
||||
language,
|
||||
);
|
||||
candidates.sort(
|
||||
(a, b) => _sortMorphTargets(
|
||||
a,
|
||||
b,
|
||||
scores[a.tokens.first]!,
|
||||
scores[b.tokens.first]!,
|
||||
),
|
||||
);
|
||||
|
||||
final seenTexts = <String>{};
|
||||
final seenLemmas = <String>{};
|
||||
|
|
@ -179,18 +232,24 @@ class PracticeSelectionRepo {
|
|||
final ids = tokens.map((t) => t.vocabConstructID).toList();
|
||||
final idMap = {for (final token in tokens) token: token.vocabConstructID};
|
||||
|
||||
final constructs = await MatrixState.pangeaController.matrixState.analyticsDataService.getConstructUses(
|
||||
ids,
|
||||
language,
|
||||
);
|
||||
final constructs = await MatrixState
|
||||
.pangeaController
|
||||
.matrixState
|
||||
.analyticsDataService
|
||||
.getConstructUses(ids, language);
|
||||
|
||||
for (final token in tokens) {
|
||||
final construct = constructs[idMap[token]];
|
||||
final lastUsed = construct?.lastUseByTypes(activityType.associatedUseTypes);
|
||||
final lastUsed = construct?.lastUseByTypes(
|
||||
activityType.associatedUseTypes,
|
||||
);
|
||||
|
||||
final daysSinceLastUsed = lastUsed == null ? 20 : DateTime.now().difference(lastUsed).inDays;
|
||||
final daysSinceLastUsed = lastUsed == null
|
||||
? 20
|
||||
: DateTime.now().difference(lastUsed).inDays;
|
||||
|
||||
scores[token] = daysSinceLastUsed * (token.vocabConstructID.isContentWord ? 10 : 7);
|
||||
scores[token] =
|
||||
daysSinceLastUsed * (token.vocabConstructID.isContentWord ? 10 : 7);
|
||||
}
|
||||
return scores;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1486,7 +1486,7 @@ packages:
|
|||
source: git
|
||||
version: "4.1.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ dependencies:
|
|||
rive: 0.11.11
|
||||
flutter_tts: ^4.2.0
|
||||
animated_flip_counter: ^0.3.4
|
||||
meta: ^1.17.0
|
||||
# Pangea#
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/bot_client_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/gender_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/user/user_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -27,7 +28,11 @@ void main() {
|
|||
userGenders: const {userId: GenderEnum.woman},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
|
@ -40,7 +45,11 @@ void main() {
|
|||
userGenders: const {userId: GenderEnum.woman},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.targetLanguage, 'es');
|
||||
|
|
@ -57,7 +66,11 @@ void main() {
|
|||
userGenders: const {userId: GenderEnum.woman},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.languageLevel, LanguageLevelTypeEnum.b1);
|
||||
|
|
@ -71,7 +84,11 @@ void main() {
|
|||
userGenders: const {userId: GenderEnum.woman},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.targetVoice, 'voice_1');
|
||||
|
|
@ -85,14 +102,22 @@ void main() {
|
|||
userGenders: const {userId: GenderEnum.man},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.userGenders[userId], GenderEnum.woman);
|
||||
});
|
||||
|
||||
test('defaults to empty BotOptionsModel when currentOptions is null', () {
|
||||
final result = buildUpdatedBotOptions(currentOptions: null, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: null,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.targetLanguage, 'es');
|
||||
|
|
@ -106,10 +131,17 @@ void main() {
|
|||
targetLanguage: 'fr', // different → triggers update
|
||||
languageLevel: LanguageLevelTypeEnum.b1,
|
||||
targetVoice: 'voice_1',
|
||||
userGenders: const {'@other:server': GenderEnum.man, userId: GenderEnum.woman},
|
||||
userGenders: const {
|
||||
'@other:server': GenderEnum.man,
|
||||
userId: GenderEnum.woman,
|
||||
},
|
||||
);
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: userId);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: userId,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.userGenders['@other:server'], GenderEnum.man);
|
||||
|
|
@ -119,7 +151,11 @@ void main() {
|
|||
test('handles null userId gracefully', () {
|
||||
const currentOptions = BotOptionsModel(targetLanguage: 'fr');
|
||||
|
||||
final result = buildUpdatedBotOptions(currentOptions: currentOptions, userSettings: baseSettings, userId: null);
|
||||
final result = buildUpdatedBotOptions(
|
||||
currentOptions: currentOptions,
|
||||
userSettings: baseSettings,
|
||||
userId: null,
|
||||
);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.targetLanguage, 'es');
|
||||
|
|
@ -193,52 +229,58 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('remaining updates do NOT execute when priority update fails', () async {
|
||||
final callLog = <String>[];
|
||||
test(
|
||||
'remaining updates do NOT execute when priority update fails',
|
||||
() async {
|
||||
final callLog = <String>[];
|
||||
|
||||
try {
|
||||
await applyBotOptionUpdatesInOrder(
|
||||
priorityUpdate: () async {
|
||||
throw Exception('DM failed');
|
||||
},
|
||||
remainingUpdates: [
|
||||
() async {
|
||||
callLog.add('room_a');
|
||||
},
|
||||
],
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
expect(callLog, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'isolates errors in remaining updates and continues to next room',
|
||||
() async {
|
||||
final callLog = <String>[];
|
||||
final errors = <Object>[];
|
||||
|
||||
try {
|
||||
await applyBotOptionUpdatesInOrder(
|
||||
priorityUpdate: () async {
|
||||
throw Exception('DM failed');
|
||||
callLog.add('dm');
|
||||
},
|
||||
remainingUpdates: [
|
||||
() async {
|
||||
callLog.add('room_a');
|
||||
},
|
||||
() async {
|
||||
throw Exception('room_b failed');
|
||||
},
|
||||
() async {
|
||||
callLog.add('room_c');
|
||||
},
|
||||
],
|
||||
onError: (e, _) => errors.add(e),
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
expect(callLog, isEmpty);
|
||||
});
|
||||
|
||||
test('isolates errors in remaining updates and continues to next room', () async {
|
||||
final callLog = <String>[];
|
||||
final errors = <Object>[];
|
||||
|
||||
await applyBotOptionUpdatesInOrder(
|
||||
priorityUpdate: () async {
|
||||
callLog.add('dm');
|
||||
},
|
||||
remainingUpdates: [
|
||||
() async {
|
||||
callLog.add('room_a');
|
||||
},
|
||||
() async {
|
||||
throw Exception('room_b failed');
|
||||
},
|
||||
() async {
|
||||
callLog.add('room_c');
|
||||
},
|
||||
],
|
||||
onError: (e, _) => errors.add(e),
|
||||
);
|
||||
|
||||
// room_b's error didn't prevent room_c from running
|
||||
expect(callLog, ['dm', 'room_a', 'room_c']);
|
||||
expect(errors, hasLength(1));
|
||||
expect(errors.first, isA<Exception>());
|
||||
});
|
||||
// room_b's error didn't prevent room_c from running
|
||||
expect(callLog, ['dm', 'room_a', 'room_c']);
|
||||
expect(errors, hasLength(1));
|
||||
expect(errors.first, isA<Exception>());
|
||||
},
|
||||
);
|
||||
|
||||
test('works correctly when priority update is null', () async {
|
||||
final callLog = <String>[];
|
||||
|
|
@ -273,7 +315,10 @@ void main() {
|
|||
|
||||
test('handles all null / empty gracefully', () async {
|
||||
// Should complete without error
|
||||
await applyBotOptionUpdatesInOrder(priorityUpdate: null, remainingUpdates: []);
|
||||
await applyBotOptionUpdatesInOrder(
|
||||
priorityUpdate: null,
|
||||
remainingUpdates: [],
|
||||
);
|
||||
});
|
||||
|
||||
test('multiple remaining errors are all reported', () async {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue