add draft construct uses while using language assistance, added morphs to learning progress indicators
This commit is contained in:
parent
75234e6a6f
commit
54040d841a
15 changed files with 328 additions and 274 deletions
|
|
@ -4017,7 +4017,7 @@
|
|||
"conversationBotTextAdventureZone_title": "Text Adventure",
|
||||
"conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions",
|
||||
"conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions",
|
||||
"conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions",
|
||||
"conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions",
|
||||
"studentAnalyticsNotAvailable": "Student data not currently available",
|
||||
"roomDataMissing": "Some data may be missing from rooms in which you are not a member.",
|
||||
"updatePhoneOS": "You may need to update your device's OS version.",
|
||||
|
|
@ -4125,5 +4125,6 @@
|
|||
"wordsUsed": "Words Used",
|
||||
"errorTypes": "Error Types",
|
||||
"level": "Level",
|
||||
"canceledSend": "Canceled send"
|
||||
"canceledSend": "Canceled send",
|
||||
"morphsUsed": "Morphs Used"
|
||||
}
|
||||
|
|
@ -470,7 +470,7 @@ class ChatView extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
const PointsGainedAnimation(
|
||||
color: Colors.blue,
|
||||
gainColor: Colors.blue,
|
||||
),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
|
|
|
|||
|
|
@ -411,7 +411,6 @@ class Choreographer {
|
|||
choreoRecord = ChoreoRecord.newRecord;
|
||||
itController.clear();
|
||||
igc.clear();
|
||||
pangeaController.myAnalytics.clearDraftConstructUses(roomId);
|
||||
// errorService.clear();
|
||||
_resetDebounceTimer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import 'dart:developer';
|
|||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -285,6 +287,20 @@ 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.myAnalytics.addDraftUses(
|
||||
ignoredTokens ?? [],
|
||||
choreographer.roomId,
|
||||
ConstructUseTypeEnum.ignIt,
|
||||
);
|
||||
|
||||
Future.delayed(
|
||||
const Duration(
|
||||
milliseconds: ChoreoConstants.millisecondsToDisplayFeedback,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ 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/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -76,7 +78,12 @@ class ITBarState extends State<ITBar> {
|
|||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
const Positioned(
|
||||
top: 60,
|
||||
child: PointsGainedAnimation(),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
|
|
@ -334,6 +341,15 @@ class ITChoices extends StatelessWidget {
|
|||
continuance.feedbackText(context),
|
||||
);
|
||||
}
|
||||
if (!continuance.wasClicked) {
|
||||
controller.choreographer.pangeaController.myAnalytics.addDraftUses(
|
||||
continuance.tokens,
|
||||
controller.choreographer.roomId,
|
||||
continuance.level > 1
|
||||
? ConstructUseTypeEnum.incIt
|
||||
: ConstructUseTypeEnum.corIt,
|
||||
);
|
||||
}
|
||||
controller.currentITStep!.continuances[index].wasClicked = true;
|
||||
controller.choreographer.setState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ class MyAnalyticsController extends BaseController {
|
|||
void onMessageSent(Map<String, dynamic> data) {
|
||||
// cancel the last timer that was set on message event and
|
||||
// reset it to fire after _minutesBeforeUpdate minutes
|
||||
debugPrint("ONE MESSAGE SENT");
|
||||
_updateTimer?.cancel();
|
||||
_updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () {
|
||||
debugPrint("timer fired, updating analytics");
|
||||
|
|
@ -138,12 +137,11 @@ class MyAnalyticsController extends BaseController {
|
|||
timeStamp: DateTime.now(),
|
||||
);
|
||||
|
||||
final List<OneConstructUse> constructs = [];
|
||||
final List<OneConstructUse> constructs = getDraftUses(roomID);
|
||||
|
||||
if (eventType == EventTypes.Message) {
|
||||
final grammarConstructs =
|
||||
choreo?.grammarConstructUses(metadata: metadata);
|
||||
final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata);
|
||||
final vocabUses = tokensSent != null
|
||||
? originalSent?.vocabUses(
|
||||
choreo: choreo,
|
||||
|
|
@ -153,7 +151,6 @@ class MyAnalyticsController extends BaseController {
|
|||
: null;
|
||||
constructs.addAll([
|
||||
...(grammarConstructs ?? []),
|
||||
...(itConstructs ?? []),
|
||||
...(vocabUses ?? []),
|
||||
]);
|
||||
}
|
||||
|
|
@ -171,35 +168,18 @@ class MyAnalyticsController extends BaseController {
|
|||
.filterConstructs(unfilteredConstructs: constructs)
|
||||
.then((filtered) {
|
||||
if (filtered.isEmpty) return;
|
||||
filtered.addAll(getDraftUses(roomID));
|
||||
final level = _pangeaController.analytics.level;
|
||||
addLocalMessage(eventID, filtered).then(
|
||||
(_) => afterAddLocalMessages(level),
|
||||
(_) {
|
||||
clearDraftUses(roomID);
|
||||
afterAddLocalMessages(level);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Called when the user selects a replacement during IGC
|
||||
void onReplacementSelected(
|
||||
List<PangeaToken> tokens,
|
||||
String roomID,
|
||||
bool isBestCorrection,
|
||||
) {
|
||||
final useType = isBestCorrection
|
||||
? ConstructUseTypeEnum.corIGC
|
||||
: ConstructUseTypeEnum.incIGC;
|
||||
setDraftConstructUses(tokens, roomID, useType);
|
||||
}
|
||||
|
||||
/// Called when the user ignores a match during IGC
|
||||
void onIgnoreMatch(
|
||||
List<PangeaToken> tokens,
|
||||
String roomID,
|
||||
) {
|
||||
const useType = ConstructUseTypeEnum.ignIGC;
|
||||
setDraftConstructUses(tokens, roomID, useType);
|
||||
}
|
||||
|
||||
void setDraftConstructUses(
|
||||
void addDraftUses(
|
||||
List<PangeaToken> tokens,
|
||||
String roomID,
|
||||
ConstructUseTypeEnum useType,
|
||||
|
|
@ -220,19 +200,41 @@ class MyAnalyticsController extends BaseController {
|
|||
),
|
||||
)
|
||||
.toList();
|
||||
addLocalMessage('draft$roomID', uses);
|
||||
|
||||
final List<String> morphs = tokens
|
||||
.map((t) => t.morph.values)
|
||||
.expand((m) => m)
|
||||
.cast<String>()
|
||||
.toList();
|
||||
|
||||
uses.addAll(
|
||||
morphs.map(
|
||||
(morph) => OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: morph,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final level = _pangeaController.analytics.level;
|
||||
addLocalMessage('draft$roomID', uses).then(
|
||||
(_) => afterAddLocalMessages(level),
|
||||
);
|
||||
}
|
||||
|
||||
void clearDraftConstructUses(String roomID) {
|
||||
List<OneConstructUse> getDraftUses(String roomID) {
|
||||
final currentCache = _pangeaController.analytics.messagesSinceUpdate;
|
||||
return currentCache['draft$roomID'] ?? [];
|
||||
}
|
||||
|
||||
void clearDraftUses(String roomID) {
|
||||
final currentCache = _pangeaController.analytics.messagesSinceUpdate;
|
||||
currentCache.remove('draft$roomID');
|
||||
setMessagesSinceUpdate(currentCache);
|
||||
}
|
||||
|
||||
/// Called when the user selects a continuance during IT
|
||||
/// TODO implement
|
||||
void onSelectContinuance() {}
|
||||
|
||||
/// Add a list of construct uses for a new message to the local
|
||||
/// cache of recently sent messages
|
||||
Future<void> addLocalMessage(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
enum ConstructTypeEnum {
|
||||
grammar,
|
||||
vocab,
|
||||
morph,
|
||||
}
|
||||
|
||||
extension ConstructExtension on ConstructTypeEnum {
|
||||
|
|
@ -10,6 +11,8 @@ extension ConstructExtension on ConstructTypeEnum {
|
|||
return 'grammar';
|
||||
case ConstructTypeEnum.vocab:
|
||||
return 'vocab';
|
||||
case ConstructTypeEnum.morph:
|
||||
return 'morph';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +26,9 @@ class ConstructTypeUtil {
|
|||
case 'v':
|
||||
case 'vocab':
|
||||
return ConstructTypeEnum.vocab;
|
||||
case 'm':
|
||||
case 'morph':
|
||||
return ConstructTypeEnum.morph;
|
||||
default:
|
||||
return ConstructTypeEnum.vocab;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
enum ProgressIndicatorEnum {
|
||||
level,
|
||||
wordsUsed,
|
||||
errorTypes,
|
||||
morphsUsed,
|
||||
}
|
||||
|
||||
extension ProgressIndicatorsExtension on ProgressIndicatorEnum {
|
||||
|
|
@ -14,6 +16,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum {
|
|||
return Icons.text_fields_outlined;
|
||||
case ProgressIndicatorEnum.errorTypes:
|
||||
return Icons.error_outline;
|
||||
case ProgressIndicatorEnum.morphsUsed:
|
||||
return Symbols.toys_and_games;
|
||||
case ProgressIndicatorEnum.level:
|
||||
return Icons.star;
|
||||
}
|
||||
|
|
@ -36,8 +40,10 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum {
|
|||
return Theme.of(context).brightness == Brightness.dark
|
||||
? const Color.fromARGB(255, 250, 220, 129)
|
||||
: const Color.fromARGB(255, 255, 208, 67);
|
||||
default:
|
||||
return Theme.of(context).textTheme.bodyLarge!.color ?? Colors.blueGrey;
|
||||
case ProgressIndicatorEnum.morphsUsed:
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? const Color.fromARGB(255, 169, 183, 237)
|
||||
: const Color.fromARGB(255, 38, 59, 141);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +55,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum {
|
|||
return L10n.of(context)!.errorTypes;
|
||||
case ProgressIndicatorEnum.level:
|
||||
return L10n.of(context)!.level;
|
||||
case ProgressIndicatorEnum.morphsUsed:
|
||||
return L10n.of(context)!.morphsUsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -665,12 +665,10 @@ class PangeaMessageEvent {
|
|||
l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!);
|
||||
|
||||
/// all construct uses for the message, including vocab and grammar
|
||||
List<OneConstructUse> get allConstructUses =>
|
||||
[..._grammarConstructUses, ..._vocabUses, ..._itStepsToConstructUses];
|
||||
|
||||
/// Returns a list of [OneConstructUse] from itSteps
|
||||
List<OneConstructUse> get _itStepsToConstructUses =>
|
||||
originalSent?.choreo?.itStepsToConstructUses(event: event) ?? [];
|
||||
List<OneConstructUse> get allConstructUses => [
|
||||
..._grammarConstructUses,
|
||||
..._vocabUses,
|
||||
];
|
||||
|
||||
/// get construct uses of type vocab for the message
|
||||
List<OneConstructUse> get _vocabUses {
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ class OneConstructUse {
|
|||
OneConstructUse({
|
||||
required this.useType,
|
||||
required this.lemma,
|
||||
required this.form,
|
||||
required this.constructType,
|
||||
required this.metadata,
|
||||
this.form,
|
||||
this.id,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'it_step.dart';
|
||||
|
|
@ -155,65 +153,6 @@ class ChoreoRecord {
|
|||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
/// Returns a list of [OneConstructUse] from itSteps for which the continuance
|
||||
/// was selected or ignored. Correct selections are considered in the tokens
|
||||
/// flow. Once all continuances have lemmas, we can do both correct and incorrect
|
||||
/// in this flow. It actually doesn't do anything at all right now, because the
|
||||
/// choregrapher is not returning lemmas for continuances. This is a TODO.
|
||||
/// So currently only the lemmas can be gotten from the tokens for choices that
|
||||
/// are actually in the final message.
|
||||
List<OneConstructUse> itStepsToConstructUses({
|
||||
Event? event,
|
||||
ConstructUseMetaData? metadata,
|
||||
}) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
if (event == null && metadata == null) {
|
||||
return uses;
|
||||
}
|
||||
|
||||
metadata ??= ConstructUseMetaData(
|
||||
roomId: event!.roomId!,
|
||||
eventId: event.eventId,
|
||||
timeStamp: event.originServerTs,
|
||||
);
|
||||
|
||||
for (final itStep in itSteps) {
|
||||
for (final continuance in itStep.continuances) {
|
||||
final List<PangeaToken> tokensToSave =
|
||||
continuance.tokens.where((t) => t.lemma.saveVocab).toList();
|
||||
|
||||
if (finalMessage.contains(continuance.text)) {
|
||||
continue;
|
||||
}
|
||||
if (continuance.wasClicked) {
|
||||
//PTODO - account for end of flow score
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
token.lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.incIt,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
token.lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.ignIt,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
}
|
||||
|
||||
/// A new ChoreoRecordStep is saved in the following cases:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
||||
|
|
@ -118,8 +119,8 @@ class PangeaRepresentation {
|
|||
final tokensToSave =
|
||||
tokens.where((token) => token.lemma.saveVocab).toList();
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
getVocabUseForToken(
|
||||
uses.addAll(
|
||||
getUsesForToken(
|
||||
token,
|
||||
metadata,
|
||||
choreo: choreo,
|
||||
|
|
@ -137,21 +138,38 @@ class PangeaRepresentation {
|
|||
/// If the [token] is in the [choreo.acceptedOrIgnoredMatch], it is considered to be a [ConstructUseTypeEnum.ga].
|
||||
/// If the [token] is in the [choreo.acceptedOrIgnoredMatch.choices], it is considered to be a [ConstructUseTypeEnum.corIt].
|
||||
/// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa].
|
||||
OneConstructUse getVocabUseForToken(
|
||||
List<OneConstructUse> getUsesForToken(
|
||||
PangeaToken token,
|
||||
ConstructUseMetaData metadata, {
|
||||
ChoreoRecord? choreo,
|
||||
}) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
final lemma = token.lemma;
|
||||
final content = token.text.content;
|
||||
final morphs = token.morph.values.toList();
|
||||
|
||||
if (choreo == null) {
|
||||
final bool inUserL2 = langCode ==
|
||||
MatrixState.pangeaController.languageController.activeL2Code();
|
||||
return lemma.toVocabUse(
|
||||
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
|
||||
metadata,
|
||||
final useType =
|
||||
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk;
|
||||
uses.addAll(
|
||||
morphs.map(
|
||||
(morph) => OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: morph,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
),
|
||||
);
|
||||
uses.add(
|
||||
lemma.toVocabUse(
|
||||
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
return uses;
|
||||
}
|
||||
|
||||
for (final step in choreo.choreoSteps) {
|
||||
|
|
@ -174,10 +192,7 @@ class PangeaRepresentation {
|
|||
step.text.contains(choice.value),
|
||||
);
|
||||
if (stepContainedToken) {
|
||||
return lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.ga,
|
||||
metadata,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,16 +200,27 @@ class PangeaRepresentation {
|
|||
final bool pickedThroughIT =
|
||||
step.itStep!.chosenContinuance!.text.contains(content);
|
||||
if (pickedThroughIT) {
|
||||
return lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.corIt,
|
||||
metadata,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
return lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.wa,
|
||||
metadata,
|
||||
|
||||
uses.addAll(
|
||||
morphs.map(
|
||||
(morph) => OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.wa,
|
||||
lemma: morph,
|
||||
constructType: ConstructTypeEnum.morph,
|
||||
metadata: metadata,
|
||||
),
|
||||
),
|
||||
);
|
||||
uses.add(
|
||||
lemma.toVocabUse(
|
||||
ConstructUseTypeEnum.wa,
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
return uses;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class PointsGainedAnimation extends StatefulWidget {
|
||||
final Color? color;
|
||||
const PointsGainedAnimation({super.key, this.color});
|
||||
final Color? gainColor;
|
||||
final Color? loseColor;
|
||||
const PointsGainedAnimation({
|
||||
super.key,
|
||||
this.gainColor,
|
||||
this.loseColor = Colors.red,
|
||||
});
|
||||
|
||||
@override
|
||||
PointsGainedAnimationState createState() => PointsGainedAnimationState();
|
||||
|
|
@ -66,7 +71,7 @@ class PointsGainedAnimationState extends State<PointsGainedAnimation>
|
|||
|
||||
void _showPointsGained(List<OneConstructUse> constructs) {
|
||||
setState(() => _addedPoints = (_currentXP ?? 0) - (_prevXP ?? 0));
|
||||
if (_prevXP != _currentXP && !_controller.isAnimating) {
|
||||
if (_prevXP != _currentXP) {
|
||||
_controller.reset();
|
||||
_controller.forward();
|
||||
}
|
||||
|
|
@ -82,18 +87,20 @@ class PointsGainedAnimationState extends State<PointsGainedAnimation>
|
|||
Widget build(BuildContext context) {
|
||||
if (!animate) return const SizedBox();
|
||||
|
||||
final textColor = _addedPoints! > 0 ? widget.gainColor : widget.loseColor;
|
||||
|
||||
return SlideTransition(
|
||||
position: _offsetAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Text(
|
||||
'+$_addedPoints',
|
||||
'${_addedPoints! > 0 ? '+' : ''}$_addedPoints',
|
||||
style: BotStyle.text(
|
||||
context,
|
||||
big: true,
|
||||
setColor: widget.color == null,
|
||||
setColor: textColor == null,
|
||||
existingStyle: TextStyle(
|
||||
color: widget.color,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ class LearningProgressIndicatorsState
|
|||
/// Grammar constructs model
|
||||
ConstructListModel? errors;
|
||||
|
||||
/// Morph constructs model
|
||||
ConstructListModel? morphs;
|
||||
|
||||
bool loading = true;
|
||||
|
||||
int get serverXP => _pangeaController.analytics.serverXP;
|
||||
|
|
@ -78,6 +81,10 @@ class LearningProgressIndicatorsState
|
|||
type: ConstructTypeEnum.grammar,
|
||||
uses: constructs,
|
||||
);
|
||||
morphs = ConstructListModel(
|
||||
type: ConstructTypeEnum.morph,
|
||||
uses: constructs,
|
||||
);
|
||||
|
||||
if (loading) loading = false;
|
||||
if (mounted) setState(() {});
|
||||
|
|
@ -90,6 +97,8 @@ class LearningProgressIndicatorsState
|
|||
return words?.lemmas.length;
|
||||
case ProgressIndicatorEnum.errorTypes:
|
||||
return errors?.lemmas.length;
|
||||
case ProgressIndicatorEnum.morphsUsed:
|
||||
return morphs?.lemmas.length;
|
||||
case ProgressIndicatorEnum.level:
|
||||
return level;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/span_data_type.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/models/span_data.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/match_copy.dart';
|
||||
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -120,6 +123,16 @@ class SpanCardState extends State<SpanCard> {
|
|||
Future<void> onChoiceSelect(int index) async {
|
||||
selectedChoiceIndex = index;
|
||||
if (selectedChoice != null) {
|
||||
if (!selectedChoice!.selected) {
|
||||
MatrixState.pangeaController.myAnalytics.addDraftUses(
|
||||
selectedChoice!.tokens,
|
||||
widget.roomId,
|
||||
selectedChoice!.isBestCorrection
|
||||
? ConstructUseTypeEnum.corIGC
|
||||
: ConstructUseTypeEnum.incIGC,
|
||||
);
|
||||
}
|
||||
|
||||
selectedChoice!.timestamp = DateTime.now();
|
||||
selectedChoice!.selected = true;
|
||||
setState(
|
||||
|
|
@ -130,41 +143,44 @@ class SpanCardState extends State<SpanCard> {
|
|||
}
|
||||
}
|
||||
|
||||
void onReplaceSelected() {
|
||||
if (selectedChoice != null) {
|
||||
final tokens = widget.scm.choreographer.igc.igcTextData
|
||||
?.matchTokens(widget.scm.matchIndex) ??
|
||||
[];
|
||||
MatrixState.pangeaController.myAnalytics.onReplacementSelected(
|
||||
tokens,
|
||||
widget.roomId,
|
||||
selectedChoice!.isBestCorrection,
|
||||
);
|
||||
}
|
||||
/// Returns the list of choices that are not selected
|
||||
List<SpanChoice>? get ignoredMatches => widget.scm.pangeaMatch?.match.choices
|
||||
?.where((choice) => !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.myAnalytics.addDraftUses(
|
||||
ignoredTokens ?? [],
|
||||
widget.roomId,
|
||||
ConstructUseTypeEnum.ignIGC,
|
||||
);
|
||||
}
|
||||
|
||||
void onReplaceSelected() {
|
||||
addIgnoredTokenUses();
|
||||
widget.scm
|
||||
.onReplacementSelect(
|
||||
matchIndex: widget.scm.matchIndex,
|
||||
choiceIndex: selectedChoiceIndex!,
|
||||
)
|
||||
.then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
matchIndex: widget.scm.matchIndex,
|
||||
choiceIndex: selectedChoiceIndex!,
|
||||
)
|
||||
.then((value) => setState(() {}));
|
||||
}
|
||||
|
||||
void onIgnoreMatch() {
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
addIgnoredTokenUses();
|
||||
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() {
|
||||
widget.scm.onIgnore();
|
||||
final tokens = widget.scm.choreographer.igc.igcTextData
|
||||
?.matchTokens(widget.scm.matchIndex) ??
|
||||
[];
|
||||
MatrixState.pangeaController.myAnalytics.onIgnoreMatch(
|
||||
tokens,
|
||||
widget.roomId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -205,142 +221,153 @@ class WordMatchContent extends StatelessWidget {
|
|||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
try {
|
||||
return Column(
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
// if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
CardHeader(
|
||||
text: controller.error?.toString() ?? matchCopy.title,
|
||||
botExpression: controller.error == null
|
||||
? controller.currentExpression
|
||||
: BotExpression.addled,
|
||||
const Positioned(
|
||||
top: 40,
|
||||
child: PointsGainedAnimation(),
|
||||
),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// const SizedBox(height: 10.0),
|
||||
// if (matchCopy.description != null)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(),
|
||||
// child: Text(
|
||||
// matchCopy.description!,
|
||||
// style: BotStyle.text(context),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 8),
|
||||
if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
ChoicesArray(
|
||||
originalSpan:
|
||||
controller.widget.scm.pangeaMatch!.matchContent,
|
||||
isLoading: controller.fetchingData,
|
||||
choices:
|
||||
controller.widget.scm.pangeaMatch!.match.choices
|
||||
?.map(
|
||||
(e) => Choice(
|
||||
text: e.value,
|
||||
color: e.selected ? e.type.color : null,
|
||||
isGold: e.type.name == 'bestCorrection',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onPressed: controller.onChoiceSelect,
|
||||
uniqueKeyForLayerLink: (int index) => "wordMatch$index",
|
||||
selectedChoiceIndex: controller.selectedChoiceIndex,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
PromptAndFeedback(controller: controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
// if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
CardHeader(
|
||||
text: controller.error?.toString() ?? matchCopy.title,
|
||||
botExpression: controller.error == null
|
||||
? controller.currentExpression
|
||||
: BotExpression.addled,
|
||||
),
|
||||
Expanded(
|
||||
child: Opacity(
|
||||
opacity: 0.8,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
AppConfig.primaryColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
onPressed: controller.onIgnoreMatch,
|
||||
child: Center(
|
||||
child: Text(L10n.of(context)!.ignoreInThisText),
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// const SizedBox(height: 10.0),
|
||||
// if (matchCopy.description != null)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(),
|
||||
// child: Text(
|
||||
// matchCopy.description!,
|
||||
// style: BotStyle.text(context),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 8),
|
||||
if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
ChoicesArray(
|
||||
originalSpan:
|
||||
controller.widget.scm.pangeaMatch!.matchContent,
|
||||
isLoading: controller.fetchingData,
|
||||
choices:
|
||||
controller.widget.scm.pangeaMatch!.match.choices
|
||||
?.map(
|
||||
(e) => Choice(
|
||||
text: e.value,
|
||||
color: e.selected ? e.type.color : null,
|
||||
isGold: e.type.name == 'bestCorrection',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onPressed: controller.onChoiceSelect,
|
||||
uniqueKeyForLayerLink: (int index) =>
|
||||
"wordMatch$index",
|
||||
selectedChoiceIndex: controller.selectedChoiceIndex,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
PromptAndFeedback(controller: controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
Expanded(
|
||||
child: Opacity(
|
||||
opacity: controller.selectedChoiceIndex != null ? 1.0 : 0.5,
|
||||
child: TextButton(
|
||||
onPressed: controller.selectedChoiceIndex != null
|
||||
? controller.onReplaceSelected
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
(controller.selectedChoice != null
|
||||
? controller.selectedChoice!.color
|
||||
: AppConfig.primaryColor)
|
||||
.withOpacity(0.2),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Opacity(
|
||||
opacity: 0.8,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
AppConfig.primaryColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
onPressed: controller.onIgnoreMatch,
|
||||
child: Center(
|
||||
child: Text(L10n.of(context)!.ignoreInThisText),
|
||||
),
|
||||
// Outline if Replace button enabled
|
||||
side: controller.selectedChoice != null
|
||||
? WidgetStateProperty.all(
|
||||
BorderSide(
|
||||
color: controller.selectedChoice!.color,
|
||||
style: BorderStyle.solid,
|
||||
width: 2.0,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(L10n.of(context)!.replace),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const SizedBox(width: 10),
|
||||
if (!controller.widget.scm.pangeaMatch!.isITStart)
|
||||
Expanded(
|
||||
child: Opacity(
|
||||
opacity:
|
||||
controller.selectedChoiceIndex != null ? 1.0 : 0.5,
|
||||
child: TextButton(
|
||||
onPressed: controller.selectedChoiceIndex != null
|
||||
? controller.onReplaceSelected
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
(controller.selectedChoice != null
|
||||
? controller.selectedChoice!.color
|
||||
: AppConfig.primaryColor)
|
||||
.withOpacity(0.2),
|
||||
),
|
||||
// Outline if Replace button enabled
|
||||
side: controller.selectedChoice != null
|
||||
? WidgetStateProperty.all(
|
||||
BorderSide(
|
||||
color: controller.selectedChoice!.color,
|
||||
style: BorderStyle.solid,
|
||||
width: 2.0,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(L10n.of(context)!.replace),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (controller.widget.scm.pangeaMatch!.isITStart)
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => controller.widget.scm.onITStart(),
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
(AppConfig.primaryColor).withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
child: Text(L10n.of(context)!.helpMeTranslate),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.widget.scm.pangeaMatch!.isITStart)
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => controller.widget.scm.onITStart(),
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
(AppConfig.primaryColor).withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
child: Text(L10n.of(context)!.helpMeTranslate),
|
||||
),
|
||||
DontShowSwitchListTile(
|
||||
controller: pangeaController,
|
||||
onSwitch: (bool value) {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.itAutoPlay = value;
|
||||
return profile;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.widget.scm.pangeaMatch!.isITStart)
|
||||
DontShowSwitchListTile(
|
||||
controller: pangeaController,
|
||||
onSwitch: (bool value) {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.itAutoPlay = value;
|
||||
return profile;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue