refactor: remove tokens and detections from IGC text data model (#2528)
* refactor: remove tokens and detections from IGC text data model * generated * refactor: initial work to remove tokens from span_details and IT responses * refactor: add xp field to construct use class, rewrite function for turning choreo record into construct uses * refactor: add translation assistance construct use type * refactor: move analytics feedback to popup above messages * generated --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
0027ce8536
commit
b25676a58d
39 changed files with 840 additions and 1564 deletions
|
|
@ -4530,7 +4530,8 @@
|
|||
"updateNow": "Update Now",
|
||||
"updateLater": "Later",
|
||||
"constructUseWaDesc": "Used without help",
|
||||
"constructUseGaDesc": "Grammar mistake",
|
||||
"constructUseGaDesc": "Grammar assistance",
|
||||
"constructUseTaDesc": "Translation assistance",
|
||||
"constructUseUnkDesc": "Unknown",
|
||||
"constructUseCorITDesc": "Correct in translation",
|
||||
"constructUseIgnITDesc": "Ignored in translation",
|
||||
|
|
@ -4865,5 +4866,7 @@
|
|||
"enterSpaceCode": "Enter the Space Code",
|
||||
"shareSpaceLink": "Share link to space",
|
||||
"byUsingPangeaChat": "By using Pangea Chat, I agree to the ",
|
||||
"details": "Details"
|
||||
"details": "Details",
|
||||
"newVocab": "New vocab",
|
||||
"newGrammar": "New grammar concepts"
|
||||
}
|
||||
|
|
@ -5191,7 +5191,6 @@
|
|||
"enterCodeToJoin": "Ingrese el código para unirse",
|
||||
"updateLater": "Más tarde",
|
||||
"constructUseWaDesc": "Usado sin ayuda",
|
||||
"constructUseGaDesc": "Error gramatical",
|
||||
"constructUseUnkDesc": "Desconocido",
|
||||
"constructUseCorITDesc": "Correcto en la traducción",
|
||||
"constructUseIgnITDesc": "Ignorado en la traducción",
|
||||
|
|
|
|||
|
|
@ -3377,7 +3377,6 @@
|
|||
"updateNow": "Cập nhật ngay",
|
||||
"updateLater": "Để sau",
|
||||
"constructUseWaDesc": "Dùng không cần trợ giúp",
|
||||
"constructUseGaDesc": "Có lỗi ngữ pháp",
|
||||
"constructUseUnkDesc": "Không xác định",
|
||||
"constructUseCorITDesc": "Đúng trong dịch",
|
||||
"constructUseIgnITDesc": "Bỏ qua trong dịch",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import 'package:fluffychat/pages/chat/chat_view.dart';
|
|||
import 'package:fluffychat/pages/chat/event_info_dialog.dart';
|
||||
import 'package:fluffychat/pages/chat/recording_dialog.dart';
|
||||
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_up.dart';
|
||||
|
|
@ -36,6 +37,7 @@ import 'package:fluffychat/pangea/chat/widgets/event_too_large_dialog.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/message_analytics_feedback.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -819,18 +821,46 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
|
||||
if (msgEventId != null && originalSent != null && tokensSent != null) {
|
||||
final List<OneConstructUse> constructs = [
|
||||
...originalSent.vocabAndMorphUses(
|
||||
choreo: choreo,
|
||||
tokens: tokensSent.tokens,
|
||||
metadata: metadata,
|
||||
),
|
||||
];
|
||||
|
||||
final newGrammarConstructs =
|
||||
pangeaController.getAnalytics.newConstructCount(
|
||||
constructs,
|
||||
ConstructTypeEnum.morph,
|
||||
);
|
||||
|
||||
final newVocabConstructs =
|
||||
pangeaController.getAnalytics.newConstructCount(
|
||||
constructs,
|
||||
ConstructTypeEnum.vocab,
|
||||
);
|
||||
|
||||
OverlayUtil.showOverlay(
|
||||
overlayKey: "msg_analytics_feedback_$msgEventId",
|
||||
followerAnchor: Alignment.bottomRight,
|
||||
targetAnchor: Alignment.topRight,
|
||||
context: context,
|
||||
child: MessageAnalyticsFeedback(
|
||||
overlayId: "msg_analytics_feedback_$msgEventId",
|
||||
newGrammarConstructs: newGrammarConstructs,
|
||||
newVocabConstructs: newVocabConstructs,
|
||||
),
|
||||
transformTargetId: msgEventId,
|
||||
ignorePointer: true,
|
||||
);
|
||||
|
||||
pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: msgEventId,
|
||||
targetID: msgEventId,
|
||||
roomId: room.id,
|
||||
constructs: [
|
||||
...originalSent.vocabAndMorphUses(
|
||||
choreo: choreo,
|
||||
tokens: tokensSent.tokens,
|
||||
metadata: metadata,
|
||||
),
|
||||
],
|
||||
constructs: constructs,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ class LemmaUsageDots extends StatelessWidget {
|
|||
List<bool> sortedUses(LearningSkillsEnum category) {
|
||||
final List<bool> useList = [];
|
||||
for (final OneConstructUse use in construct.uses) {
|
||||
if (use.useType.pointValue == 0) {
|
||||
if (use.xp == 0) {
|
||||
continue;
|
||||
}
|
||||
// If the use type matches the given category, save to list
|
||||
// Usage with positive XP is saved as true, else false
|
||||
if (category == use.useType.skillsEnumType) {
|
||||
useList.add(use.useType.pointValue > 0);
|
||||
useList.add(use.xp > 0);
|
||||
}
|
||||
}
|
||||
return useList;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class LemmaUseExampleMessages extends StatelessWidget {
|
|||
if (use.useType.skillsEnumType != LearningSkillsEnum.writing ||
|
||||
use.metadata.eventId == null ||
|
||||
use.form == null ||
|
||||
use.pointValue <= 0) {
|
||||
use.xp <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class AnalyticsSummaryModel {
|
|||
use.useType != ConstructUseTypeEnum.wa &&
|
||||
use.useType != ConstructUseTypeEnum.ga &&
|
||||
use.useType != ConstructUseTypeEnum.unk &&
|
||||
use.pointValue != 0,
|
||||
use.xp != 0,
|
||||
percent: 0.8,
|
||||
context: context,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ class LemmasToUsesWrapper {
|
|||
final uses = entry.value.toList();
|
||||
|
||||
for (final use in uses) {
|
||||
use.pointValue > 0 ? correctUses.add(use) : incorrectUses.add(use);
|
||||
use.xp > 0 ? correctUses.add(use) : incorrectUses.add(use);
|
||||
}
|
||||
|
||||
final totalUses = correctUses.length + incorrectUses.length;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ConstructUses {
|
|||
int get points {
|
||||
return uses.fold<int>(
|
||||
0,
|
||||
(total, use) => total + use.useType.pointValue,
|
||||
(total, use) => total + use.xp,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ class ConstructUses {
|
|||
return _category!.toLowerCase();
|
||||
}
|
||||
|
||||
bool get hasCorrectUse => uses.any((use) => use.pointValue > 0);
|
||||
bool get hasIncorrectUse => uses.any((use) => use.pointValue < 0);
|
||||
bool get hasCorrectUse => uses.any((use) => use.xp > 0);
|
||||
bool get hasIncorrectUse => uses.any((use) => use.xp < 0);
|
||||
|
||||
ConstructIdentifier get id => ConstructIdentifier(
|
||||
lemma: lemma,
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ enum ConstructUseTypeEnum {
|
|||
/// produced in chat by user, igc was run, and we've judged it to be a correct use
|
||||
wa,
|
||||
|
||||
/// produced in chat by user, igc was run, and we've judged it to be a incorrect use
|
||||
/// Note: if the IGC match is ignored, this is not counted as an incorrect use
|
||||
/// produced during IGC
|
||||
ga,
|
||||
|
||||
/// produced during IT
|
||||
ta,
|
||||
|
||||
/// produced in chat by user and igc was not run
|
||||
unk,
|
||||
|
||||
|
|
@ -78,6 +80,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
return L10n.of(context).constructUseWaDesc;
|
||||
case ConstructUseTypeEnum.ga:
|
||||
return L10n.of(context).constructUseGaDesc;
|
||||
case ConstructUseTypeEnum.ta:
|
||||
return L10n.of(context).constructUseTaDesc;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
return L10n.of(context).constructUseUnkDesc;
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
|
|
@ -149,6 +153,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.incIGC:
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.ta:
|
||||
return Icons.spellcheck;
|
||||
case ConstructUseTypeEnum.corPA:
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
|
|
@ -223,9 +228,10 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.ignMM:
|
||||
case ConstructUseTypeEnum.unk:
|
||||
case ConstructUseTypeEnum.nan:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.ta:
|
||||
return 0;
|
||||
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.incMM:
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
case ConstructUseTypeEnum.incIGC:
|
||||
|
|
@ -244,6 +250,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
switch (this) {
|
||||
case ConstructUseTypeEnum.wa:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.ta:
|
||||
case ConstructUseTypeEnum.unk:
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
|
|
@ -283,6 +290,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
switch (this) {
|
||||
case ConstructUseTypeEnum.wa:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.ta:
|
||||
case ConstructUseTypeEnum.unk:
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
|
|
@ -323,6 +331,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
switch (this) {
|
||||
case ConstructUseTypeEnum.wa:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
case ConstructUseTypeEnum.ta:
|
||||
case ConstructUseTypeEnum.unk:
|
||||
case ConstructUseTypeEnum.pvm:
|
||||
return AnalyticsSummaryEnum.numWordsTyped;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ class OneConstructUse {
|
|||
String? id;
|
||||
ConstructUseMetaData metadata;
|
||||
|
||||
int xp;
|
||||
|
||||
OneConstructUse({
|
||||
required this.useType,
|
||||
required this.lemma,
|
||||
|
|
@ -97,6 +99,7 @@ class OneConstructUse {
|
|||
required this.metadata,
|
||||
required category,
|
||||
required this.form,
|
||||
required this.xp,
|
||||
this.id,
|
||||
}) {
|
||||
if (category is MorphFeaturesEnum) {
|
||||
|
|
@ -117,8 +120,10 @@ class OneConstructUse {
|
|||
? ConstructTypeUtil.fromString(json['constructType'])
|
||||
: ConstructTypeEnum.vocab;
|
||||
|
||||
final useType = ConstructUseTypeUtil.fromString(json['useType']);
|
||||
|
||||
return OneConstructUse(
|
||||
useType: ConstructUseTypeUtil.fromString(json['useType']),
|
||||
useType: useType,
|
||||
lemma: json['lemma'],
|
||||
form: json['form'],
|
||||
category: getCategory(json, constructType),
|
||||
|
|
@ -129,6 +134,7 @@ class OneConstructUse {
|
|||
roomId: json['chatId'],
|
||||
timeStamp: DateTime.parse(json['timeStamp']),
|
||||
),
|
||||
xp: json['xp'] ?? useType.pointValue,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +148,7 @@ class OneConstructUse {
|
|||
'constructType': constructType.string,
|
||||
'categories': category,
|
||||
'id': id,
|
||||
'xp': xp,
|
||||
};
|
||||
|
||||
String get category {
|
||||
|
|
@ -196,11 +203,9 @@ class OneConstructUse {
|
|||
return room.getEventById(metadata.eventId!);
|
||||
}
|
||||
|
||||
int get pointValue => useType.pointValue;
|
||||
|
||||
Color pointValueColor(BuildContext context) {
|
||||
if (pointValue == 0) return Theme.of(context).colorScheme.primary;
|
||||
return pointValue > 0 ? AppConfig.gold : Colors.red;
|
||||
if (xp == 0) return Theme.of(context).colorScheme.primary;
|
||||
return xp > 0 ? AppConfig.gold : Colors.red;
|
||||
}
|
||||
|
||||
ConstructIdentifier get identifier => ConstructIdentifier(
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class GetAnalyticsController extends BaseController {
|
|||
_updateAnalyticsStream(
|
||||
points: analyticsUpdate.newConstructs.fold<int>(
|
||||
0,
|
||||
(previousValue, element) => previousValue + element.pointValue,
|
||||
(previousValue, element) => previousValue + element.xp,
|
||||
),
|
||||
targetID: analyticsUpdate.targetID,
|
||||
newConstructs: [...newUnlockedMorphs, ...newUnlockedVocab],
|
||||
|
|
@ -398,6 +398,29 @@ class GetAnalyticsController extends BaseController {
|
|||
_cache.add(entry);
|
||||
}
|
||||
|
||||
int newConstructCount(
|
||||
List<OneConstructUse> newConstructs,
|
||||
ConstructTypeEnum type,
|
||||
) {
|
||||
final uses = newConstructs.where((c) => c.constructType == type);
|
||||
final Map<ConstructIdentifier, int> constructPoints = {};
|
||||
for (final use in uses) {
|
||||
constructPoints[use.identifier] ??= 0;
|
||||
constructPoints[use.identifier] =
|
||||
constructPoints[use.identifier]! + use.xp;
|
||||
}
|
||||
|
||||
int newConstructCount = 0;
|
||||
for (final entry in constructPoints.entries) {
|
||||
final construct = constructListModel.getConstructUses(entry.key);
|
||||
if (construct == null || construct.points == entry.value) {
|
||||
newConstructCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return newConstructCount;
|
||||
}
|
||||
|
||||
// Future<GenerateConstructSummaryResult?>
|
||||
// _generateLevelUpAnalyticsAndSaveToStateEvent(
|
||||
// final int lowerLevel,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
|||
import 'package:fluffychat/pangea/common/controllers/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -122,17 +121,17 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
final String? eventID = data.eventId;
|
||||
final String? roomID = data.roomId;
|
||||
|
||||
List<OneConstructUse> constructs = [];
|
||||
if (roomID != null) {
|
||||
constructs = _getDraftUses(roomID);
|
||||
}
|
||||
final List<OneConstructUse> constructs = [];
|
||||
// if (roomID != null) {
|
||||
// constructs = _getDraftUses(roomID);
|
||||
// }
|
||||
|
||||
constructs.addAll(data.constructs);
|
||||
|
||||
if (kDebugMode) {
|
||||
for (final use in constructs) {
|
||||
debugPrint(
|
||||
"_onNewAnalyticsData filtered use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}",
|
||||
"_onNewAnalyticsData filtered use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.xp}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -161,87 +160,87 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
});
|
||||
}
|
||||
|
||||
void addDraftUses(
|
||||
List<PangeaToken> tokens,
|
||||
String roomID,
|
||||
ConstructUseTypeEnum useType, {
|
||||
String? targetID,
|
||||
}) {
|
||||
final metadata = ConstructUseMetaData(
|
||||
roomId: roomID,
|
||||
timeStamp: DateTime.now(),
|
||||
);
|
||||
// void addDraftUses(
|
||||
// List<PangeaToken> tokens,
|
||||
// String roomID,
|
||||
// ConstructUseTypeEnum useType, {
|
||||
// String? targetID,
|
||||
// }) {
|
||||
// final metadata = ConstructUseMetaData(
|
||||
// roomId: roomID,
|
||||
// timeStamp: DateTime.now(),
|
||||
// );
|
||||
|
||||
// we only save those with saveVocab == true
|
||||
final tokensToSave =
|
||||
tokens.where((token) => token.lemma.saveVocab).toList();
|
||||
// // we only save those with saveVocab == true
|
||||
// final tokensToSave =
|
||||
// tokens.where((token) => token.lemma.saveVocab).toList();
|
||||
|
||||
// get all our vocab constructs
|
||||
final uses = tokensToSave
|
||||
.map(
|
||||
(token) => OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: token.lemma.text,
|
||||
form: token.text.content,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: metadata,
|
||||
category: token.pos,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
// // get all our vocab constructs
|
||||
// final uses = tokensToSave
|
||||
// .map(
|
||||
// (token) => OneConstructUse(
|
||||
// useType: useType,
|
||||
// lemma: token.lemma.text,
|
||||
// form: token.text.content,
|
||||
// constructType: ConstructTypeEnum.vocab,
|
||||
// metadata: metadata,
|
||||
// category: token.pos,
|
||||
// ),
|
||||
// )
|
||||
// .toList();
|
||||
|
||||
// get all our grammar constructs
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: token.pos,
|
||||
form: token.text.content,
|
||||
category: "POS",
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
for (final entry in token.morph.entries) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: entry.value,
|
||||
form: token.text.content,
|
||||
category: entry.key,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// // get all our grammar constructs
|
||||
// for (final token in tokensToSave) {
|
||||
// uses.add(
|
||||
// OneConstructUse(
|
||||
// useType: useType,
|
||||
// lemma: token.pos,
|
||||
// form: token.text.content,
|
||||
// category: "POS",
|
||||
// constructType: ConstructTypeEnum.morph,
|
||||
// metadata: metadata,
|
||||
// ),
|
||||
// );
|
||||
// for (final entry in token.morph.entries) {
|
||||
// uses.add(
|
||||
// OneConstructUse(
|
||||
// useType: useType,
|
||||
// lemma: entry.value,
|
||||
// form: token.text.content,
|
||||
// category: entry.key,
|
||||
// constructType: ConstructTypeEnum.morph,
|
||||
// metadata: metadata,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
if (kDebugMode) {
|
||||
for (final use in uses) {
|
||||
debugPrint(
|
||||
"Draft use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}",
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (kDebugMode) {
|
||||
// for (final use in uses) {
|
||||
// debugPrint(
|
||||
// "Draft use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}",
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
final level = _pangeaController.getAnalytics.constructListModel.level;
|
||||
// final level = _pangeaController.getAnalytics.constructListModel.level;
|
||||
|
||||
// the list 'uses' gets altered in the _addLocalMessage method,
|
||||
// so copy it here to that the list of new uses is accurate
|
||||
final List<OneConstructUse> newUses = List.from(uses);
|
||||
_addLocalMessage('draft$roomID', uses).then(
|
||||
(_) => _decideWhetherToUpdateAnalyticsRoom(
|
||||
level,
|
||||
targetID,
|
||||
newUses,
|
||||
),
|
||||
);
|
||||
}
|
||||
// // the list 'uses' gets altered in the _addLocalMessage method,
|
||||
// // so copy it here to that the list of new uses is accurate
|
||||
// final List<OneConstructUse> newUses = List.from(uses);
|
||||
// _addLocalMessage('draft$roomID', uses).then(
|
||||
// (_) => _decideWhetherToUpdateAnalyticsRoom(
|
||||
// level,
|
||||
// targetID,
|
||||
// newUses,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
List<OneConstructUse> _getDraftUses(String roomID) {
|
||||
final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate;
|
||||
return currentCache['draft$roomID'] ?? [];
|
||||
}
|
||||
// List<OneConstructUse> _getDraftUses(String roomID) {
|
||||
// final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate;
|
||||
// return currentCache['draft$roomID'] ?? [];
|
||||
// }
|
||||
|
||||
void _clearDraftUses(String roomID) {
|
||||
final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class LevelBarPopup extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"${use.pointValue > 0 ? '+' : ''}${use.pointValue}",
|
||||
"${use.xp > 0 ? '+' : ''}${use.xp}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
|
|
|
|||
|
|
@ -1,219 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../repo/similarity_repo.dart';
|
||||
|
||||
class AlternativeTranslator {
|
||||
final Choreographer choreographer;
|
||||
bool showAlternativeTranslations = false;
|
||||
bool loadingAlternativeTranslations = false;
|
||||
bool showTranslationFeedback = false;
|
||||
String? userTranslation;
|
||||
FeedbackKey? translationFeedbackKey;
|
||||
List<String> translations = [];
|
||||
SimilartyResponseModel? similarityResponse;
|
||||
|
||||
AlternativeTranslator(this.choreographer);
|
||||
|
||||
void clear() {
|
||||
userTranslation = null;
|
||||
showAlternativeTranslations = false;
|
||||
loadingAlternativeTranslations = false;
|
||||
showTranslationFeedback = false;
|
||||
translationFeedbackKey = null;
|
||||
translations = [];
|
||||
similarityResponse = null;
|
||||
}
|
||||
|
||||
double get _percentCorrectChoices {
|
||||
final totalSteps = choreographer.choreoRecord.itSteps.length;
|
||||
if (totalSteps == 0) return 0.0;
|
||||
final int correctFirstAttempts = choreographer.itController.completedITSteps
|
||||
.where(
|
||||
(step) => !step.continuances.any(
|
||||
(c) =>
|
||||
c.level != ChoreoConstants.levelThresholdForGreen &&
|
||||
c.wasClicked,
|
||||
),
|
||||
)
|
||||
.length;
|
||||
final double percentage = (correctFirstAttempts / totalSteps) * 100;
|
||||
return percentage;
|
||||
}
|
||||
|
||||
int get starRating {
|
||||
final double percent = _percentCorrectChoices;
|
||||
if (percent == 100) return 5;
|
||||
if (percent >= 80) return 4;
|
||||
if (percent >= 60) return 3;
|
||||
if (percent >= 40) return 2;
|
||||
if (percent > 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> setTranslationFeedback() async {
|
||||
try {
|
||||
choreographer.startLoading();
|
||||
translationFeedbackKey = FeedbackKey.loadingPleaseWait;
|
||||
showTranslationFeedback = true;
|
||||
userTranslation = choreographer.currentText;
|
||||
|
||||
final double percentCorrect = _percentCorrectChoices;
|
||||
|
||||
// Set feedback based on percentage
|
||||
if (percentCorrect == 100) {
|
||||
translationFeedbackKey = FeedbackKey.allCorrect;
|
||||
} else if (percentCorrect >= 80) {
|
||||
translationFeedbackKey = FeedbackKey.newWayAllGood;
|
||||
} else {
|
||||
translationFeedbackKey = FeedbackKey.othersAreBetter;
|
||||
}
|
||||
} catch (err, stack) {
|
||||
if (err is! http.Response) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: stack,
|
||||
data: {
|
||||
"sourceText": choreographer.itController.sourceText,
|
||||
"currentText": choreographer.currentText,
|
||||
"userL1": choreographer.l1LangCode,
|
||||
"userL2": choreographer.l2LangCode,
|
||||
"goldRouteTranslation":
|
||||
choreographer.itController.goldRouteTracker.fullTranslation,
|
||||
},
|
||||
);
|
||||
}
|
||||
choreographer.errorService.setError(
|
||||
ChoreoError(type: ChoreoErrorType.unknown, raw: err),
|
||||
);
|
||||
} finally {
|
||||
choreographer.stopLoading();
|
||||
}
|
||||
}
|
||||
|
||||
List<OneConstructUse> get _itStepConstructs {
|
||||
final metadata = ConstructUseMetaData(
|
||||
roomId: choreographer.roomId,
|
||||
timeStamp: DateTime.now(),
|
||||
);
|
||||
|
||||
final List<OneConstructUse> constructs = [];
|
||||
for (final step in choreographer.choreoRecord.itSteps) {
|
||||
for (final continuance in step.continuances) {
|
||||
final ConstructUseTypeEnum useType = continuance.wasClicked &&
|
||||
continuance.level == ChoreoConstants.levelThresholdForGreen
|
||||
? ConstructUseTypeEnum.corIt
|
||||
: continuance.wasClicked
|
||||
? ConstructUseTypeEnum.incIt
|
||||
: ConstructUseTypeEnum.ignIt;
|
||||
|
||||
final tokens = continuance.tokens.where((t) => t.lemma.saveVocab);
|
||||
constructs.addAll(
|
||||
tokens.map(
|
||||
(token) => OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: token.lemma.text,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: metadata,
|
||||
category: token.pos,
|
||||
form: token.text.content,
|
||||
),
|
||||
),
|
||||
);
|
||||
for (final token in tokens) {
|
||||
constructs.add(
|
||||
OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: token.pos,
|
||||
form: token.text.content,
|
||||
category: "POS",
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
for (final entry in token.morph.entries) {
|
||||
constructs.add(
|
||||
OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: entry.value,
|
||||
form: token.text.content,
|
||||
category: entry.key,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return constructs;
|
||||
}
|
||||
|
||||
int countNewConstructs(ConstructTypeEnum type) {
|
||||
final vocabUses = _itStepConstructs.where((c) => c.constructType == type);
|
||||
final Map<ConstructIdentifier, int> constructPoints = {};
|
||||
for (final use in vocabUses) {
|
||||
constructPoints[use.identifier] ??= 0;
|
||||
constructPoints[use.identifier] =
|
||||
constructPoints[use.identifier]! + use.pointValue;
|
||||
}
|
||||
|
||||
final constructListModel =
|
||||
MatrixState.pangeaController.getAnalytics.constructListModel;
|
||||
|
||||
int newConstructCount = 0;
|
||||
for (final entry in constructPoints.entries) {
|
||||
final construct = constructListModel.getConstructUses(entry.key);
|
||||
if (construct?.points == entry.value) {
|
||||
newConstructCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return newConstructCount;
|
||||
}
|
||||
|
||||
String getDefaultFeedback(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
switch (translationFeedbackKey) {
|
||||
case FeedbackKey.allCorrect:
|
||||
return l10n.perfectTranslation;
|
||||
case FeedbackKey.newWayAllGood:
|
||||
return l10n.greatJobTranslation;
|
||||
case FeedbackKey.othersAreBetter:
|
||||
if (_percentCorrectChoices >= 60) {
|
||||
return l10n.goodJobTranslation;
|
||||
}
|
||||
if (_percentCorrectChoices >= 40) {
|
||||
return l10n.makingProgress;
|
||||
}
|
||||
return l10n.keepPracticing;
|
||||
case FeedbackKey.loadingPleaseWait:
|
||||
return l10n.letMeThink;
|
||||
case FeedbackKey.allDone:
|
||||
return l10n.allDone;
|
||||
default:
|
||||
return l10n.loadingPleaseWait;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FeedbackKey {
|
||||
allCorrect,
|
||||
newWayAllGood,
|
||||
othersAreBetter,
|
||||
loadingPleaseWait,
|
||||
allDone,
|
||||
}
|
||||
|
||||
extension FeedbackKeyExtension on FeedbackKey {}
|
||||
|
|
@ -7,13 +7,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/alternative_translator.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/igc_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/it_step.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/utils/input_paste_listener.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart';
|
||||
|
|
@ -24,6 +24,7 @@ import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
|
|
@ -41,7 +42,6 @@ class Choreographer {
|
|||
late PangeaTextController _textController;
|
||||
late ITController itController;
|
||||
late IgcController igc;
|
||||
late AlternativeTranslator altTranslator;
|
||||
late ErrorService errorService;
|
||||
late TtsController tts;
|
||||
|
||||
|
|
@ -71,7 +71,6 @@ class Choreographer {
|
|||
itController = ITController(this);
|
||||
igc = IgcController(this);
|
||||
errorService = ErrorService(this);
|
||||
altTranslator = AlternativeTranslator(this);
|
||||
_textController.addListener(_onChangeListener);
|
||||
_trialStream = pangeaController
|
||||
.subscriptionController.trialActivationStream.stream
|
||||
|
|
@ -144,6 +143,7 @@ class Choreographer {
|
|||
return;
|
||||
}
|
||||
|
||||
chatController.sendFakeMessage();
|
||||
final PangeaRepresentation? originalWritten =
|
||||
choreoRecord.includedIT && itController.sourceText != null
|
||||
? PangeaRepresentation(
|
||||
|
|
@ -154,59 +154,43 @@ class Choreographer {
|
|||
)
|
||||
: null;
|
||||
|
||||
// we've got a rather elaborate method of updating tokens after matches are accepted
|
||||
// so we need to check if the reconstructed text matches the current text
|
||||
// if not, let's get the tokens again and log an error
|
||||
if (igc.igcTextData?.tokens != null &&
|
||||
PangeaToken.reconstructText(igc.igcTextData!.tokens).trim() !=
|
||||
currentText.trim()) {
|
||||
if (kDebugMode) {
|
||||
PangeaToken.reconstructText(
|
||||
igc.igcTextData!.tokens,
|
||||
debugWalkThrough: true,
|
||||
);
|
||||
}
|
||||
ErrorHandler.logError(
|
||||
m: "reconstructed text not working",
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
"igcTextData": igc.igcTextData?.toJson(),
|
||||
"choreoRecord": choreoRecord.toJson(),
|
||||
},
|
||||
);
|
||||
|
||||
await igc.getIGCTextData(onlyTokensAndLanguageDetection: true);
|
||||
}
|
||||
|
||||
// TODO - why does both it and igc need to be enabled for choreo to be applicable?
|
||||
// final ChoreoRecord? applicableChoreo =
|
||||
// isITandIGCEnabled && igc.igcTextData != null ? choreoRecord : null;
|
||||
|
||||
// if tokens OR language detection are not available, we should get them
|
||||
// notes
|
||||
// 1) we probably need to move this to after we clear the input field
|
||||
// or the user could experience some lag here.
|
||||
// 2) that this call is being made after we've determined if we have an applicable choreo in order to
|
||||
// say whether correction was run on the message. we may eventually want
|
||||
// to edit the useType after
|
||||
if ((l2Lang != null && l1Lang != null) &&
|
||||
(igc.igcTextData?.tokens == null ||
|
||||
igc.igcTextData?.detectedLanguage == null)) {
|
||||
await igc.getIGCTextData(onlyTokensAndLanguageDetection: true);
|
||||
}
|
||||
final detectionResp = await LanguageDetectionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request: LanguageDetectionRequest(
|
||||
text: currentText,
|
||||
senderl1: l1LangCode,
|
||||
senderl2: l2LangCode,
|
||||
),
|
||||
);
|
||||
final detections = detectionResp.detections;
|
||||
final detectedLanguage =
|
||||
detections.firstOrNull?.langCode ?? LanguageKeys.unknownLanguage;
|
||||
|
||||
final PangeaRepresentation originalSent = PangeaRepresentation(
|
||||
langCode:
|
||||
igc.igcTextData?.detectedLanguage ?? LanguageKeys.unknownLanguage,
|
||||
langCode: detectedLanguage,
|
||||
text: currentText,
|
||||
originalSent: true,
|
||||
originalWritten: originalWritten == null,
|
||||
);
|
||||
|
||||
final PangeaMessageTokens? tokensSent = igc.igcTextData?.tokens != null
|
||||
List<PangeaToken>? res;
|
||||
if (l1LangCode != null && l2LangCode != null) {
|
||||
res = await pangeaController.messageData.getTokens(
|
||||
repEventId: null,
|
||||
room: chatController.room,
|
||||
req: TokensRequestModel(
|
||||
fullText: currentText,
|
||||
langCode: detectedLanguage,
|
||||
senderL1: l1LangCode!,
|
||||
senderL2: l2LangCode!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final PangeaMessageTokens? tokensSent = res != null
|
||||
? PangeaMessageTokens(
|
||||
tokens: igc.igcTextData!.tokens,
|
||||
detections: igc.igcTextData!.detections.detections,
|
||||
tokens: res,
|
||||
detections: detections,
|
||||
)
|
||||
: null;
|
||||
|
||||
|
|
@ -235,7 +219,7 @@ class Choreographer {
|
|||
}
|
||||
choreoMode = ChoreoMode.it;
|
||||
itController.initializeIT(
|
||||
ITStartData(_textController.text, igc.igcTextData?.detectedLanguage),
|
||||
ITStartData(_textController.text, null),
|
||||
);
|
||||
itMatch.status = PangeaMatchStatus.accepted;
|
||||
|
||||
|
|
@ -284,9 +268,7 @@ class Choreographer {
|
|||
() => getLanguageHelp(),
|
||||
);
|
||||
} else {
|
||||
getLanguageHelp(
|
||||
onlyTokensAndLanguageDetection: ChoreoMode.it == choreoMode,
|
||||
);
|
||||
getLanguageHelp();
|
||||
}
|
||||
|
||||
//Note: we don't set the keyboard type on each keyboard stroke so this is how we default to
|
||||
|
|
@ -300,7 +282,6 @@ class Choreographer {
|
|||
/// or if autoIGC is not enabled and the user has not manually requested it.
|
||||
/// [onlyTokensAndLanguageDetection] will
|
||||
Future<void> getLanguageHelp({
|
||||
bool onlyTokensAndLanguageDetection = false,
|
||||
bool manual = false,
|
||||
}) async {
|
||||
try {
|
||||
|
|
@ -320,17 +301,13 @@ class Choreographer {
|
|||
|
||||
// if getting language assistance after finishing IT,
|
||||
// reset the itController
|
||||
if (choreoMode == ChoreoMode.it &&
|
||||
itController.isTranslationDone &&
|
||||
!onlyTokensAndLanguageDetection) {
|
||||
if (choreoMode == ChoreoMode.it && itController.isTranslationDone) {
|
||||
itController.clear();
|
||||
}
|
||||
|
||||
await (isRunningIT
|
||||
? itController.getTranslationData(_useCustomInput)
|
||||
: igc.getIGCTextData(
|
||||
onlyTokensAndLanguageDetection: onlyTokensAndLanguageDetection,
|
||||
));
|
||||
: igc.getIGCTextData());
|
||||
} catch (err, stack) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
|
|
@ -343,7 +320,6 @@ class Choreographer {
|
|||
"itEnabled": itEnabled,
|
||||
"isAutoIGCEnabled": isAutoIGCEnabled,
|
||||
"isTranslationDone": itController.isTranslationDone,
|
||||
"onlyTokensAndLanguageDetection": onlyTokensAndLanguageDetection,
|
||||
"useCustomInput": _useCustomInput,
|
||||
},
|
||||
);
|
||||
|
|
@ -365,18 +341,6 @@ class Choreographer {
|
|||
giveInputFocus();
|
||||
}
|
||||
|
||||
void onPredictorSelect(String text) {
|
||||
//TODO - add some kind of record of this
|
||||
// choreoRecord.addRecord(_textController.text, step: step);
|
||||
|
||||
// TODO - probably give it a different type of edit type
|
||||
_textController.setSystemText(
|
||||
"${_textController.text} $text",
|
||||
EditType.other,
|
||||
);
|
||||
giveInputFocus();
|
||||
}
|
||||
|
||||
Future<void> onReplacementSelect({
|
||||
required int matchIndex,
|
||||
required int choiceIndex,
|
||||
|
|
@ -546,21 +510,6 @@ class Choreographer {
|
|||
}
|
||||
}
|
||||
|
||||
void onSelectAlternativeTranslation(String translation) {
|
||||
// PTODO - add some kind of record of this
|
||||
// choreoRecord.addRecord(_textController.text, match);
|
||||
|
||||
_textController.setSystemText(
|
||||
translation,
|
||||
EditType.alternativeTranslation,
|
||||
);
|
||||
altTranslator.clear();
|
||||
altTranslator.translationFeedbackKey = FeedbackKey.allDone;
|
||||
altTranslator.showTranslationFeedback = true;
|
||||
giveInputFocus();
|
||||
setState();
|
||||
}
|
||||
|
||||
void giveInputFocus() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
chatController.inputFocus.requestFocus();
|
||||
|
|
@ -586,22 +535,6 @@ class Choreographer {
|
|||
_resetDebounceTimer();
|
||||
}
|
||||
|
||||
void onMatchError({int? cursorOffset}) {
|
||||
if (cursorOffset == null) {
|
||||
igc.igcTextData?.matches.clear();
|
||||
} else {
|
||||
final int? matchIndex = igc.igcTextData?.getTopMatchIndexForOffset(
|
||||
cursorOffset,
|
||||
);
|
||||
matchIndex == -1 || matchIndex == null
|
||||
? igc.igcTextData?.matches.clear()
|
||||
: igc.igcTextData?.matches.removeAt(matchIndex);
|
||||
}
|
||||
|
||||
setState();
|
||||
giveInputFocus();
|
||||
}
|
||||
|
||||
Future<void> onPaste(value) async {
|
||||
choreoRecord.pastedStrings.add(value);
|
||||
}
|
||||
|
|
@ -698,33 +631,12 @@ class Choreographer {
|
|||
chatController.room,
|
||||
);
|
||||
|
||||
// bool get itAutoPlayEnabled {
|
||||
// return pangeaController.userController.profile.userSettings.itAutoPlay;
|
||||
// }
|
||||
|
||||
bool get definitionsEnabled =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
ToolSetting.definitions,
|
||||
chatController.room,
|
||||
);
|
||||
|
||||
bool get immersionMode =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
ToolSetting.immersionMode,
|
||||
chatController.room,
|
||||
);
|
||||
|
||||
// bool get translationEnabled =>
|
||||
// pangeaController.permissionsController.isToolEnabled(
|
||||
// ToolSetting.translations,
|
||||
// chatController.room,
|
||||
// );
|
||||
|
||||
bool get isITandIGCEnabled =>
|
||||
pangeaController.permissionsController.isWritingAssistanceEnabled(
|
||||
chatController.room,
|
||||
);
|
||||
|
||||
bool get isAutoIGCEnabled =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
ToolSetting.autoIGC,
|
||||
|
|
|
|||
|
|
@ -72,32 +72,21 @@ class IgcController {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> getIGCTextData({
|
||||
required bool onlyTokensAndLanguageDetection,
|
||||
}) async {
|
||||
Future<void> getIGCTextData() async {
|
||||
try {
|
||||
if (choreographer.currentText.isEmpty) return clear();
|
||||
|
||||
// if tokenizing on message send, tokenization might take a while
|
||||
// so add a fake event to the timeline to visually indicate that the message is being sent
|
||||
if (onlyTokensAndLanguageDetection &&
|
||||
choreographer.choreoMode != ChoreoMode.it) {
|
||||
choreographer.chatController.sendFakeMessage();
|
||||
}
|
||||
|
||||
debugPrint('getIGCTextData called with ${choreographer.currentText}');
|
||||
debugPrint(
|
||||
'getIGCTextData called with tokensOnly = $onlyTokensAndLanguageDetection',
|
||||
);
|
||||
|
||||
final IGCRequestBody reqBody = IGCRequestBody(
|
||||
fullText: choreographer.currentText,
|
||||
userId: choreographer.pangeaController.userController.userId!,
|
||||
userL1: choreographer.l1LangCode!,
|
||||
userL2: choreographer.l2LangCode!,
|
||||
enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection,
|
||||
enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection,
|
||||
prevMessages: prevMessages(),
|
||||
enableIGC: choreographer.igcEnabled &&
|
||||
choreographer.choreoMode != ChoreoMode.it,
|
||||
enableIT: choreographer.itEnabled &&
|
||||
choreographer.choreoMode != ChoreoMode.it,
|
||||
prevMessages: _prevMessages(),
|
||||
);
|
||||
|
||||
if (_cacheClearTimer == null || !_cacheClearTimer!.isActive) {
|
||||
|
|
@ -142,37 +131,7 @@ class IgcController {
|
|||
}
|
||||
}
|
||||
|
||||
// If there are any matches that don't match up with token offsets,
|
||||
// this indicates and choreographer bug. Remove them.
|
||||
final tokens = igcTextData!.tokens;
|
||||
final List<PangeaMatch> confirmedMatches = List.from(filteredMatches);
|
||||
for (final match in filteredMatches) {
|
||||
final substring = match.match.fullText.characters
|
||||
.skip(match.match.offset)
|
||||
.take(match.match.length);
|
||||
final trimmed = substring.toString().trim().characters;
|
||||
final matchOffset = (match.match.offset + match.match.length) -
|
||||
(substring.length - trimmed.length);
|
||||
final hasStartMatch = tokens.any(
|
||||
(token) => token.text.offset == match.match.offset,
|
||||
);
|
||||
final hasEndMatch = tokens.any(
|
||||
(token) => token.text.offset + token.text.length == matchOffset,
|
||||
);
|
||||
if (!hasStartMatch || !hasEndMatch) {
|
||||
confirmedMatches.clear();
|
||||
ErrorHandler.logError(
|
||||
m: "Match offset and/or length do not tokens with matching offset and length. This is a choreographer bug.",
|
||||
data: {
|
||||
"match": match.toJson(),
|
||||
"tokens": tokens.map((e) => e.toJson()).toList(),
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
igcTextData!.matches = confirmedMatches;
|
||||
igcTextData!.matches = filteredMatches;
|
||||
choreographer.acceptNormalizationMatches();
|
||||
|
||||
// TODO - for each new match,
|
||||
|
|
@ -200,7 +159,6 @@ class IgcController {
|
|||
e: err,
|
||||
s: stack,
|
||||
data: {
|
||||
"onlyTokensAndLanguageDetection": onlyTokensAndLanguageDetection,
|
||||
"currentText": choreographer.currentText,
|
||||
"userL1": choreographer.l1LangCode,
|
||||
"userL2": choreographer.l2LangCode,
|
||||
|
|
@ -275,7 +233,7 @@ class IgcController {
|
|||
|
||||
/// Get the content of previous text and audio messages in chat.
|
||||
/// Passed to IGC request to add context.
|
||||
List<PreviousMessage> prevMessages({int numMessages = 5}) {
|
||||
List<PreviousMessage> _prevMessages({int numMessages = 5}) {
|
||||
final List<Event> events = choreographer.chatController.visibleEvents
|
||||
.where(
|
||||
(e) =>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,14 @@ import 'dart:async';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../models/custom_input_translation_model.dart';
|
||||
import '../models/it_response_model.dart';
|
||||
|
|
@ -56,7 +53,6 @@ class ITController {
|
|||
goldRouteTracker = GoldRouteTracker.defaultTracker;
|
||||
payLoadIds = [];
|
||||
|
||||
choreographer.altTranslator.clear();
|
||||
choreographer.choreoMode = ChoreoMode.igc;
|
||||
choreographer.setState();
|
||||
}
|
||||
|
|
@ -164,11 +160,10 @@ class ITController {
|
|||
|
||||
if (isTranslationDone) {
|
||||
nextITStep = null;
|
||||
choreographer.altTranslator.setTranslationFeedback();
|
||||
choreographer.getLanguageHelp(onlyTokensAndLanguageDetection: true);
|
||||
closeIT();
|
||||
} else {
|
||||
nextITStep = Completer<CurrentITStep?>();
|
||||
final nextStep = await getNextTranslationData();
|
||||
final nextStep = await _getNextTranslationData();
|
||||
nextITStep?.complete(nextStep);
|
||||
}
|
||||
} catch (e, s) {
|
||||
|
|
@ -192,7 +187,7 @@ class ITController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<CurrentITStep?> getNextTranslationData() async {
|
||||
Future<CurrentITStep?> _getNextTranslationData() async {
|
||||
if (sourceText == null) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception("sourceText is null in getNextTranslationData"),
|
||||
|
|
@ -300,20 +295,6 @@ class ITController {
|
|||
);
|
||||
}
|
||||
|
||||
// MessageServiceModel? messageServiceModelWithMessageId() =>
|
||||
// usedInteractiveTranslation
|
||||
// ? MessageServiceModel(
|
||||
// classId: choreographer.classId,
|
||||
// roomId: choreographer.roomId,
|
||||
// message: choreographer.currentText,
|
||||
// messageId: null,
|
||||
// payloadIds: payLoadIds,
|
||||
// userId: choreographer.userId!,
|
||||
// l1Lang: sourceLangCode,
|
||||
// l2Lang: targetLangCode,
|
||||
// )
|
||||
// : null;
|
||||
|
||||
//maybe we store IT data in the same format? make a specific kind of match?
|
||||
void selectTranslation(int chosenIndex) {
|
||||
if (currentITStep == null) return;
|
||||
|
|
@ -323,20 +304,6 @@ class ITController {
|
|||
|
||||
showChoiceFeedback = true;
|
||||
|
||||
// Get a list of the choices that the user did not click
|
||||
final List<PangeaToken>? ignoredTokens = currentITStep?.continuances
|
||||
.where((e) => !e.wasClicked)
|
||||
.map((e) => e.tokens)
|
||||
.expand((e) => e)
|
||||
.toList();
|
||||
|
||||
// Save those choices' tokens to local construct analytics as ignored tokens
|
||||
choreographer.pangeaController.putAnalytics.addDraftUses(
|
||||
ignoredTokens ?? [],
|
||||
choreographer.roomId,
|
||||
ConstructUseTypeEnum.ignIt,
|
||||
);
|
||||
|
||||
Future.delayed(
|
||||
const Duration(
|
||||
milliseconds: ChoreoConstants.millisecondsToDisplayFeedback,
|
||||
|
|
@ -357,8 +324,6 @@ class ITController {
|
|||
payLoadIds.add(res.payloadId);
|
||||
}
|
||||
|
||||
bool get usedInteractiveTranslation => sourceText != null;
|
||||
|
||||
bool get isTranslationDone => currentITStep != null && currentITStep!.isFinal;
|
||||
|
||||
bool get isOpen => _isOpen;
|
||||
|
|
@ -371,41 +336,12 @@ class ITController {
|
|||
|
||||
bool get isLoading => choreographer.isFetching;
|
||||
|
||||
String latestChoiceFeedback(BuildContext context) =>
|
||||
completedITSteps.isNotEmpty
|
||||
? completedITSteps.last.choiceFeedback(context)
|
||||
: "";
|
||||
|
||||
// String translationFeedback(BuildContext context) =>
|
||||
// completedITSteps.isNotEmpty
|
||||
// ? completedITSteps.last.translationFeedback(context)
|
||||
// : "";
|
||||
|
||||
bool get showAlternativeTranslationsOption => completedITSteps.isNotEmpty
|
||||
? completedITSteps.last.showAlternativeTranslationOption &&
|
||||
sourceText != null
|
||||
: false;
|
||||
|
||||
setIsEditingSourceText(bool value) {
|
||||
void setIsEditingSourceText(bool value) {
|
||||
_isEditingSourceText = value;
|
||||
choreographer.setState();
|
||||
}
|
||||
|
||||
bool get isEditingSourceText => _isEditingSourceText;
|
||||
|
||||
int get correctChoices =>
|
||||
completedITSteps.where((element) => element.isCorrect).length;
|
||||
|
||||
int get wildcardChoices =>
|
||||
completedITSteps.where((element) => element.isYellow).length;
|
||||
|
||||
int get incorrectChoices =>
|
||||
completedITSteps.where((element) => element.isWrong).length;
|
||||
|
||||
int get customChoices =>
|
||||
completedITSteps.where((element) => element.isCustom).length;
|
||||
|
||||
bool get allCorrect => completedITSteps.every((element) => element.isCorrect);
|
||||
}
|
||||
|
||||
class ITStartData {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,17 @@ class ChoreoRecordStep {
|
|||
data[_stepKey] = itStep?.toJson();
|
||||
return data;
|
||||
}
|
||||
|
||||
List<String>? get choices {
|
||||
if (itStep != null) {
|
||||
return itStep!.continuances.map((e) => e.text).toList().cast<String>();
|
||||
}
|
||||
|
||||
return acceptedOrIgnoredMatch?.match.choices
|
||||
?.map((e) => e.value)
|
||||
.toList()
|
||||
.cast<String>();
|
||||
}
|
||||
}
|
||||
|
||||
// Example flow
|
||||
|
|
|
|||
|
|
@ -6,25 +6,19 @@ import 'package:flutter/material.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/language_detection_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
// import 'package:language_tool/language_tool.dart';
|
||||
|
||||
class IGCTextData {
|
||||
LanguageDetectionResponse detections;
|
||||
String originalInput;
|
||||
String? fullTextCorrection;
|
||||
List<PangeaToken> tokens;
|
||||
List<PangeaMatch> matches;
|
||||
String userL1;
|
||||
String userL2;
|
||||
|
|
@ -33,10 +27,8 @@ class IGCTextData {
|
|||
bool loading = false;
|
||||
|
||||
IGCTextData({
|
||||
required this.detections,
|
||||
required this.originalInput,
|
||||
required this.fullTextCorrection,
|
||||
required this.tokens,
|
||||
required this.matches,
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
|
|
@ -45,25 +37,7 @@ class IGCTextData {
|
|||
});
|
||||
|
||||
factory IGCTextData.fromJson(Map<String, dynamic> json) {
|
||||
// changing this to allow for use of the LanguageDetectionResponse methods
|
||||
// TODO - change API after we're sure all clients are updated. not urgent.
|
||||
final LanguageDetectionResponse detections =
|
||||
json[_detectionsKey] is Iterable
|
||||
? LanguageDetectionResponse.fromJson({
|
||||
"detections": json[_detectionsKey],
|
||||
"full_text": json["original_input"],
|
||||
})
|
||||
: LanguageDetectionResponse.fromJson(
|
||||
json[_detectionsKey] as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
return IGCTextData(
|
||||
tokens: (json[_tokensKey] as Iterable)
|
||||
.map<PangeaToken>(
|
||||
(e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<PangeaToken>(),
|
||||
matches: json[_matchesKey] != null
|
||||
? (json[_matchesKey] as Iterable)
|
||||
.map<PangeaMatch>(
|
||||
|
|
@ -74,7 +48,6 @@ class IGCTextData {
|
|||
.toList()
|
||||
.cast<PangeaMatch>()
|
||||
: [],
|
||||
detections: detections,
|
||||
originalInput: json["original_input"],
|
||||
fullTextCorrection: json["full_text_correction"],
|
||||
userL1: json[ModelKey.userL1],
|
||||
|
|
@ -90,7 +63,6 @@ class IGCTextData {
|
|||
String userL2,
|
||||
) {
|
||||
final PangeaRepresentation content = event.content;
|
||||
final List<PangeaToken> tokens = event.tokens ?? [];
|
||||
final List<PangeaMatch> matches = event.choreo?.choreoSteps
|
||||
.map((step) => step.acceptedOrIgnoredMatch)
|
||||
.whereType<PangeaMatch>()
|
||||
|
|
@ -102,38 +74,9 @@ class IGCTextData {
|
|||
originalInput = matches.first.match.fullText;
|
||||
}
|
||||
|
||||
final defaultDetections = LanguageDetectionResponse(
|
||||
detections: [
|
||||
LanguageDetection(langCode: content.langCode, confidence: 1),
|
||||
],
|
||||
fullText: content.text,
|
||||
);
|
||||
|
||||
LanguageDetectionResponse detections = defaultDetections;
|
||||
if (event.detections != null) {
|
||||
try {
|
||||
detections = LanguageDetectionResponse.fromJson({
|
||||
"detections": event.detections,
|
||||
"full_text": content.text,
|
||||
});
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
m: "Error parsing detections in IGCTextData.fromRepresentationEvent",
|
||||
data: {
|
||||
"detections": event.detections,
|
||||
"full_text": content.text,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return IGCTextData(
|
||||
detections: detections,
|
||||
originalInput: originalInput,
|
||||
fullTextCorrection: content.text,
|
||||
tokens: tokens,
|
||||
matches: matches,
|
||||
userL1: userL1,
|
||||
userL2: userL2,
|
||||
|
|
@ -142,15 +85,11 @@ class IGCTextData {
|
|||
);
|
||||
}
|
||||
|
||||
static const String _tokensKey = "tokens";
|
||||
static const String _matchesKey = "matches";
|
||||
static const String _detectionsKey = "detections";
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
_detectionsKey: detections.toJson(),
|
||||
"original_input": originalInput,
|
||||
"full_text_correction": fullTextCorrection,
|
||||
_tokensKey: tokens.map((e) => e.toJson()).toList(),
|
||||
_matchesKey: matches.map((e) => e.toJson()).toList(),
|
||||
ModelKey.userL1: userL1,
|
||||
ModelKey.userL2: userL2,
|
||||
|
|
@ -158,15 +97,6 @@ class IGCTextData {
|
|||
"enable_igc": enableIGC,
|
||||
};
|
||||
|
||||
/// if we haven't run IGC or IT or there are no matches, we use the highest validated detection
|
||||
/// from [LanguageDetectionResponse.highestValidatedDetection]
|
||||
/// if we have run igc/it and there are no matches, we can relax the threshold
|
||||
/// and use the highest confidence detection
|
||||
String get detectedLanguage {
|
||||
return detections.detections.firstOrNull?.langCode ??
|
||||
LanguageKeys.unknownLanguage;
|
||||
}
|
||||
|
||||
// reconstruct fullText based on accepted match
|
||||
//update offsets in existing matches to reflect the change
|
||||
//if existing matches overlap with the accepted one, remove them??
|
||||
|
|
@ -198,72 +128,8 @@ class IGCTextData {
|
|||
final fullText = newStart + replacement.value.characters + newEnd;
|
||||
originalInput = fullText.toString();
|
||||
|
||||
int startIndex;
|
||||
int endIndex;
|
||||
|
||||
// replace the tokens that are part of the match
|
||||
// with the tokens in the replacement
|
||||
// start is inclusive
|
||||
try {
|
||||
startIndex = tokenIndexByOffset(pangeaMatch.match.offset);
|
||||
// end is exclusive, hence the +1
|
||||
// use pangeaMatch.matchContent.trim().length instead of pangeaMatch.match.length since pangeaMatch.match.length may include leading/trailing spaces
|
||||
endIndex = tokenIndexByOffset(
|
||||
pangeaMatch.match.offset + pangeaMatch.matchContent.trim().length,
|
||||
) +
|
||||
1;
|
||||
} catch (err, s) {
|
||||
matches.removeAt(matchIndex);
|
||||
|
||||
for (final match in matches) {
|
||||
match.match.fullText = originalInput;
|
||||
if (match.match.offset > pangeaMatch.match.offset) {
|
||||
match.match.offset +=
|
||||
replacement.value.length - pangeaMatch.match.length;
|
||||
}
|
||||
}
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"cursorOffset": pangeaMatch.match.offset,
|
||||
"match": pangeaMatch.match.toJson(),
|
||||
"tokens": tokens.map((e) => e.toJson()).toString(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// for all tokens after the replacement, update their offsets
|
||||
for (int i = endIndex; i < tokens.length; i++) {
|
||||
tokens[i].text.offset +=
|
||||
replacement.value.length - pangeaMatch.match.length;
|
||||
}
|
||||
|
||||
// clone the list for debugging purposes
|
||||
final List<PangeaToken> newTokens = List.from(tokens);
|
||||
|
||||
// replace the tokens in the list
|
||||
newTokens.replaceRange(startIndex, endIndex, replacement.tokens);
|
||||
|
||||
final String newFullText = PangeaToken.reconstructText(newTokens);
|
||||
if (newFullText.trim() != originalInput.trim() && kDebugMode) {
|
||||
PangeaToken.reconstructText(newTokens, debugWalkThrough: true);
|
||||
ErrorHandler.logError(
|
||||
m: "reconstructed text not working",
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
"originalInput": originalInput,
|
||||
"newFullText": newFullText,
|
||||
"match": pangeaMatch.match.toJson(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
tokens = newTokens;
|
||||
|
||||
//update offsets in existing matches to reflect the change
|
||||
//Question - remove matches that overlap with the accepted one?
|
||||
// update offsets in existing matches to reflect the change
|
||||
// Question - remove matches that overlap with the accepted one?
|
||||
// see case of "quiero ver un fix"
|
||||
matches.removeAt(matchIndex);
|
||||
|
||||
|
|
@ -276,23 +142,6 @@ class IGCTextData {
|
|||
}
|
||||
}
|
||||
|
||||
void removeMatchByOffset(int offset) {
|
||||
final int index = getTopMatchIndexForOffset(offset);
|
||||
if (index != -1) {
|
||||
matches.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
int tokenIndexByOffset(int cursorOffset) {
|
||||
final tokenIndex = tokens.indexWhere(
|
||||
(token) => token.start <= cursorOffset && cursorOffset <= token.end,
|
||||
);
|
||||
if (tokenIndex < 0) {
|
||||
throw "No token found for cursor offset";
|
||||
}
|
||||
return tokenIndex;
|
||||
}
|
||||
|
||||
List<int> matchIndicesByOffset(int offset) {
|
||||
final List<int> matchesForOffset = [];
|
||||
for (final (index, match) in matches.indexed) {
|
||||
|
|
@ -314,34 +163,6 @@ class IGCTextData {
|
|||
return matchesForToken[matchIndex];
|
||||
}
|
||||
|
||||
PangeaMatch? getTopMatchForToken(PangeaToken token) {
|
||||
final int topMatchIndex = getTopMatchIndexForOffset(token.text.offset);
|
||||
if (topMatchIndex == -1) return null;
|
||||
return matches[topMatchIndex];
|
||||
}
|
||||
|
||||
int getAfterTokenSpacingByIndex(int tokenIndex) {
|
||||
final int endOfToken = tokens[tokenIndex].end;
|
||||
|
||||
if (tokenIndex + 1 < tokens.length) {
|
||||
final spaceBetween = tokens[tokenIndex + 1].text.offset - endOfToken;
|
||||
|
||||
if (spaceBetween < 0) {
|
||||
ErrorHandler.logError(
|
||||
m: "weird token lengths for ${tokens[tokenIndex].text.content} and ${tokens[tokenIndex + 1].text.content}",
|
||||
data: {
|
||||
"fullText": originalInput,
|
||||
"tokens": tokens.map((e) => e.toJson()).toString(),
|
||||
},
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
return spaceBetween;
|
||||
} else {
|
||||
return originalInput.length - endOfToken;
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle underlineStyle(Color color) => TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: color,
|
||||
|
|
@ -459,19 +280,4 @@ class IGCTextData {
|
|||
|
||||
return items;
|
||||
}
|
||||
|
||||
List<PangeaToken> matchTokens(int matchIndex) {
|
||||
if (matchIndex >= matches.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final PangeaMatch match = matches[matchIndex];
|
||||
final List<PangeaToken> tokensForMatch = [];
|
||||
for (final token in tokens) {
|
||||
if (match.isOffsetInMatchSpan(token.text.offset)) {
|
||||
tokensForMatch.add(token);
|
||||
}
|
||||
}
|
||||
return tokensForMatch;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
|
||||
class ITResponseModel {
|
||||
String fullTextTranslation;
|
||||
|
|
@ -80,7 +78,7 @@ class Continuance {
|
|||
double probability;
|
||||
int level;
|
||||
String text;
|
||||
List<PangeaToken> tokens;
|
||||
// List<PangeaToken> tokens;
|
||||
|
||||
/// saving this in a full json form
|
||||
String description;
|
||||
|
|
@ -100,18 +98,18 @@ class Continuance {
|
|||
required this.inDictionary,
|
||||
required this.hasInfo,
|
||||
required this.gold,
|
||||
required this.tokens,
|
||||
// required this.tokens,
|
||||
});
|
||||
|
||||
factory Continuance.fromJson(Map<String, dynamic> json) {
|
||||
final List<PangeaToken> tokensInternal = (json[ModelKey.tokens] != null)
|
||||
? (json[ModelKey.tokens] as Iterable)
|
||||
.map<PangeaToken>(
|
||||
(e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<PangeaToken>()
|
||||
: [];
|
||||
// final List<PangeaToken> tokensInternal = (json[ModelKey.tokens] != null)
|
||||
// ? (json[ModelKey.tokens] as Iterable)
|
||||
// .map<PangeaToken>(
|
||||
// (e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
// )
|
||||
// .toList()
|
||||
// .cast<PangeaToken>()
|
||||
// : [];
|
||||
return Continuance(
|
||||
probability: json['probability'].toDouble(),
|
||||
level: json['level'],
|
||||
|
|
@ -122,7 +120,7 @@ class Continuance {
|
|||
wasClicked: json['clkd'] ?? false,
|
||||
hasInfo: json['has_info'] ?? false,
|
||||
gold: json['gold'] ?? false,
|
||||
tokens: tokensInternal,
|
||||
// tokens: tokensInternal,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +130,7 @@ class Continuance {
|
|||
data['level'] = level;
|
||||
data['text'] = text;
|
||||
data['clkd'] = wasClicked;
|
||||
data[ModelKey.tokens] = tokens.map((e) => e.toJson()).toList();
|
||||
// data[ModelKey.tokens] = tokens.map((e) => e.toJson()).toList();
|
||||
|
||||
if (!condensed) {
|
||||
data['description'] = description;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import '../enums/span_choice_type.dart';
|
||||
import '../enums/span_data_type.dart';
|
||||
|
||||
|
|
@ -75,7 +73,7 @@ class SpanChoice {
|
|||
bool selected;
|
||||
String? feedback;
|
||||
DateTime? timestamp;
|
||||
List<PangeaToken> tokens;
|
||||
// List<PangeaToken> tokens;
|
||||
|
||||
SpanChoice({
|
||||
required this.value,
|
||||
|
|
@ -83,18 +81,18 @@ class SpanChoice {
|
|||
this.feedback,
|
||||
this.selected = false,
|
||||
this.timestamp,
|
||||
this.tokens = const [],
|
||||
// this.tokens = const [],
|
||||
});
|
||||
|
||||
factory SpanChoice.fromJson(Map<String, dynamic> json) {
|
||||
final List<PangeaToken> tokensInternal = (json[ModelKey.tokens] != null)
|
||||
? (json[ModelKey.tokens] as Iterable)
|
||||
.map<PangeaToken>(
|
||||
(e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<PangeaToken>()
|
||||
: [];
|
||||
// final List<PangeaToken> tokensInternal = (json[ModelKey.tokens] != null)
|
||||
// ? (json[ModelKey.tokens] as Iterable)
|
||||
// .map<PangeaToken>(
|
||||
// (e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
// )
|
||||
// .toList()
|
||||
// .cast<PangeaToken>()
|
||||
// : [];
|
||||
return SpanChoice(
|
||||
value: json['value'] as String,
|
||||
type: json['type'] != null
|
||||
|
|
@ -107,7 +105,7 @@ class SpanChoice {
|
|||
selected: json['selected'] ?? false,
|
||||
timestamp:
|
||||
json['timestamp'] != null ? DateTime.parse(json['timestamp']) : null,
|
||||
tokens: tokensInternal,
|
||||
// tokens: tokensInternal,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +115,7 @@ class SpanChoice {
|
|||
'selected': selected,
|
||||
'feedback': feedback,
|
||||
'timestamp': timestamp?.toIso8601String(),
|
||||
'tokens': tokens.map((e) => e.toJson()).toList(),
|
||||
// 'tokens': tokens.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
String feedbackToDisplay(BuildContext context) {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,9 @@ import 'dart:convert';
|
|||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/models/language_detection_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/span_data_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma.dart';
|
||||
import '../../common/constants/model_keys.dart';
|
||||
import '../../common/network/requests.dart';
|
||||
import '../../common/network/urls.dart';
|
||||
|
|
@ -41,42 +36,6 @@ class IgcRepo {
|
|||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
final IGCTextData igcTextData = IGCTextData(
|
||||
detections: LanguageDetectionResponse(
|
||||
detections: [LanguageDetection(langCode: "en", confidence: 0.99)],
|
||||
fullText: "This be a sample text",
|
||||
),
|
||||
tokens: [
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "This", offset: 0, length: 4),
|
||||
lemma: Lemma(form: "This", text: "this", saveVocab: true),
|
||||
pos: "DET",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "be", offset: 5, length: 2),
|
||||
lemma: Lemma(form: "be", text: "be", saveVocab: true),
|
||||
pos: "VERB",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "a", offset: 8, length: 1),
|
||||
lemma: Lemma(form: "a", text: "a", saveVocab: true),
|
||||
pos: "DET",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "sample", offset: 10, length: 6),
|
||||
lemma: Lemma(form: "sample", text: "sample", saveVocab: true),
|
||||
pos: "NOUN",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "text", offset: 17, length: 4),
|
||||
lemma: Lemma(form: "text", text: "text", saveVocab: true),
|
||||
pos: "NOUN",
|
||||
morph: {},
|
||||
),
|
||||
],
|
||||
matches: [
|
||||
PangeaMatch(
|
||||
match: spanDataRepomockSpan,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import '../../common/network/requests.dart';
|
|||
import '../../common/network/urls.dart';
|
||||
import '../models/custom_input_translation_model.dart';
|
||||
import '../models/it_response_model.dart';
|
||||
import 'system_choice_translation_model.dart';
|
||||
|
||||
class ITRepo {
|
||||
static Future<ITResponseModel> customInputTranslate(
|
||||
|
|
@ -26,19 +25,19 @@ class ITRepo {
|
|||
return ITResponseModel.fromJson(json);
|
||||
}
|
||||
|
||||
static Future<ITResponseModel> systemChoiceTranslate(
|
||||
SystemChoiceRequestModel subseqText,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
// static Future<ITResponseModel> systemChoiceTranslate(
|
||||
// SystemChoiceRequestModel subseqText,
|
||||
// ) async {
|
||||
// final Requests req = Requests(
|
||||
// choreoApiKey: Environment.choreoApiKey,
|
||||
// accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
// );
|
||||
|
||||
final Response res =
|
||||
await req.post(url: PApiUrls.subseqStep, body: subseqText.toJson());
|
||||
// final Response res =
|
||||
// await req.post(url: PApiUrls.subseqStep, body: subseqText.toJson());
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes).toString());
|
||||
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes).toString());
|
||||
|
||||
return ITResponseModel.fromJson(decodedBody);
|
||||
}
|
||||
// return ITResponseModel.fromJson(decodedBody);
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
46
lib/pangea/choreographer/repo/tokens_repo.dart
Normal file
46
lib/pangea/choreographer/repo/tokens_repo.dart
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
|
||||
class TokensRepo {
|
||||
static Future<TokensResponseModel> get(
|
||||
String? accessToken, {
|
||||
required TokensRequestModel request,
|
||||
}) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.tokenize,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
final TokensResponseModel response = TokensResponseModel.fromJson(
|
||||
jsonDecode(
|
||||
utf8.decode(res.bodyBytes).toString(),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.tokens.isEmpty) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception(
|
||||
"empty tokens in tokenize response return",
|
||||
),
|
||||
data: {
|
||||
"accessToken": accessToken,
|
||||
"request": request.toJson(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class MessageAnalyticsFeedback extends StatefulWidget {
|
||||
final String overlayId;
|
||||
final int newGrammarConstructs;
|
||||
final int newVocabConstructs;
|
||||
|
||||
const MessageAnalyticsFeedback({
|
||||
required this.overlayId,
|
||||
required this.newGrammarConstructs,
|
||||
required this.newVocabConstructs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MessageAnalyticsFeedback> createState() =>
|
||||
MessageAnalyticsFeedbackState();
|
||||
}
|
||||
|
||||
class MessageAnalyticsFeedbackState extends State<MessageAnalyticsFeedback>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _vocabController;
|
||||
late AnimationController _grammarController;
|
||||
late AnimationController _bubbleController;
|
||||
|
||||
late Animation<double> _vocabOpacity;
|
||||
late Animation<double> _grammarOpacity;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _opacityAnimation;
|
||||
|
||||
static const counterDelay = Duration(milliseconds: 400);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_grammarController = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
|
||||
_grammarOpacity = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _grammarController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_vocabController = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
|
||||
_vocabOpacity = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _vocabController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_bubbleController = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _bubbleController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_opacityAnimation = Tween<double>(begin: 0.0, end: 0.9).animate(
|
||||
CurvedAnimation(parent: _bubbleController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _bubbleController.forward();
|
||||
|
||||
Future.delayed(counterDelay, () {
|
||||
if (mounted) {
|
||||
_vocabController.forward();
|
||||
_grammarController.forward();
|
||||
}
|
||||
});
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 4000), () {
|
||||
if (!mounted) return;
|
||||
_bubbleController.reverse().then((_) {
|
||||
MatrixState.pAnyState.closeOverlay(widget.overlayId);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_vocabController.dispose();
|
||||
_grammarController.dispose();
|
||||
_bubbleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showAnalyticsDialog(ConstructTypeEnum? type) {
|
||||
showDialog<AnalyticsPopupWrapper>(
|
||||
context: context,
|
||||
builder: (context) => AnalyticsPopupWrapper(
|
||||
view: type ?? ConstructTypeEnum.vocab,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.newVocabConstructs <= 0 && widget.newGrammarConstructs <= 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: () => _showAnalyticsDialog(null),
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
alignment: Alignment.bottomRight,
|
||||
child: AnimatedBuilder(
|
||||
animation: _bubbleController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest
|
||||
.withAlpha((_opacityAnimation.value * 255).round()),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16.0),
|
||||
topRight: Radius.circular(16.0),
|
||||
bottomLeft: Radius.circular(16.0),
|
||||
bottomRight: Radius.circular(4.0),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.newVocabConstructs > 0)
|
||||
NewConstructsBadge(
|
||||
controller: _vocabController,
|
||||
opacityAnimation: _vocabOpacity,
|
||||
newConstructs: widget.newVocabConstructs,
|
||||
type: ConstructTypeEnum.vocab,
|
||||
tooltip: L10n.of(context).newVocab,
|
||||
onTap: () => _showAnalyticsDialog(
|
||||
ConstructTypeEnum.vocab,
|
||||
),
|
||||
),
|
||||
if (widget.newGrammarConstructs > 0)
|
||||
NewConstructsBadge(
|
||||
controller: _grammarController,
|
||||
opacityAnimation: _grammarOpacity,
|
||||
newConstructs: widget.newGrammarConstructs,
|
||||
type: ConstructTypeEnum.morph,
|
||||
tooltip: L10n.of(context).newGrammar,
|
||||
onTap: () => _showAnalyticsDialog(
|
||||
ConstructTypeEnum.morph,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NewConstructsBadge extends StatelessWidget {
|
||||
final AnimationController controller;
|
||||
final Animation<double> opacityAnimation;
|
||||
|
||||
final int newConstructs;
|
||||
final ConstructTypeEnum type;
|
||||
final String tooltip;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const NewConstructsBadge({
|
||||
required this.controller,
|
||||
required this.opacityAnimation,
|
||||
required this.newConstructs,
|
||||
required this.type,
|
||||
required this.tooltip,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Tooltip(
|
||||
message: tooltip,
|
||||
child: AnimatedBuilder(
|
||||
animation: controller,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: opacityAnimation.value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.toys_and_games,
|
||||
color: ProgressIndicatorEnum.morphsUsed.color(context),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
AnimatedCounter(
|
||||
key: ValueKey("$type-counter"),
|
||||
endValue: newConstructs,
|
||||
startAnimation: opacityAnimation.value > 0.9,
|
||||
style: TextStyle(
|
||||
color: ProgressIndicatorEnum.morphsUsed.color(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedCounter extends StatefulWidget {
|
||||
final int endValue;
|
||||
final TextStyle? style;
|
||||
final bool startAnimation;
|
||||
|
||||
const AnimatedCounter({
|
||||
super.key,
|
||||
required this.endValue,
|
||||
this.style,
|
||||
this.startAnimation = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedCounter> createState() => _AnimatedCounterState();
|
||||
}
|
||||
|
||||
class _AnimatedCounterState extends State<AnimatedCounter>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<int> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
|
||||
_animation = IntTween(
|
||||
begin: 0,
|
||||
end: widget.endValue,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.startAnimation) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _controller.forward();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedCounter oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!oldWidget.startAnimation && widget.startAnimation && !_hasAnimated) {
|
||||
_controller.forward();
|
||||
}
|
||||
}
|
||||
|
||||
bool get _hasAnimated => _controller.isCompleted || _controller.isAnimating;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return Text(
|
||||
"+ ${_animation.value}",
|
||||
style: widget.style,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -72,15 +72,6 @@ class PangeaTextController extends TextEditingController {
|
|||
return;
|
||||
}
|
||||
|
||||
int tokenIndex;
|
||||
try {
|
||||
tokenIndex = choreographer.igc.igcTextData!.tokenIndexByOffset(
|
||||
selection.baseOffset,
|
||||
);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int matchIndex =
|
||||
choreographer.igc.igcTextData!.getTopMatchIndexForOffset(
|
||||
selection.baseOffset,
|
||||
|
|
@ -102,9 +93,7 @@ class PangeaTextController extends TextEditingController {
|
|||
matchIndex: matchIndex,
|
||||
onReplacementSelect: choreographer.onReplacementSelect,
|
||||
// may not need this
|
||||
onSentenceRewrite: ((sentenceRewrite) async {
|
||||
debugPrint("onSentenceRewrite $tokenIndex $sentenceRewrite");
|
||||
}),
|
||||
onSentenceRewrite: ((sentenceRewrite) async {}),
|
||||
onIgnore: () => choreographer.onIgnoreMatch(
|
||||
cursorOffset: selection.baseOffset,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/span_data_type.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/utils/match_copy.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/card_error_widget.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import '../../../../widgets/matrix.dart';
|
||||
import '../../../bot/widgets/bot_face_svg.dart';
|
||||
|
|
@ -153,18 +151,6 @@ class SpanCardState extends State<SpanCard> {
|
|||
Future<void> onChoiceSelect(int index) async {
|
||||
selectedChoiceIndex = index;
|
||||
if (selectedChoice != null) {
|
||||
if (!selectedChoice!.selected) {
|
||||
MatrixState.pangeaController.putAnalytics.addDraftUses(
|
||||
selectedChoice!.tokens,
|
||||
widget.roomId,
|
||||
selectedChoice!.isBestCorrection
|
||||
? ConstructUseTypeEnum.corIGC
|
||||
: ConstructUseTypeEnum.incIGC,
|
||||
targetID:
|
||||
"${selectedChoice!.value}${widget.scm.pangeaMatch?.hashCode.toString()}",
|
||||
);
|
||||
}
|
||||
|
||||
selectedChoice!.timestamp = DateTime.now();
|
||||
selectedChoice!.selected = true;
|
||||
setState(
|
||||
|
|
@ -175,28 +161,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the list of distractor choices that are not selected
|
||||
List<SpanChoice>? get ignoredMatches => widget.scm.pangeaMatch?.match.choices
|
||||
?.where((choice) => choice.isDistractor && !choice.selected)
|
||||
.toList();
|
||||
|
||||
/// Returns the list of tokens from choices that are not selected
|
||||
List<PangeaToken>? get ignoredTokens => ignoredMatches
|
||||
?.expand((choice) => choice.tokens)
|
||||
.toList()
|
||||
.cast<PangeaToken>();
|
||||
|
||||
/// Adds the ignored tokens to locally cached analytics
|
||||
void addIgnoredTokenUses() {
|
||||
MatrixState.pangeaController.putAnalytics.addDraftUses(
|
||||
ignoredTokens ?? [],
|
||||
widget.roomId,
|
||||
ConstructUseTypeEnum.ignIGC,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onReplaceSelected() async {
|
||||
addIgnoredTokenUses();
|
||||
await widget.scm.onReplacementSelect(
|
||||
matchIndex: widget.scm.matchIndex,
|
||||
choiceIndex: selectedChoiceIndex!,
|
||||
|
|
@ -205,8 +170,6 @@ class SpanCardState extends State<SpanCard> {
|
|||
}
|
||||
|
||||
void onIgnoreMatch() {
|
||||
addIgnoredTokenUses();
|
||||
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@ import 'dart:developer';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
|
|
@ -230,22 +227,7 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
controller: itController,
|
||||
)
|
||||
: itController.isTranslationDone
|
||||
? TranslationFeedback(
|
||||
controller: itController,
|
||||
vocabCount: itController
|
||||
.choreographer.altTranslator
|
||||
.countNewConstructs(
|
||||
ConstructTypeEnum.vocab,
|
||||
),
|
||||
grammarCount: itController
|
||||
.choreographer.altTranslator
|
||||
.countNewConstructs(
|
||||
ConstructTypeEnum.morph,
|
||||
),
|
||||
feedbackText: itController
|
||||
.choreographer.altTranslator
|
||||
.getDefaultFeedback(context),
|
||||
)
|
||||
? const SizedBox()
|
||||
: ITChoices(controller: itController),
|
||||
),
|
||||
),
|
||||
|
|
@ -394,17 +376,6 @@ class ITChoices extends StatelessWidget {
|
|||
continuance.feedbackText(context),
|
||||
);
|
||||
}
|
||||
if (!continuance.wasClicked) {
|
||||
controller.choreographer.pangeaController.putAnalytics.addDraftUses(
|
||||
continuance.tokens,
|
||||
controller.choreographer.roomId,
|
||||
continuance.level > 1
|
||||
? ConstructUseTypeEnum.incIt
|
||||
: ConstructUseTypeEnum.corIt,
|
||||
targetID:
|
||||
"${continuance.text.trim()}${controller.currentITStep.hashCode.toString()}",
|
||||
);
|
||||
}
|
||||
controller.currentITStep!.continuances[index].wasClicked = true;
|
||||
controller.choreographer.setState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,7 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
);
|
||||
return;
|
||||
case AssistanceState.notFetched:
|
||||
await widget.controller.choreographer.getLanguageHelp(
|
||||
onlyTokensAndLanguageDetection: false,
|
||||
manual: true,
|
||||
);
|
||||
await widget.controller.choreographer.getLanguageHelp(manual: true);
|
||||
_showFirstMatch();
|
||||
return;
|
||||
case AssistanceState.fetched:
|
||||
|
|
|
|||
|
|
@ -1,379 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_stars.dart';
|
||||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../common/utils/error_handler.dart';
|
||||
import '../controllers/it_controller.dart';
|
||||
|
||||
class TranslationFeedback extends StatefulWidget {
|
||||
final int vocabCount;
|
||||
final int grammarCount;
|
||||
final String feedbackText;
|
||||
final ITController controller;
|
||||
|
||||
const TranslationFeedback({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.vocabCount,
|
||||
required this.grammarCount,
|
||||
required this.feedbackText,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TranslationFeedback> createState() => _TranslationFeedbackState();
|
||||
}
|
||||
|
||||
class _TranslationFeedbackState extends State<TranslationFeedback>
|
||||
with TickerProviderStateMixin {
|
||||
late final int starRating;
|
||||
late final int vocabCount;
|
||||
late final int grammarCount;
|
||||
|
||||
// Animation controllers for each component
|
||||
late AnimationController _starsController;
|
||||
late AnimationController _vocabController;
|
||||
late AnimationController _grammarController;
|
||||
|
||||
// Animations for opacity and scale
|
||||
late Animation<double> _starsOpacity;
|
||||
late Animation<double> _starsScale;
|
||||
late Animation<double> _vocabOpacity;
|
||||
late Animation<double> _grammarOpacity;
|
||||
|
||||
// Constants for animation timing
|
||||
static const vocabDelay = Duration(milliseconds: 800);
|
||||
static const grammarDelay = Duration(milliseconds: 1400);
|
||||
|
||||
// Duration for each individual animation
|
||||
static const elementAnimDuration = Duration(milliseconds: 800);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
vocabCount = widget.vocabCount;
|
||||
grammarCount = widget.grammarCount;
|
||||
|
||||
final altTranslator = widget.controller.choreographer.altTranslator;
|
||||
starRating = altTranslator.starRating;
|
||||
|
||||
// Initialize animation controllers
|
||||
_starsController = AnimationController(
|
||||
vsync: this,
|
||||
duration: elementAnimDuration,
|
||||
);
|
||||
|
||||
_vocabController = AnimationController(
|
||||
vsync: this,
|
||||
duration: elementAnimDuration,
|
||||
);
|
||||
|
||||
_grammarController = AnimationController(
|
||||
vsync: this,
|
||||
duration: elementAnimDuration,
|
||||
);
|
||||
|
||||
// Define animations
|
||||
_starsOpacity = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _starsController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_starsScale = Tween<double>(begin: 0.5, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _starsController, curve: Curves.elasticOut),
|
||||
);
|
||||
|
||||
_vocabOpacity = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _vocabController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_grammarOpacity = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _grammarController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
// Start animations with appropriate delays
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
// Start stars animation immediately
|
||||
_starsController.forward();
|
||||
|
||||
// Start vocab animation after delay if there's vocab to show
|
||||
if (vocabCount > 0) {
|
||||
Future.delayed(vocabDelay, () {
|
||||
if (mounted) _vocabController.forward();
|
||||
});
|
||||
}
|
||||
|
||||
// Start grammar animation after delay if there's grammar to show
|
||||
if (grammarCount > 0) {
|
||||
Future.delayed(grammarDelay, () {
|
||||
if (mounted) _grammarController.forward();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_starsController.dispose();
|
||||
_vocabController.dispose();
|
||||
_grammarController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
try {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Animated stars
|
||||
AnimatedBuilder(
|
||||
animation: _starsController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _starsOpacity.value,
|
||||
child: Transform.scale(
|
||||
scale: _starsScale.value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: FillingStars(rating: starRating),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
if (vocabCount > 0 || grammarCount > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (vocabCount > 0)
|
||||
AnimatedBuilder(
|
||||
animation: _vocabController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _vocabOpacity.value,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.dictionary,
|
||||
color: ProgressIndicatorEnum.wordsUsed
|
||||
.color(context),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
AnimatedCounter(
|
||||
key: const ValueKey("vocabCounter"),
|
||||
endValue: vocabCount,
|
||||
// Only start counter animation when opacity animation is complete
|
||||
startAnimation: _vocabOpacity.value > 0.9,
|
||||
style: TextStyle(
|
||||
color: ProgressIndicatorEnum.wordsUsed
|
||||
.color(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (grammarCount > 0)
|
||||
AnimatedBuilder(
|
||||
animation: _grammarController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _grammarOpacity.value,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.toys_and_games,
|
||||
color: ProgressIndicatorEnum.morphsUsed
|
||||
.color(context),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
AnimatedCounter(
|
||||
key: const ValueKey("grammarCounter"),
|
||||
endValue: grammarCount,
|
||||
// Only start counter animation when opacity animation is complete
|
||||
startAnimation: _grammarOpacity.value > 0.9,
|
||||
style: TextStyle(
|
||||
color: ProgressIndicatorEnum.morphsUsed
|
||||
.color(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
AnimatedBuilder(
|
||||
animation: _starsController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _starsOpacity.value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
widget.feedbackText,
|
||||
textAlign: TextAlign.center,
|
||||
style: BotStyle.text(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 8.0),
|
||||
],
|
||||
);
|
||||
} catch (err, stack) {
|
||||
debugPrint("Error in TranslationFeedback: $err");
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: stack,
|
||||
data: {},
|
||||
);
|
||||
// Fallback to a simple message if anything goes wrong
|
||||
return Center(child: Text(L10n.of(context).niceJob));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedCounter extends StatefulWidget {
|
||||
final int endValue;
|
||||
final TextStyle? style;
|
||||
final Duration duration;
|
||||
final String prefix;
|
||||
final bool startAnimation;
|
||||
|
||||
const AnimatedCounter({
|
||||
super.key,
|
||||
required this.endValue,
|
||||
this.style,
|
||||
this.duration = const Duration(milliseconds: 1500),
|
||||
this.prefix = "+ ",
|
||||
this.startAnimation = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedCounter> createState() => _AnimatedCounterState();
|
||||
}
|
||||
|
||||
class _AnimatedCounterState extends State<AnimatedCounter>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<int> _animation;
|
||||
bool _hasAnimated = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.duration,
|
||||
);
|
||||
|
||||
_animation = IntTween(
|
||||
begin: 0,
|
||||
end: widget.endValue,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
|
||||
// Only start animation if startAnimation is true
|
||||
if (widget.startAnimation) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_controller.forward();
|
||||
_hasAnimated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedCounter oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
// Start animation when startAnimation changes to true
|
||||
if (!oldWidget.startAnimation && widget.startAnimation && !_hasAnimated) {
|
||||
_controller.forward();
|
||||
_hasAnimated = true;
|
||||
}
|
||||
|
||||
if (oldWidget.endValue != widget.endValue) {
|
||||
if (_hasAnimated) {
|
||||
_animation = IntTween(
|
||||
begin: _animation.value,
|
||||
end: widget.endValue,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
|
||||
_controller.forward(from: 0.0);
|
||||
} else if (widget.startAnimation) {
|
||||
_animation = IntTween(
|
||||
begin: 0,
|
||||
end: widget.endValue,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
|
||||
_controller.forward();
|
||||
_hasAnimated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return Text(
|
||||
"${widget.prefix}${_animation.value}",
|
||||
style: widget.style,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/repo/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
|
|
@ -56,42 +52,6 @@ class MessageDataController extends BaseController {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
/// get tokens from the server
|
||||
static Future<TokensResponseModel> _fetchTokens(
|
||||
String accessToken,
|
||||
TokensRequestModel request,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.tokenize,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
final TokensResponseModel response = TokensResponseModel.fromJson(
|
||||
jsonDecode(
|
||||
utf8.decode(res.bodyBytes).toString(),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.tokens.isEmpty) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception(
|
||||
"empty tokens in tokenize response return",
|
||||
),
|
||||
data: {
|
||||
"accessToken": accessToken,
|
||||
"request": request.toJson(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// get tokens from the server
|
||||
/// if repEventId is not null, send the tokens to the room
|
||||
Future<List<PangeaToken>> _getTokens({
|
||||
|
|
@ -99,9 +59,9 @@ class MessageDataController extends BaseController {
|
|||
required TokensRequestModel req,
|
||||
required Room? room,
|
||||
}) async {
|
||||
final TokensResponseModel res = await _fetchTokens(
|
||||
final TokensResponseModel res = await TokensRepo.get(
|
||||
_pangeaController.userController.accessToken,
|
||||
req,
|
||||
request: req,
|
||||
);
|
||||
if (repEventId != null && room != null) {
|
||||
room
|
||||
|
|
@ -234,9 +194,9 @@ class MessageDataController extends BaseController {
|
|||
required TokensRequestModel req,
|
||||
required Room room,
|
||||
}) async {
|
||||
final TokensResponseModel res = await _fetchTokens(
|
||||
final TokensResponseModel res = await TokensRepo.get(
|
||||
_pangeaController.userController.accessToken,
|
||||
req,
|
||||
request: req,
|
||||
);
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ class PangeaToken {
|
|||
OneConstructUse toVocabUse(
|
||||
ConstructUseTypeEnum type,
|
||||
ConstructUseMetaData metadata,
|
||||
int xp,
|
||||
) {
|
||||
return OneConstructUse(
|
||||
useType: type,
|
||||
|
|
@ -197,17 +198,19 @@ class PangeaToken {
|
|||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: metadata,
|
||||
category: pos,
|
||||
xp: xp,
|
||||
);
|
||||
}
|
||||
|
||||
List<OneConstructUse> allUses(
|
||||
ConstructUseTypeEnum type,
|
||||
ConstructUseMetaData metadata,
|
||||
int xp,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
if (!lemma.saveVocab) return uses;
|
||||
|
||||
uses.add(toVocabUse(type, metadata));
|
||||
uses.add(toVocabUse(type, metadata, xp));
|
||||
for (final morphFeature in morph.keys) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
|
|
@ -217,6 +220,7 @@ class PangeaToken {
|
|||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
category: morphFeature,
|
||||
xp: xp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -132,162 +134,138 @@ class PangeaRepresentation {
|
|||
)
|
||||
.toList();
|
||||
}
|
||||
for (final token in tokensToSave) {
|
||||
uses.addAll(
|
||||
_getUsesForToken(
|
||||
token,
|
||||
metadata,
|
||||
choreo: choreo,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
|
||||
/// Returns a [OneConstructUse] for the given [token]
|
||||
/// If there is no [choreo], the [token] is
|
||||
/// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language.
|
||||
/// Later on, we may want to consider putting it in some category of like 'pending'
|
||||
///
|
||||
/// For both vocab and morph constructs, we should
|
||||
/// 1) give wa if no assistance was used
|
||||
/// 2) give ga if IGC was used and
|
||||
/// 3) make no use if IT was used
|
||||
List<OneConstructUse> _getUsesForToken(
|
||||
PangeaToken token,
|
||||
ConstructUseMetaData metadata, {
|
||||
ChoreoRecord? choreo,
|
||||
}) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
final lemma = token.lemma;
|
||||
final content = token.text.content;
|
||||
|
||||
if (choreo == null) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.wa,
|
||||
lemma: token.pos,
|
||||
form: token.text.content,
|
||||
category: "POS",
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
|
||||
for (final entry in token.morph.entries) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.wa,
|
||||
lemma: entry.value,
|
||||
form: token.text.content,
|
||||
category: entry.key,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (lemma.saveVocab) {
|
||||
uses.add(
|
||||
token.toVocabUse(
|
||||
if (choreo == null ||
|
||||
(choreo.choreoSteps.isEmpty && choreo.itSteps.isEmpty)) {
|
||||
for (final token in tokensToSave) {
|
||||
uses.addAll(
|
||||
token.allUses(
|
||||
ConstructUseTypeEnum.wa,
|
||||
metadata,
|
||||
ConstructUseTypeEnum.wa.pointValue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
|
||||
for (final step in choreo.choreoSteps) {
|
||||
/// if 1) accepted match 2) token is in the replacement and 3) replacement
|
||||
/// is in the overall step text, then token was a ga
|
||||
final bool isAcceptedMatch =
|
||||
step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted;
|
||||
|
||||
// if the token was in an IT match, return no uses
|
||||
if (step.itStep != null) return [];
|
||||
|
||||
// if this step was not accepted, continue
|
||||
if (!isAcceptedMatch) continue;
|
||||
|
||||
if (isAcceptedMatch &&
|
||||
step.acceptedOrIgnoredMatch?.match.choices != null) {
|
||||
final choices = step.acceptedOrIgnoredMatch!.match.choices!;
|
||||
final bool stepContainedToken = choices.any(
|
||||
(choice) =>
|
||||
// if this choice contains the token's content
|
||||
choice.value.contains(content),
|
||||
);
|
||||
if (stepContainedToken) {
|
||||
// give ga if IGC was used
|
||||
uses.add(
|
||||
token.toVocabUse(
|
||||
ConstructUseTypeEnum.ga,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.ga,
|
||||
lemma: token.pos,
|
||||
form: token.text.content,
|
||||
category: "POS",
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
|
||||
for (final entry in token.morph.entries) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.ga,
|
||||
lemma: entry.value,
|
||||
form: token.text.content,
|
||||
category: entry.key,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
return uses;
|
||||
for (final token in tokensToSave) {
|
||||
ChoreoRecordStep? tokenStep;
|
||||
for (final step in choreo.choreoSteps) {
|
||||
final igcMatch = step.acceptedOrIgnoredMatch;
|
||||
final itStep = step.itStep;
|
||||
if (itStep == null && igcMatch == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final choices = step.choices;
|
||||
if (choices == null || choices.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final stepContainsToken = choices.any(
|
||||
(choice) => choice.contains(token.text.content),
|
||||
);
|
||||
|
||||
// if the step contains the token, and the token hasn't been assigned a step
|
||||
// (or the assigned step is an IGC step, but an IT step contains the token)
|
||||
// then assign the token to the step
|
||||
if (stepContainsToken &&
|
||||
(tokenStep == null ||
|
||||
(tokenStep.itStep == null && step.itStep != null))) {
|
||||
tokenStep = step;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenStep == null ||
|
||||
tokenStep.acceptedOrIgnoredMatch?.status ==
|
||||
PangeaMatchStatus.automatic) {
|
||||
// if the token wasn't found in any IT or IGC step, so it was wa
|
||||
uses.addAll(
|
||||
token.allUses(
|
||||
ConstructUseTypeEnum.wa,
|
||||
metadata,
|
||||
ConstructUseTypeEnum.wa.pointValue,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokenStep.acceptedOrIgnoredMatch != null &&
|
||||
tokenStep.acceptedOrIgnoredMatch?.status !=
|
||||
PangeaMatchStatus.accepted) {
|
||||
uses.addAll(
|
||||
token.allUses(
|
||||
ConstructUseTypeEnum.ga,
|
||||
metadata,
|
||||
0,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokenStep.itStep != null) {
|
||||
final selectedChoices = tokenStep.itStep!.continuances
|
||||
.where((choice) => choice.wasClicked)
|
||||
.length;
|
||||
if (selectedChoices == 0) {
|
||||
ErrorHandler.logError(
|
||||
e: "No selected choices for IT step",
|
||||
data: {
|
||||
"token": token.text.content,
|
||||
"step": tokenStep.toJson(),
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final corITPoints = ConstructUseTypeEnum.corIt.pointValue;
|
||||
final incITPoints = ConstructUseTypeEnum.incIt.pointValue;
|
||||
final xp = max(
|
||||
0,
|
||||
corITPoints + (incITPoints * (selectedChoices - 1)),
|
||||
);
|
||||
|
||||
uses.addAll(
|
||||
token.allUses(
|
||||
ConstructUseTypeEnum.ta,
|
||||
metadata,
|
||||
xp,
|
||||
),
|
||||
);
|
||||
} else if (tokenStep.acceptedOrIgnoredMatch!.match.choices != null) {
|
||||
final selectedChoices = tokenStep.acceptedOrIgnoredMatch!.match.choices!
|
||||
.where((choice) => choice.selected)
|
||||
.length;
|
||||
if (selectedChoices == 0) {
|
||||
ErrorHandler.logError(
|
||||
e: "No selected choices for IGC step",
|
||||
data: {
|
||||
"token": token.text.content,
|
||||
"step": tokenStep.toJson(),
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final corIGCPoints = ConstructUseTypeEnum.corIGC.pointValue;
|
||||
final incIGCPoints = ConstructUseTypeEnum.incIGC.pointValue;
|
||||
final xp = max(
|
||||
0,
|
||||
corIGCPoints + (incIGCPoints * (selectedChoices - 1)),
|
||||
);
|
||||
|
||||
uses.addAll(
|
||||
token.allUses(
|
||||
ConstructUseTypeEnum.ga,
|
||||
metadata,
|
||||
xp,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.wa,
|
||||
lemma: token.pos,
|
||||
form: token.text.content,
|
||||
category: "POS",
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
|
||||
// the token wasn't found in any IT or IGC step, so it was wa
|
||||
for (final entry in token.morph.entries) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.wa,
|
||||
lemma: entry.value,
|
||||
form: token.text.content,
|
||||
category: entry.key,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (lemma.saveVocab) {
|
||||
uses.add(
|
||||
token.toVocabUse(
|
||||
ConstructUseTypeEnum.wa,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -103,13 +104,15 @@ class PracticeActivityModel {
|
|||
// "onMultipleChoiceSelect: ${choice.form} ${responseUseType(choice)}",
|
||||
// );
|
||||
|
||||
final constructUseType =
|
||||
practiceTarget.record.responses.last.useType(activityType);
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: event?.eventId,
|
||||
roomId: event?.room.id,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType: practiceTarget.record.responses.last.useType(activityType),
|
||||
useType: constructUseType,
|
||||
lemma: choice.form.cId.lemma,
|
||||
constructType: choice.form.cId.type,
|
||||
metadata: ConstructUseMetaData(
|
||||
|
|
@ -119,6 +122,7 @@ class PracticeActivityModel {
|
|||
),
|
||||
category: choice.form.cId.category,
|
||||
form: choice.form.form,
|
||||
xp: constructUseType.pointValue,
|
||||
),
|
||||
],
|
||||
targetID: targetTokens.first.text.uniqueKey,
|
||||
|
|
@ -179,14 +183,15 @@ class PracticeActivityModel {
|
|||
|
||||
// we don't take off points for incorrect emoji matches
|
||||
if (ActivityTypeEnum.emoji != activityType || isCorrect) {
|
||||
final constructUseType =
|
||||
practiceTarget.record.responses.last.useType(activityType);
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: event?.eventId,
|
||||
roomId: event?.room.id,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType:
|
||||
practiceTarget.record.responses.last.useType(activityType),
|
||||
useType: constructUseType,
|
||||
lemma: token.lemma.text,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: ConstructUseMetaData(
|
||||
|
|
@ -197,6 +202,7 @@ class PracticeActivityModel {
|
|||
category: token.pos,
|
||||
// in the case of a wrong answer, the cId doesn't match the token
|
||||
form: token.text.content,
|
||||
xp: constructUseType.pointValue,
|
||||
),
|
||||
],
|
||||
targetID: token.text.uniqueKey,
|
||||
|
|
|
|||
|
|
@ -220,35 +220,41 @@ class ActivityRecordResponse {
|
|||
case ActivityTypeEnum.wordFocusListening:
|
||||
case ActivityTypeEnum.lemmaId:
|
||||
final token = practiceActivity.targetTokens.first;
|
||||
final constructUseType = useType(practiceActivity.activityType);
|
||||
return [
|
||||
OneConstructUse(
|
||||
lemma: token.lemma.text,
|
||||
form: token.text.content,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
useType: useType(practiceActivity.activityType),
|
||||
useType: constructUseType,
|
||||
metadata: metadata,
|
||||
category: token.pos,
|
||||
xp: constructUseType.pointValue,
|
||||
),
|
||||
];
|
||||
case ActivityTypeEnum.messageMeaning:
|
||||
final constructUseType = useType(practiceActivity.activityType);
|
||||
return practiceActivity.targetTokens
|
||||
.expand(
|
||||
(t) => t.allUses(
|
||||
useType(practiceActivity.activityType),
|
||||
constructUseType,
|
||||
metadata,
|
||||
constructUseType.pointValue,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
case ActivityTypeEnum.hiddenWordListening:
|
||||
final constructUseType = useType(practiceActivity.activityType);
|
||||
return practiceActivity.targetTokens
|
||||
.map(
|
||||
(token) => OneConstructUse(
|
||||
lemma: token.lemma.text,
|
||||
form: token.text.content,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
useType: useType(practiceActivity.activityType),
|
||||
useType: constructUseType,
|
||||
metadata: metadata,
|
||||
category: token.pos,
|
||||
xp: constructUseType.pointValue,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
|
@ -273,13 +279,15 @@ class ActivityRecordResponse {
|
|||
);
|
||||
return null;
|
||||
}
|
||||
final constructUseType = useType(practiceActivity.activityType);
|
||||
return OneConstructUse(
|
||||
lemma: tag,
|
||||
form: practiceActivity.targetTokens.first.text.content,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
useType: useType(practiceActivity.activityType),
|
||||
useType: constructUseType,
|
||||
metadata: metadata,
|
||||
category: practiceActivity.morphFeature!,
|
||||
xp: constructUseType.pointValue,
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
|
|
@ -9,7 +12,6 @@ import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class PracticeSelection {
|
||||
late String _userL2;
|
||||
|
|
@ -42,8 +44,6 @@ class PracticeSelection {
|
|||
_tokens.any((t) => t.lemma.saveVocab) &&
|
||||
langCode.split("-")[0] == _userL2.split("-")[0];
|
||||
|
||||
String get messageText => PangeaToken.reconstructText(tokens);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'lang_code': langCode,
|
||||
|
|
|
|||
|
|
@ -68,13 +68,6 @@ class MessageMorphInputBarContentState
|
|||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
String? get _correctChoice {
|
||||
return widget.activity.multipleChoiceContent?.choices
|
||||
.firstWhereOrNull((choice) {
|
||||
return widget.activity.practiceTarget.wasCorrectChoice(choice) == true;
|
||||
});
|
||||
}
|
||||
|
||||
TextStyle? textStyle(BuildContext context) => overlay.maxWidth > 600
|
||||
? Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
|||
94
pubspec.lock
94
pubspec.lock
|
|
@ -114,10 +114,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
version: "2.11.0"
|
||||
audio_session:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -218,10 +218,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -266,10 +266,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -306,18 +306,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
version: "1.19.0"
|
||||
colorize:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -506,10 +506,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.1"
|
||||
fcm_shared_isolate:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -529,10 +529,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
version: "7.0.0"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1390,18 +1390,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "10.0.7"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.8"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1478,10 +1478,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1511,10 +1511,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.15.0"
|
||||
mgrs_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1695,10 +1695,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1823,10 +1823,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
version: "3.1.5"
|
||||
platform_detect:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1903,10 +1903,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d"
|
||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
version: "5.0.2"
|
||||
proj4dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2284,10 +2284,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2364,26 +2364,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
version: "1.12.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.3.0"
|
||||
string_validator:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2436,34 +2436,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
test:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
|
||||
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.15"
|
||||
version: "1.25.8"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.3"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.8"
|
||||
version: "0.6.5"
|
||||
text_to_speech:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -2757,10 +2757,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
version: "14.3.0"
|
||||
wakelock_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -2890,5 +2890,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue