diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 83d678e98..6d5f8e347 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -28,6 +28,7 @@ class ITController { String? sourceText; List completedITSteps = []; CurrentITStep? currentITStep; + CurrentITStep? nextITStep; GoldRouteTracker goldRouteTracker = GoldRouteTracker.defaultTracker; List payLoadIds = []; @@ -42,6 +43,7 @@ class ITController { sourceText = null; completedITSteps = []; currentITStep = null; + nextITStep = null; goldRouteTracker = GoldRouteTracker.defaultTracker; payLoadIds = []; @@ -130,36 +132,75 @@ class ITController { ); } - currentITStep = null; - - final ITResponseModel res = await _customInputTranslation(currentText); - // final ITResponseModel res = await (useCustomInput || - // currentText.isEmpty || - // translationId == null || - // completedITSteps.last.chosenContinuance?.indexSavedByServer == - // null - // ? _customInputTranslation(currentText) - // : _systemChoiceTranslation(translationId)); - - if (res.goldContinuances != null && res.goldContinuances!.isNotEmpty) { - goldRouteTracker = GoldRouteTracker( - res.goldContinuances!, - sourceText!, + if (nextITStep == null) { + currentITStep = null; + + final ITResponseModel res = await _customInputTranslation(currentText); + // final ITResponseModel res = await (useCustomInput || + // currentText.isEmpty || + // translationId == null || + // completedITSteps.last.chosenContinuance?.indexSavedByServer == + // null + // ? _customInputTranslation(currentText) + // : _systemChoiceTranslation(translationId)); + + if (res.goldContinuances != null && res.goldContinuances!.isNotEmpty) { + goldRouteTracker = GoldRouteTracker( + res.goldContinuances!, + sourceText!, + ); + } + + currentITStep = CurrentITStep( + sourceText: sourceText!, + currentText: currentText, + responseModel: res, + storedGoldContinuances: goldRouteTracker.continuances, ); - } - - currentITStep = CurrentITStep( - sourceText: sourceText!, - currentText: currentText, - responseModel: res, - storedGoldContinuances: goldRouteTracker.continuances, - ); - _addPayloadId(res); + _addPayloadId(res); + } else { + currentITStep = nextITStep; + nextITStep = null; + } if (isTranslationDone) { choreographer.altTranslator.setTranslationFeedback(); choreographer.getLanguageHelp(true); + } else { + getNextTranslationData(); + } + } catch (e, s) { + debugger(when: kDebugMode); + if (e is! http.Response) { + ErrorHandler.logError(e: e, s: s); + } + choreographer.errorService.setErrorAndLock( + ChoreoError(type: ChoreoErrorType.unknown, raw: e), + ); + } finally { + choreographer.stopLoading(); + } + } + + FuturegetNextTranslationData() async { + try { + if (completedITSteps.length < goldRouteTracker.continuances.length) { + final String currentText = choreographer.currentText; + final String nextText = + goldRouteTracker.continuances[completedITSteps.length].text; + + final ITResponseModel res = + await _customInputTranslation(currentText + nextText); + + nextITStep = CurrentITStep( + sourceText: sourceText!, + currentText: nextText, + responseModel: res, + storedGoldContinuances: goldRouteTracker.continuances, + ); + } else { + nextITStep = null; } } catch (e, s) { debugger(when: kDebugMode); diff --git a/lib/pangea/choreographer/widgets/choice_array.dart b/lib/pangea/choreographer/widgets/choice_array.dart index ca3f3bf7d..54fd601b9 100644 --- a/lib/pangea/choreographer/widgets/choice_array.dart +++ b/lib/pangea/choreographer/widgets/choice_array.dart @@ -1,9 +1,11 @@ import 'dart:developer'; +import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; import '../../utils/bot_style.dart'; import 'it_shimmer.dart'; @@ -56,10 +58,12 @@ class Choice { Choice({ this.color, required this.text, + this.isGold = false, }); final Color? color; final String text; + final bool isGold; } class ChoiceItem extends StatelessWidget { @@ -86,45 +90,50 @@ class ChoiceItem extends StatelessWidget { waitDuration: onLongPress != null ? const Duration(milliseconds: 500) : const Duration(days: 1), - child: Container( - margin: const EdgeInsets.all(2), - padding: EdgeInsets.zero, - decoration: isSelected - ? BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(10)), - border: Border.all( - color: entry.value.color ?? theme.colorScheme.primary, - style: BorderStyle.solid, - width: 2.0, + child: ChoiceAnimationWidget( + key: ValueKey(entry.value.text), + selected: entry.value.color != null, + isGold: entry.value.isGold, + child: Container( + margin: const EdgeInsets.all(2), + padding: EdgeInsets.zero, + decoration: isSelected + ? BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all( + color: entry.value.color ?? theme.colorScheme.primary, + style: BorderStyle.solid, + width: 2.0, + ), + ) + : null, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 7), + ), + //if index is selected, then give the background a slight primary color + backgroundColor: MaterialStateProperty.all( + entry.value.color != null + ? entry.value.color!.withOpacity(0.2) + : theme.colorScheme.primary.withOpacity(0.1), + ), + textStyle: MaterialStateProperty.all( + BotStyle.text(context), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - ) - : null, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(horizontal: 7), - ), - //if index is selected, then give the background a slight primary color - backgroundColor: MaterialStateProperty.all( - entry.value.color != null - ? entry.value.color!.withOpacity(0.2) - : theme.colorScheme.primary.withOpacity(0.1), - ), - textStyle: MaterialStateProperty.all( - BotStyle.text(context), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), ), ), - ), - onLongPress: - onLongPress != null ? () => onLongPress!(entry.key) : null, - onPressed: () => onPressed(entry.key), - child: Text( - entry.value.text, - style: BotStyle.text(context), + onLongPress: + onLongPress != null ? () => onLongPress!(entry.key) : null, + onPressed: () => onPressed(entry.key), + child: Text( + entry.value.text, + style: BotStyle.text(context), + ), ), ), ), @@ -135,3 +144,110 @@ class ChoiceItem extends StatelessWidget { } } } + +class ChoiceAnimationWidget extends StatefulWidget { + final Widget child; + final bool selected; + final bool isGold; + + const ChoiceAnimationWidget({ + super.key, + required this.child, + required this.selected, + this.isGold = false, + }); + + @override + ChoiceAnimationWidgetState createState() => ChoiceAnimationWidgetState(); +} + +class ChoiceAnimationWidgetState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + bool animationPlayed = false; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _animation = widget.isGold + ? Tween(begin: 1.0, end: 1.2).animate(_controller) + : TweenSequence([ + TweenSequenceItem( + tween: Tween(begin: 0, end: -8 * pi / 180), + weight: 1.0, + ), + TweenSequenceItem( + tween: Tween(begin: -8 * pi / 180, end: 16 * pi / 180), + weight: 2.0, + ), + TweenSequenceItem( + tween: Tween(begin: 16 * pi / 180, end: 0), + weight: 1.0, + ), + ]).animate(_controller); + + if (widget.selected && !animationPlayed) { + _controller.forward(); + animationPlayed = true; + setState(() {}); + } + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.stop(); + _controller.reset(); + } + }); + } + + @override + void didUpdateWidget(ChoiceAnimationWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selected && !animationPlayed) { + _controller.forward(); + animationPlayed = true; + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + return widget.isGold + ? AnimatedBuilder( + key: UniqueKey(), + animation: _animation, + builder: (context, child) { + return Transform.scale( + scale: _animation.value, + child: child, + ); + }, + child: widget.child, + ) + : AnimatedBuilder( + key: UniqueKey(), + animation: _animation, + builder: (context, child) { + return Transform.rotate( + angle: _animation.value, + child: child, + ); + }, + child: widget.child, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 2ab811008..4e26cba58 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -280,7 +280,11 @@ class ITChoices extends StatelessWidget { originalSpan: "dummy", choices: controller.currentITStep!.continuances.map((e) { try { - return Choice(text: e.text.trim(), color: e.color); + return Choice( + text: e.text.trim(), + color: e.color, + isGold: e.description == "best", + ); } catch (e) { debugger(when: kDebugMode); return Choice(text: "error", color: Colors.red); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 9ef2f4122..fd44383a0 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -198,6 +198,7 @@ class WordMatchContent extends StatelessWidget { (e) => Choice( text: e.value, color: e.selected ? e.type.color : null, + isGold: e.type.name == 'bestCorrection', ), ) .toList(),