From fdbbd0e603ae010f889793ce1e2d58d51bb5e755 Mon Sep 17 00:00:00 2001 From: Matthew <119624750+casualWaist@users.noreply.github.com> Date: Thu, 23 May 2024 23:48:08 -0400 Subject: [PATCH 1/2] improve ITStep Transitions --- .../controllers/it_controller.dart | 89 +++++++--- .../choreographer/widgets/choice_array.dart | 153 +++++++++++++----- 2 files changed, 182 insertions(+), 60 deletions(-) diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 83d678e984..6d5f8e3476 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 ca3f3bf7d9..7a2efe72be 100644 --- a/lib/pangea/choreographer/widgets/choice_array.dart +++ b/lib/pangea/choreographer/widgets/choice_array.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -86,45 +87,48 @@ 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: SelectiveRotatingWidget( + selected: entry.value.color != null, + 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 +139,80 @@ class ChoiceItem extends StatelessWidget { } } } + +class SelectiveRotatingWidget extends StatefulWidget { + final Widget child; + final bool selected; + + const SelectiveRotatingWidget({super.key, required this.child, required this.selected}); + + @override + SelectiveRotatingWidgetState createState() => SelectiveRotatingWidgetState(); +} + +class SelectiveRotatingWidgetState extends State with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _animation = 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) { + _controller.repeat(reverse: true); + } + } + + @override + void didUpdateWidget(SelectiveRotatingWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selected != oldWidget.selected) { + if (widget.selected) { + _controller.repeat(reverse: true); + } else { + _controller.stop(); + _controller.reset(); + } + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Transform.rotate( + angle: _animation.value, + child: child, + ); + }, + child: widget.child, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} From c272bc8e5d09bc2a98e538c1a6de616c44415cdf Mon Sep 17 00:00:00 2001 From: Matthew <119624750+casualWaist@users.noreply.github.com> Date: Tue, 28 May 2024 16:32:59 -0400 Subject: [PATCH 2/2] unique animations for choices --- .../choreographer/widgets/choice_array.dart | 97 +++++++++++++------ lib/pangea/choreographer/widgets/it_bar.dart | 6 +- lib/pangea/widgets/igc/span_card.dart | 1 + 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/lib/pangea/choreographer/widgets/choice_array.dart b/lib/pangea/choreographer/widgets/choice_array.dart index 7a2efe72be..54fd601b99 100644 --- a/lib/pangea/choreographer/widgets/choice_array.dart +++ b/lib/pangea/choreographer/widgets/choice_array.dart @@ -5,6 +5,7 @@ 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'; @@ -57,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 { @@ -87,8 +90,10 @@ class ChoiceItem extends StatelessWidget { waitDuration: onLongPress != null ? const Duration(milliseconds: 500) : const Duration(days: 1), - child: SelectiveRotatingWidget( + 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, @@ -140,19 +145,27 @@ class ChoiceItem extends StatelessWidget { } } -class SelectiveRotatingWidget extends StatefulWidget { +class ChoiceAnimationWidget extends StatefulWidget { final Widget child; final bool selected; + final bool isGold; - const SelectiveRotatingWidget({super.key, required this.child, required this.selected}); + const ChoiceAnimationWidget({ + super.key, + required this.child, + required this.selected, + this.isGold = false, + }); @override - SelectiveRotatingWidgetState createState() => SelectiveRotatingWidgetState(); + ChoiceAnimationWidgetState createState() => ChoiceAnimationWidgetState(); } -class SelectiveRotatingWidgetState extends State with SingleTickerProviderStateMixin { +class ChoiceAnimationWidgetState extends State + with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _animation; + bool animationPlayed = false; @override void initState() { @@ -163,42 +176,64 @@ class SelectiveRotatingWidgetState extends State with S vsync: this, ); - _animation = 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) { - _controller.repeat(reverse: true); + _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(SelectiveRotatingWidget oldWidget) { + void didUpdateWidget(ChoiceAnimationWidget oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.selected != oldWidget.selected) { - if (widget.selected) { - _controller.repeat(reverse: true); - } else { - _controller.stop(); - _controller.reset(); - } + if (widget.selected && !animationPlayed) { + _controller.forward(); + animationPlayed = true; + setState(() {}); } } @override Widget build(BuildContext context) { - return AnimatedBuilder( + 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( diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 2ab8110088..4e26cba589 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 9ef2f41227..fd44383a08 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(),