add draft construct uses while using language assistance, added morphs to learning progress indicators

This commit is contained in:
ggurdin 2024-08-21 14:01:24 -04:00
parent 75234e6a6f
commit 54040d841a
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
15 changed files with 328 additions and 274 deletions

View file

@ -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"
}

View file

@ -470,7 +470,7 @@ class ChatView extends StatelessWidget {
Row(
children: [
const PointsGainedAnimation(
color: Colors.blue,
gainColor: Colors.blue,
),
ChatFloatingActionButton(
controller: controller,

View file

@ -411,7 +411,6 @@ class Choreographer {
choreoRecord = ChoreoRecord.newRecord;
itController.clear();
igc.clear();
pangeaController.myAnalytics.clearDraftConstructUses(roomId);
// errorService.clear();
_resetDebounceTimer();
}

View file

@ -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,

View file

@ -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();
}

View file

@ -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(

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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 {

View file

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

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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,
),
),
),

View file

@ -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;
}

View file

@ -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) {