diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index cd1ff7833..06ab3b84c 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -529,7 +529,7 @@ class Choreographer { } } - void onIgnoreMatch({required int cursorOffset}) { + void onIgnoreMatch({required int matchIndex}) { try { if (igc.igcTextData == null) { debugger(when: kDebugMode); @@ -541,10 +541,6 @@ class Choreographer { return; } - final int matchIndex = igc.igcTextData!.getTopMatchIndexForOffset( - cursorOffset, - ); - if (matchIndex == -1) { debugger(when: kDebugMode); throw Exception("Cannot find the ignored match in igcTextData"); @@ -571,7 +567,7 @@ class Choreographer { Breadcrumb( data: { "igcTextData": igc.igcTextData?.toJson(), - "offset": cursorOffset, + "matchIndex": matchIndex, }, ), ); diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 6d3e0e070..11759039a 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -18,7 +18,6 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar import 'package:fluffychat/widgets/matrix.dart'; import '../../common/utils/error_handler.dart'; import '../../common/utils/overlay.dart'; -import '../models/span_card_model.dart'; class _IGCTextDataCacheItem { Future data; @@ -214,27 +213,15 @@ class IgcController { overlayKey: "span_card_overlay_$firstMatchIndex", context: context, cardToShow: SpanCard( - scm: SpanCardModel( - matchIndex: firstMatchIndex, - onReplacementSelect: choreographer.onReplacementSelect, - onSentenceRewrite: (value) async {}, - onIgnore: () => choreographer.onIgnoreMatch( - cursorOffset: match.match.offset, - ), - onITStart: () { - if (choreographer.itEnabled && igcTextData != null) { - choreographer.onITStart(igcTextData!.matches[firstMatchIndex]); - } - }, - choreographer: choreographer, - ), - roomId: choreographer.roomId, + matchIndex: firstMatchIndex, + choreographer: choreographer, ), - maxHeight: match.isITStart ? 260 : 350, - maxWidth: 350, + maxHeight: 325, + maxWidth: 325, transformTargetId: choreographer.inputTransformTargetKey, onDismiss: () => choreographer.setState(), ignorePointer: true, + isScrollable: false, ); } diff --git a/lib/pangea/choreographer/models/span_card_model.dart b/lib/pangea/choreographer/models/span_card_model.dart deleted file mode 100644 index 5d341f509..000000000 --- a/lib/pangea/choreographer/models/span_card_model.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; - -class SpanCardModel { - // IGCTextData igcTextData; - int matchIndex; - Future Function({required int matchIndex, required int choiceIndex}) - onReplacementSelect; - Future Function(String) onSentenceRewrite; - void Function() onIgnore; - void Function() onITStart; - Choreographer choreographer; - - SpanCardModel({ - // required this.igcTextData, - required this.matchIndex, - required this.onReplacementSelect, - required this.onSentenceRewrite, - required this.onIgnore, - required this.onITStart, - required this.choreographer, - }); - - PangeaMatch? get pangeaMatch { - if (choreographer.igc.igcTextData == null) return null; - if (matchIndex >= choreographer.igc.igcTextData!.matches.length) { - ErrorHandler.logError( - m: "matchIndex out of bounds in span card", - data: { - "matchIndex": matchIndex, - "matchesLength": choreographer.igc.igcTextData?.matches.length, - }, - ); - return null; - } - return choreographer.igc.igcTextData?.matches[matchIndex]; - } -} diff --git a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart index edcaab52c..11b397ddf 100644 --- a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/widgets/matrix.dart'; import '../../../common/utils/overlay.dart'; import '../../controllers/choreographer.dart'; import '../../enums/edit_type.dart'; -import '../../models/span_card_model.dart'; class PangeaTextController extends TextEditingController { Choreographer choreographer; @@ -90,23 +89,8 @@ class PangeaTextController extends TextEditingController { final Widget? cardToShow = matchIndex != -1 ? SpanCard( - scm: SpanCardModel( - // igcTextData: choreographer.igc.igcTextData!, - matchIndex: matchIndex, - onReplacementSelect: choreographer.onReplacementSelect, - // may not need this - onSentenceRewrite: ((sentenceRewrite) async {}), - onIgnore: () => choreographer.onIgnoreMatch( - cursorOffset: selection.baseOffset, - ), - onITStart: () { - choreographer.onITStart( - choreographer.igc.igcTextData!.matches[matchIndex], - ); - }, - choreographer: choreographer, - ), - roomId: choreographer.roomId, + matchIndex: matchIndex, + choreographer: choreographer, ) : null; @@ -126,6 +110,7 @@ class PangeaTextController extends TextEditingController { transformTargetId: choreographer.inputTransformTargetKey, onDismiss: () => choreographer.setState(), ignorePointer: true, + isScrollable: false, ); } } diff --git a/lib/pangea/choreographer/widgets/igc/span_card.dart b/lib/pangea/choreographer/widgets/igc/span_card.dart index 53bca53a6..696fff0e2 100644 --- a/lib/pangea/choreographer/widgets/igc/span_card.dart +++ b/lib/pangea/choreographer/widgets/igc/span_card.dart @@ -1,39 +1,27 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/bot/utils/bot_style.dart'; +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pangea/choreographer/enums/span_choice_type.dart'; import 'package:fluffychat/pangea/choreographer/enums/span_data_type.dart'; +import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/choreographer/models/span_data.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import '../../../../widgets/matrix.dart'; import '../../../bot/widgets/bot_face_svg.dart'; -import '../../../common/controllers/pangea_controller.dart'; -import '../../enums/span_choice_type.dart'; -import '../../models/span_card_model.dart'; import '../choice_array.dart'; import 'why_button.dart'; -//switch for definition vs correction vs practice - -//always show a title -//if description then show description -//choices then show choices -// - class SpanCard extends StatefulWidget { - final PangeaController pangeaController = MatrixState.pangeaController; - final SpanCardModel scm; - final String roomId; + final int matchIndex; + final Choreographer choreographer; - SpanCard({ + const SpanCard({ super.key, - required this.scm, - required this.roomId, + required this.matchIndex, + required this.choreographer, }); @override @@ -43,60 +31,78 @@ class SpanCard extends StatefulWidget { class SpanCardState extends State { bool fetchingData = false; int? selectedChoiceIndex; + final ScrollController scrollController = ScrollController(); - BotExpression currentExpression = BotExpression.nonGold; - - //on initState, get SpanDetails @override void initState() { - // debugger(when: kDebugMode); super.initState(); + if (pangeaMatch?.isITStart == true) { + _onITStart(); + return; + } + getSpanDetails(); - fetchSelected(); + _fetchSelected(); } @override void dispose() { TtsController.stop(); + scrollController.dispose(); super.dispose(); } + PangeaMatch? get pangeaMatch { + if (widget.choreographer.igc.igcTextData == null) return null; + if (widget.matchIndex >= + widget.choreographer.igc.igcTextData!.matches.length) { + ErrorHandler.logError( + m: "matchIndex out of bounds in span card", + data: { + "matchIndex": widget.matchIndex, + "matchesLength": widget.choreographer.igc.igcTextData?.matches.length, + }, + ); + return null; + } + return widget.choreographer.igc.igcTextData?.matches[widget.matchIndex]; + } + //get selected choice SpanChoice? get selectedChoice { if (selectedChoiceIndex == null) return null; - return choiceByIndex(selectedChoiceIndex!); + return _choiceByIndex(selectedChoiceIndex!); } - SpanChoice? choiceByIndex(int index) { - if (widget.scm.pangeaMatch?.match.choices == null || - widget.scm.pangeaMatch!.match.choices!.length <= index) { + SpanChoice? _choiceByIndex(int index) { + if (pangeaMatch?.match.choices == null || + pangeaMatch!.match.choices!.length <= index) { return null; } - return widget.scm.pangeaMatch?.match.choices?[index]; + return pangeaMatch?.match.choices?[index]; } - void fetchSelected() { - if (widget.scm.pangeaMatch?.match.choices == null) { + void _fetchSelected() { + if (pangeaMatch?.match.choices == null) { return; } // if user ever selected the correct choice, automatically select it final selectedCorrectIndex = - widget.scm.pangeaMatch!.match.choices!.indexWhere((choice) { + pangeaMatch!.match.choices!.indexWhere((choice) { return choice.selected && choice.isBestCorrection; }); if (selectedCorrectIndex != -1) { selectedChoiceIndex = selectedCorrectIndex; - currentExpression = BotExpression.gold; return; } if (selectedChoiceIndex == null) { DateTime? mostRecent; - final numChoices = widget.scm.pangeaMatch!.match.choices!.length; + final numChoices = pangeaMatch!.match.choices!.length; for (int i = 0; i < numChoices; i++) { - final choice = choiceByIndex(i); + final choice = _choiceByIndex(i); if (choice!.timestamp != null && (mostRecent == null || choice.timestamp!.isAfter(mostRecent))) { mostRecent = choice.timestamp; @@ -107,15 +113,15 @@ class SpanCardState extends State { } Future getSpanDetails({bool force = false}) async { - if (widget.scm.pangeaMatch?.isITStart ?? false) return; + if (pangeaMatch?.isITStart ?? false) return; if (!mounted) return; setState(() { fetchingData = true; }); - await widget.scm.choreographer.igc.spanDataController.getSpanDetails( - widget.scm.matchIndex, + await widget.choreographer.igc.spanDataController.getSpanDetails( + widget.matchIndex, force: force, ); @@ -124,7 +130,13 @@ class SpanCardState extends State { } } - Future onChoiceSelect(int index) async { + void _onITStart() { + if (widget.choreographer.itEnabled && pangeaMatch != null) { + widget.choreographer.onITStart(pangeaMatch!); + } + } + + Future _onChoiceSelect(int index) async { selectedChoiceIndex = index; if (selectedChoice != null) { selectedChoice!.timestamp = DateTime.now(); @@ -137,28 +149,30 @@ class SpanCardState extends State { } } - Future onReplaceSelected() async { - await widget.scm.onReplacementSelect( - matchIndex: widget.scm.matchIndex, + Future _onReplaceSelected() async { + await widget.choreographer.onReplacementSelect( + matchIndex: widget.matchIndex, choiceIndex: selectedChoiceIndex!, ); _showFirstMatch(); } - void onIgnoreMatch() { + void _onIgnoreMatch() { Future.delayed( Duration.zero, () { - widget.scm.onIgnore(); + widget.choreographer.onIgnoreMatch( + matchIndex: widget.matchIndex, + ); _showFirstMatch(); }, ); } void _showFirstMatch() { - if (widget.scm.choreographer.igc.igcTextData != null && - widget.scm.choreographer.igc.igcTextData!.matches.isNotEmpty) { - widget.scm.choreographer.igc.showFirstMatch(context); + if (widget.choreographer.igc.igcTextData != null && + widget.choreographer.igc.igcTextData!.matches.isNotEmpty) { + widget.choreographer.igc.showFirstMatch(context); } else { MatrixState.pAnyState.closeOverlay(); } @@ -166,113 +180,103 @@ class SpanCardState extends State { @override Widget build(BuildContext context) { - return WordMatchContent(controller: this); + return WordMatchContent( + controller: this, + scrollController: scrollController, + ); } } class WordMatchContent extends StatelessWidget { - final PangeaController pangeaController = MatrixState.pangeaController; final SpanCardState controller; + final ScrollController scrollController; - WordMatchContent({ + const WordMatchContent({ required this.controller, + required this.scrollController, super.key, }); @override Widget build(BuildContext context) { - if (controller.widget.scm.pangeaMatch == null) { + if (controller.pangeaMatch == null || controller.pangeaMatch!.isITStart) { return const SizedBox(); } - final ScrollController scrollController = ScrollController(); - - try { - return Column( - children: [ - // if (!controller.widget.scm.pangeaMatch!.isITStart) - // CardHeader( - // text: controller.error?.toString(), - // botExpression: controller.error == null - // ? controller.currentExpression - // : BotExpression.addled, - // onClose: () => controller.widget.scm.choreographer.setState(), - // ), - Scrollbar( + return Stack( + children: [ + SizedBox( + height: 300.0, + child: Scrollbar( controller: scrollController, - thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.stretch, 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: (value, index) => - controller.onChoiceSelect(index), - selectedChoiceIndex: controller.selectedChoiceIndex, - id: controller.widget.scm.pangeaMatch!.hashCode - .toString(), - langCode: MatrixState.pangeaController.languageController - .activeL2Code(), - ), + ChoicesArray( + originalSpan: controller.pangeaMatch!.matchContent, + isLoading: controller.fetchingData, + choices: controller.pangeaMatch!.match.choices + ?.map( + (e) => Choice( + text: e.value, + color: e.selected ? e.type.color : null, + isGold: e.type.name == 'bestCorrection', + ), + ) + .toList(), + onPressed: (value, index) => + controller._onChoiceSelect(index), + selectedChoiceIndex: controller.selectedChoiceIndex, + id: controller.pangeaMatch!.hashCode.toString(), + langCode: MatrixState.pangeaController.languageController + .activeL2Code(), + ), const SizedBox(height: 12), PromptAndFeedback(controller: controller), + const SizedBox(height: 60.0), ], ), ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(width: 10), - Expanded( - child: Opacity( - opacity: 0.8, - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - Theme.of(context).colorScheme.primary.withAlpha(25), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + ), + padding: const EdgeInsets.only(top: 8.0), + child: Row( + spacing: 10.0, + children: [ + Expanded( + child: Opacity( + opacity: 0.8, + child: TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.primary.withAlpha(25), + ), + ), + onPressed: controller._onIgnoreMatch, + child: Center( + child: Text(L10n.of(context).ignoreInThisText), ), - ), - onPressed: controller.onIgnoreMatch, - child: Center( - child: Text(L10n.of(context).ignoreInThisText), ), ), ), - ), - 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 + ? controller._onReplaceSelected : null, style: ButtonStyle( backgroundColor: WidgetStateProperty.all( @@ -296,48 +300,12 @@ class WordMatchContent extends StatelessWidget { ), ), ), - 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( - (Theme.of(context).colorScheme.primary).withAlpha(25), - ), - ), - child: Text(L10n.of(context).helpMeTranslate), - ), - ), - ], + ], + ), ), - // 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) { - debugger(when: kDebugMode); - ErrorHandler.logError( - e: e, - s: StackTrace.current, - data: {}, - ); - rethrow; - } + ), + ], + ); } } @@ -351,14 +319,14 @@ class PromptAndFeedback extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.widget.scm.pangeaMatch == null) { + if (controller.pangeaMatch == null) { return const SizedBox(); } return Container( - constraints: controller.widget.scm.pangeaMatch!.isITStart + constraints: controller.pangeaMatch!.isITStart ? null - : const BoxConstraints(minHeight: 100), + : const BoxConstraints(minHeight: 75.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -391,7 +359,7 @@ class PromptAndFeedback extends StatelessWidget { if (!controller.fetchingData && controller.selectedChoiceIndex == null) Text( - controller.widget.scm.pangeaMatch!.match.type.typeName + controller.pangeaMatch!.match.type.typeName .defaultPrompt(context), style: BotStyle.text(context).copyWith( fontStyle: FontStyle.italic, @@ -402,108 +370,3 @@ class PromptAndFeedback extends StatelessWidget { ); } } - -class LoadingText extends StatefulWidget { - const LoadingText({ - super.key, - }); - - @override - LoadingTextState createState() => LoadingTextState(); -} - -class LoadingTextState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _controller = AnimationController( - duration: const Duration(seconds: 1), - vsync: this, - )..repeat(); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - L10n.of(context).makingActivity, - style: BotStyle.text(context), - ), - AnimatedBuilder( - animation: _controller, - builder: (BuildContext context, Widget? child) { - return Text( - _controller.isAnimating ? '.' * _controller.value.toInt() : '', - style: BotStyle.text(context), - ); - }, - ), - ], - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } -} - -class StartITButton extends StatelessWidget { - const StartITButton({ - super.key, - required this.onITStart, - }); - - final void Function() onITStart; - - @override - Widget build(BuildContext context) { - return Material( - type: MaterialType.transparency, - child: ListTile( - leading: const Icon(Icons.translate_outlined), - title: Text(L10n.of(context).helpMeTranslate), - onTap: () { - MatrixState.pAnyState.closeOverlay(); - Future.delayed(Duration.zero, () => onITStart()); - }, - ), - ); - } -} - -class DontShowSwitchListTile extends StatefulWidget { - final PangeaController controller; - final Function(bool) onSwitch; - - const DontShowSwitchListTile({ - super.key, - required this.controller, - required this.onSwitch, - }); - - @override - DontShowSwitchListTileState createState() => DontShowSwitchListTileState(); -} - -class DontShowSwitchListTileState extends State { - bool switchValue = false; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context).interactiveTranslatorAutoPlaySliderHeader), - value: switchValue, - onChanged: (value) { - widget.onSwitch(value); - setState(() => switchValue = value); - }, - ); - } -}