diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 37636a198..6495da1d0 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -59,6 +59,7 @@ class Choreographer { final StreamController stateStream = StreamController.broadcast(); StreamSubscription? _trialStream; StreamSubscription? _languageStream; + late AssistanceState _currentAssistanceState; Choreographer(this.pangeaController, this.chatController) { _initialize(); @@ -86,6 +87,7 @@ class Choreographer { // for changes like enabling autocorrect setState(); }); + _currentAssistanceState = assistanceState; clear(); } @@ -248,6 +250,12 @@ class Choreographer { /// Handles any changes to the text input _onChangeListener() { + // Rebuild the IGC button if the state has changed. + // This accounts for user typing after initial IGC has completed + if (_currentAssistanceState != assistanceState) { + setState(); + } + if (_noChange) { return; } @@ -453,6 +461,33 @@ class Choreographer { } } + void acceptNormalizationMatches() { + for (int i = 0; i < igc.igcTextData!.matches.length; i++) { + final isNormalizationError = + igc.spanDataController.isNormalizationError(i); + + if (!isNormalizationError) continue; + final match = igc.igcTextData!.matches[i]; + + choreoRecord.addRecord( + _textController.text, + match: match.copyWith..status = PangeaMatchStatus.automatic, + ); + + igc.igcTextData!.acceptReplacement( + i, + match.match.choices!.indexWhere( + (c) => c.isBestCorrection, + ), + ); + + _textController.setSystemText( + igc.igcTextData!.originalInput, + EditType.igc, + ); + } + } + void onIgnoreMatch({required int cursorOffset}) { try { if (igc.igcTextData == null) { @@ -635,6 +670,7 @@ class Choreographer { if (!stateStream.isClosed) { stateStream.add(0); } + _currentAssistanceState = assistanceState; } LayerLinkAndKey get itBarLinkAndKey => diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 51f3b01d2..a14d2ef41 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -172,6 +172,7 @@ class IgcController { } igcTextData!.matches = confirmedMatches; + choreographer.acceptNormalizationMatches(); // TODO - for each new match, // check if existing igcTextData has one and only one match with the same error text and correction diff --git a/lib/pangea/choreographer/models/igc_text_data_model.dart b/lib/pangea/choreographer/models/igc_text_data_model.dart index 2058a557b..4f6d46ceb 100644 --- a/lib/pangea/choreographer/models/igc_text_data_model.dart +++ b/lib/pangea/choreographer/models/igc_text_data_model.dart @@ -4,11 +4,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:matrix/matrix.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_card_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'; @@ -363,13 +362,15 @@ class IGCTextData { /// Returns a list of [TextSpan]s used to display the text in the input field /// with the appropriate styling for each error match. List constructTokenSpan({ - required BuildContext context, + ChoreoRecordStep? choreoStep, TextStyle? defaultStyle, - required SpanCardModel? spanCardModel, - required bool handleClick, - required String transformTargetId, - required Room room, }) { + final stepMatch = choreoStep?.acceptedOrIgnoredMatch; + final List textSpanMatches = List.from(matches); + if (stepMatch != null && stepMatch.status == PangeaMatchStatus.automatic) { + textSpanMatches.add(stepMatch); + } + final List items = []; if (loading) { @@ -381,7 +382,8 @@ class IGCTextData { ]; } - final List> matchRanges = matches + textSpanMatches.sort((a, b) => a.match.offset.compareTo(b.match.offset)); + final List> matchRanges = textSpanMatches .map( (match) => [ match.match.offset, @@ -403,7 +405,7 @@ class IGCTextData { if (inMatch) { // if the pointer is in a match, then add that match to items // and then move the pointer to the end of the match range - final PangeaMatch match = matches[matchIndex]; + final PangeaMatch match = textSpanMatches[matchIndex]; items.add( getSpanItem( start: match.match.offset, diff --git a/lib/pangea/choreographer/models/pangea_match_model.dart b/lib/pangea/choreographer/models/pangea_match_model.dart index a16f96f5d..f56900a42 100644 --- a/lib/pangea/choreographer/models/pangea_match_model.dart +++ b/lib/pangea/choreographer/models/pangea_match_model.dart @@ -9,7 +9,7 @@ import '../constants/match_rule_ids.dart'; import 'igc_text_data_model.dart'; import 'span_data.dart'; -enum PangeaMatchStatus { open, ignored, accepted, unknown } +enum PangeaMatchStatus { open, ignored, accepted, automatic, unknown } class PangeaMatch { SpanData match; @@ -112,6 +112,10 @@ class PangeaMatch { offset >= match.offset && offset < match.offset + match.length; Color get underlineColor { + if (status == PangeaMatchStatus.automatic) { + return const Color.fromARGB(187, 132, 96, 224); + } + switch (match.rule?.id ?? "unknown") { case MatchRuleIds.interactiveTranslation: return const Color.fromARGB(187, 132, 96, 224); diff --git a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart index b1f8b49e3..804243d0f 100644 --- a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart @@ -175,16 +175,14 @@ class PangeaTextController extends TextEditingController { return TextSpan(text: text, style: style); } + final choreoSteps = choreographer.choreoRecord.choreoSteps; + return TextSpan( style: style, children: [ ...choreographer.igc.igcTextData!.constructTokenSpan( - context: context, + choreoStep: choreoSteps.isNotEmpty ? choreoSteps.last : null, defaultStyle: style, - spanCardModel: null, - handleClick: false, - transformTargetId: choreographer.inputTransformTargetKey, - room: choreographer.chatController.room, ), TextSpan(text: parts[1], style: style), ],