diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6d9129908..475e7dc2a 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -45,7 +45,6 @@ import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart'; import 'package:fluffychat/pangea/choreographer/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/choreographer_state_extension.dart'; import 'package:fluffychat/pangea/choreographer/choreographer_ui_extension.dart'; -import 'package:fluffychat/pangea/choreographer/igc/pangea_match_state_model.dart'; import 'package:fluffychat/pangea/choreographer/text_editing/edit_type_enum.dart'; import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; @@ -183,7 +182,7 @@ class ChatController extends State with WidgetsBindingObserver { // #Pangea final PangeaController pangeaController = MatrixState.pangeaController; - late Choreographer choreographer = Choreographer(pangeaController, this); + late Choreographer choreographer; late GoRouter _router; StreamSubscription? _levelSubscription; @@ -427,6 +426,7 @@ class ChatController extends State @override void initState() { inputFocus = FocusNode(onKeyEvent: _customEnterKeyHandling); + choreographer = Choreographer(inputFocus); scrollController.addListener(_updateScrollController); // #Pangea @@ -2192,16 +2192,7 @@ class ChatController extends State await choreographer.requestWritingAssistance(); if (choreographer.assistanceState == AssistanceStateEnum.fetched) { - onSelectMatch(choreographer.igcController.firstOpenMatch); - } else if (autosend) { - await send(); - } else { - inputFocus.requestFocus(); - } - } - - void onSelectMatch(PangeaMatchState? match) { - if (match != null) { + final match = choreographer.igcController.firstOpenMatch!; match.updatedMatch.isITStart ? choreographer.openIT(match) : OverlayUtil.showIGCMatch( @@ -2209,9 +2200,11 @@ class ChatController extends State choreographer, context, ); - return; + } else if (autosend) { + await send(); + } else { + inputFocus.requestFocus(); } - inputFocus.requestFocus(); } void showLanguageMismatchPopup() { diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 5316ff887..7bcead53b 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/choreographer/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/choreographer_state_extension.dart'; import 'package:fluffychat/pangea/choreographer/choreographer_ui_extension.dart'; import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/subscription/widgets/paywall_card.dart'; @@ -461,25 +462,33 @@ class InputBar extends StatelessWidget { int adjustedOffset = controller!.selection.baseOffset; final normalizationMatches = choreographer.igcController.recentAutomaticCorrections; - if (normalizationMatches == null || normalizationMatches.isEmpty) return; - for (final match in normalizationMatches) { - if (match.updatedMatch.match.offset < adjustedOffset && - match.updatedMatch.match.length > 0) { - adjustedOffset += (match.updatedMatch.match.length - 1); + if (normalizationMatches != null) { + for (final match in normalizationMatches) { + if (match.updatedMatch.match.offset < adjustedOffset && + match.updatedMatch.match.length > 0) { + adjustedOffset += (match.updatedMatch.match.length - 1); + } } } final match = choreographer.igcController.getMatchByOffset(adjustedOffset); - choreographer.chatController.onSelectMatch(match); + if (match == null) return; + match.updatedMatch.isITStart + ? choreographer.openIT(match) + : OverlayUtil.showIGCMatch( + match, + choreographer, + context, + ); } // Pangea# @override Widget build(BuildContext context) { // #Pangea - return ListenableBuilder( - listenable: choreographer.textController, - builder: (context, _) { + return ValueListenableBuilder( + valueListenable: choreographer.textController, + builder: (context, _, __) { final enableAutocorrect = MatrixState.pangeaController.userController .profile.toolSettings.enableAutocorrect; // Pangea# diff --git a/lib/pangea/chat/widgets/chat_floating_action_button.dart b/lib/pangea/chat/widgets/chat_floating_action_button.dart index 50d0e29fd..b7bf6a8cc 100644 --- a/lib/pangea/chat/widgets/chat_floating_action_button.dart +++ b/lib/pangea/chat/widgets/chat_floating_action_button.dart @@ -19,7 +19,8 @@ class ChatFloatingActionButton extends StatelessWidget { return ListenableBuilder( listenable: Listenable.merge( [ - controller.choreographer, + controller.choreographer.errorService, + controller.choreographer.itController.open, controller.scrollController, ], ), diff --git a/lib/pangea/choreographer/choregrapher_user_settings_extension.dart b/lib/pangea/choreographer/choregrapher_user_settings_extension.dart index fdab2e8e2..13421cde4 100644 --- a/lib/pangea/choreographer/choregrapher_user_settings_extension.dart +++ b/lib/pangea/choreographer/choregrapher_user_settings_extension.dart @@ -1,26 +1,26 @@ import 'package:fluffychat/pangea/choreographer/choreographer.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/spaces/models/space_model.dart'; +import 'package:fluffychat/widgets/matrix.dart'; extension ChoregrapherUserSettingsExtension on Choreographer { LanguageModel? get l2Lang => - pangeaController.languageController.activeL2Model(); + MatrixState.pangeaController.languageController.activeL2Model(); String? get l2LangCode => l2Lang?.langCode; LanguageModel? get l1Lang => - pangeaController.languageController.activeL1Model(); + MatrixState.pangeaController.languageController.activeL1Model(); String? get l1LangCode => l1Lang?.langCode; - bool get igcEnabled => pangeaController.permissionsController.isToolEnabled( + bool get igcEnabled => + MatrixState.pangeaController.permissionsController.isToolEnabled( ToolSetting.interactiveGrammar, - chatController.room, ); - bool get itEnabled => pangeaController.permissionsController.isToolEnabled( + bool get itEnabled => + MatrixState.pangeaController.permissionsController.isToolEnabled( ToolSetting.interactiveTranslator, - chatController.room, ); bool get isAutoIGCEnabled => - pangeaController.permissionsController.isToolEnabled( + MatrixState.pangeaController.permissionsController.isToolEnabled( ToolSetting.autoIGC, - chatController.room, ); } diff --git a/lib/pangea/choreographer/choreographer.dart b/lib/pangea/choreographer/choreographer.dart index 7d0e488d4..6805de4fe 100644 --- a/lib/pangea/choreographer/choreographer.dart +++ b/lib/pangea/choreographer/choreographer.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart'; import 'package:fluffychat/pangea/choreographer/choregrapher_user_settings_extension.dart'; import 'package:fluffychat/pangea/choreographer/choreo_constants.dart'; @@ -15,12 +14,10 @@ import 'package:fluffychat/pangea/choreographer/igc/pangea_match_status_enum.dar import 'package:fluffychat/pangea/choreographer/pangea_message_content_model.dart'; import 'package:fluffychat/pangea/choreographer/text_editing/edit_type_enum.dart'; import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart'; -import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/events/repo/token_api_models.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; @@ -29,18 +26,13 @@ import '../../widgets/matrix.dart'; import 'choreographer_error_controller.dart'; import 'it/it_controller.dart'; -class OpenMatchesException implements Exception {} - -class ShowPaywallException implements Exception {} - class Choreographer extends ChangeNotifier { - final PangeaController pangeaController; - final ChatController chatController; + final FocusNode inputFocus; - late PangeaTextController textController; - late ITController itController; - late IgcController igcController; - late ChoreographerErrorController errorService; + late final PangeaTextController textController; + late final ITController itController; + late final IgcController igcController; + late final ChoreographerErrorController errorService; ChoreoRecordModel? _choreoRecord; @@ -54,7 +46,9 @@ class Choreographer extends ChangeNotifier { StreamSubscription? _languageStream; StreamSubscription? _settingsUpdateStream; - Choreographer(this.pangeaController, this.chatController) { + Choreographer( + this.inputFocus, + ) { _initialize(); } @@ -63,6 +57,12 @@ class Choreographer extends ChangeNotifier { ChoreoModeEnum get choreoMode => _choreoMode; String get currentText => textController.text; + ChoreoRecordModel get _record => _choreoRecord ??= ChoreoRecordModel( + originalText: textController.text, + choreoSteps: [], + openMatches: [], + ); + void _initialize() { textController = PangeaTextController(choreographer: this); textController.addListener(_onChange); @@ -79,13 +79,15 @@ class Choreographer extends ChangeNotifier { (e) => errorService.setErrorAndLock(ChoreoError(raw: e)), ); - _languageStream = - pangeaController.userController.languageStream.stream.listen((update) { + _languageStream ??= MatrixState + .pangeaController.userController.languageStream.stream + .listen((update) { clear(); }); - _settingsUpdateStream = - pangeaController.userController.settingsUpdateStream.stream.listen((_) { + _settingsUpdateStream ??= MatrixState + .pangeaController.userController.settingsUpdateStream.stream + .listen((_) { notifyListeners(); }); } @@ -99,12 +101,14 @@ class Choreographer extends ChangeNotifier { itController.clearSourceText(); igcController.clear(); _resetDebounceTimer(); - setChoreoMode(ChoreoModeEnum.igc); + _setChoreoMode(ChoreoModeEnum.igc); } @override void dispose() { - super.dispose(); + errorService.removeListener(notifyListeners); + itController.open.removeListener(_onCloseIT); + textController.removeListener(_onChange); itController.dispose(); errorService.dispose(); textController.dispose(); @@ -113,12 +117,10 @@ class Choreographer extends ChangeNotifier { _debounceTimer?.cancel(); _isFetching.dispose(); TtsController.stop(); + super.dispose(); } - void onPaste(value) { - _initChoreoRecord(); - _choreoRecord!.pastedStrings.add(value); - } + void onPaste(value) => _record.pastedStrings.add(value); void onClickSend() { if (assistanceState == AssistanceStateEnum.fetched) { @@ -132,7 +134,7 @@ class Choreographer extends ChangeNotifier { } } - void setChoreoMode(ChoreoModeEnum mode) { + void _setChoreoMode(ChoreoModeEnum mode) { _choreoMode = mode; notifyListeners(); } @@ -144,14 +146,6 @@ class Choreographer extends ChangeNotifier { } } - void _initChoreoRecord() { - _choreoRecord ??= ChoreoRecordModel( - originalText: textController.text, - choreoSteps: [], - openMatches: [], - ); - } - void _startLoading() { _lastChecked = textController.text; _isFetching.value = true; @@ -170,6 +164,7 @@ class Choreographer extends ChangeNotifier { if (_lastChecked != null && _lastChecked == textController.text) { return; } + // update assistance state from no message => not fetched and vice versa if (_lastChecked == null || _lastChecked!.isEmpty || textController.text.isEmpty) { @@ -203,7 +198,7 @@ class Choreographer extends ChangeNotifier { }) async { if (assistanceState != AssistanceStateEnum.notFetched) return; final SubscriptionStatus canSendStatus = - pangeaController.subscriptionController.subscriptionStatus; + MatrixState.pangeaController.subscriptionController.subscriptionStatus; if (canSendStatus != SubscriptionStatus.subscribed || l2Lang == null || @@ -214,29 +209,27 @@ class Choreographer extends ChangeNotifier { } _resetDebounceTimer(); - _initChoreoRecord(); _startLoading(); await igcController.getIGCTextData( textController.text, - chatController.room.getPreviousMessages(), + [], ); - + _acceptNormalizationMatches(); // trigger a re-render of the text field to show IGC matches textController.setSystemText( textController.text, EditTypeEnum.igc, ); - _acceptNormalizationMatches(); _stopLoading(); } Future getMessageContent(String message) async { TokensResponseModel? tokensResp; if (l1LangCode != null && l2LangCode != null) { - final res = await pangeaController.messageData + final res = await MatrixState.pangeaController.messageData .getTokens( repEventId: null, - room: chatController.room, + room: null, req: TokensRequestModel( fullText: message, senderL1: l1LangCode!, @@ -247,12 +240,12 @@ class Choreographer extends ChangeNotifier { tokensResp = res.isValue ? res.result : null; } - final hasOriginalWritten = _choreoRecord?.includedIT == true && - itController.sourceText.value != null; + final hasOriginalWritten = + _record.includedIT && itController.sourceText.value != null; return PangeaMessageContentModel( message: message, - choreo: _choreoRecord, + choreo: _record, originalSent: PangeaRepresentation( langCode: tokensResp?.detections.firstOrNull?.langCode ?? LanguageKeys.unknownLanguage, @@ -282,17 +275,14 @@ class Choreographer extends ChangeNotifier { throw Exception("Attempted to open IT with a non-IT start match"); } - chatController.inputFocus.unfocus(); - setChoreoMode(ChoreoModeEnum.it); - + _setChoreoMode(ChoreoModeEnum.it); final sourceText = currentText; textController.setSystemText("", EditTypeEnum.it); itController.openIT(sourceText); igcController.clear(); - _initChoreoRecord(); itMatch.setStatus(PangeaMatchStatusEnum.accepted); - _choreoRecord!.addRecord( + _record.addRecord( "", match: itMatch.updatedMatch, ); @@ -308,7 +298,7 @@ class Choreographer extends ChangeNotifier { ); } - setChoreoMode(ChoreoModeEnum.igc); + _setChoreoMode(ChoreoModeEnum.igc); errorService.resetError(); notifyListeners(); } @@ -325,9 +315,8 @@ class Choreographer extends ChangeNotifier { EditTypeEnum.it, ); - _initChoreoRecord(); - _choreoRecord!.addRecord(textController.text, step: step); - chatController.inputFocus.requestFocus(); + _record.addRecord(textController.text, step: step); + inputFocus.requestFocus(); notifyListeners(); } @@ -351,20 +340,19 @@ class Choreographer extends ChangeNotifier { ); if (!updatedMatch.match.isNormalizationError()) { - _initChoreoRecord(); - _choreoRecord!.addRecord( + _record.addRecord( textController.text, match: updatedMatch, ); } MatrixState.pAnyState.closeOverlay(); - chatController.inputFocus.requestFocus(); + inputFocus.requestFocus(); notifyListeners(); } void onUndoReplacement(PangeaMatchState match) { igcController.undoReplacement(match); - _choreoRecord?.choreoSteps.removeWhere( + _record.choreoSteps.removeWhere( (step) => step.acceptedOrIgnoredMatch == match.updatedMatch, ); @@ -373,21 +361,20 @@ class Choreographer extends ChangeNotifier { EditTypeEnum.igc, ); MatrixState.pAnyState.closeOverlay(); - chatController.inputFocus.requestFocus(); + inputFocus.requestFocus(); notifyListeners(); } void onIgnoreReplacement({required PangeaMatchState match}) { final updatedMatch = igcController.ignoreReplacement(match); if (!updatedMatch.match.isNormalizationError()) { - _initChoreoRecord(); - _choreoRecord!.addRecord( + _record.addRecord( textController.text, match: updatedMatch, ); } MatrixState.pAnyState.closeOverlay(); - chatController.inputFocus.requestFocus(); + inputFocus.requestFocus(); notifyListeners(); } @@ -395,7 +382,6 @@ class Choreographer extends ChangeNotifier { final normalizationsMatches = igcController.openNormalizationMatches; if (normalizationsMatches?.isEmpty ?? true) return; - _initChoreoRecord(); try { for (final match in normalizationsMatches!) { match.selectChoice( @@ -412,7 +398,7 @@ class Choreographer extends ChangeNotifier { igcController.currentText!, EditTypeEnum.igc, ); - _choreoRecord!.addRecord( + _record.addRecord( currentText, match: updatedMatch, ); @@ -425,7 +411,7 @@ class Choreographer extends ChangeNotifier { "currentText": currentText, "l1LangCode": l1LangCode, "l2LangCode": l2LangCode, - "choreoRecord": _choreoRecord?.toJson(), + "choreoRecord": _record.toJson(), }, ); } diff --git a/lib/pangea/choreographer/choreographer_state_extension.dart b/lib/pangea/choreographer/choreographer_state_extension.dart index 55e850cdf..58f2adf8f 100644 --- a/lib/pangea/choreographer/choreographer_state_extension.dart +++ b/lib/pangea/choreographer/choreographer_state_extension.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart'; import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart'; import 'package:fluffychat/pangea/choreographer/choreographer.dart'; +import 'package:fluffychat/widgets/matrix.dart'; extension ChoregrapherUserSettingsExtension on Choreographer { bool get isRunningIT { @@ -9,7 +10,8 @@ extension ChoregrapherUserSettingsExtension on Choreographer { } AssistanceStateEnum get assistanceState { - final isSubscribed = pangeaController.subscriptionController.isSubscribed; + final isSubscribed = + MatrixState.pangeaController.subscriptionController.isSubscribed; if (isSubscribed == false) return AssistanceStateEnum.noSub; if (currentText.isEmpty && itController.sourceText.value == null) { return AssistanceStateEnum.noMessage; diff --git a/lib/pangea/choreographer/choreographer_ui_extension.dart b/lib/pangea/choreographer/choreographer_ui_extension.dart index dc7e437a7..b60ccbe7d 100644 --- a/lib/pangea/choreographer/choreographer_ui_extension.dart +++ b/lib/pangea/choreographer/choreographer_ui_extension.dart @@ -5,8 +5,8 @@ import 'package:fluffychat/widgets/matrix.dart'; extension ChoregrapherUserSettingsExtension on Choreographer { LayerLinkAndKey get itBarLinkAndKey => MatrixState.pAnyState.layerLinkAndKey(itBarTransformTargetKey); - String get itBarTransformTargetKey => 'it_bar${chatController.roomId}'; + String get itBarTransformTargetKey => 'it_bar'; LayerLinkAndKey get inputLayerLinkAndKey => MatrixState.pAnyState.layerLinkAndKey(inputTransformTargetKey); - String get inputTransformTargetKey => 'input${chatController.roomId}'; + String get inputTransformTargetKey => 'input_text_field'; } diff --git a/lib/pangea/choreographer/igc/span_card.dart b/lib/pangea/choreographer/igc/span_card.dart index 7ad6b0f16..3324849d3 100644 --- a/lib/pangea/choreographer/igc/span_card.dart +++ b/lib/pangea/choreographer/igc/span_card.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart'; import 'package:fluffychat/pangea/choreographer/igc/span_data_type_enum.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/utils/feedback_model.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import '../../../widgets/matrix.dart'; import '../../common/widgets/choice_array.dart'; @@ -156,7 +157,15 @@ class SpanCardState extends State { void _showFirstMatch() { final match = widget.choreographer.igcController.firstOpenMatch; - widget.choreographer.chatController.onSelectMatch(match); + if (match == null) { + MatrixState.pAnyState.closeAllOverlays(); + return; + } + OverlayUtil.showIGCMatch( + match, + widget.choreographer, + context, + ); } @override diff --git a/lib/pangea/choreographer/it/it_bar.dart b/lib/pangea/choreographer/it/it_bar.dart index 583789cef..7edd41bad 100644 --- a/lib/pangea/choreographer/it/it_bar.dart +++ b/lib/pangea/choreographer/it/it_bar.dart @@ -24,7 +24,10 @@ import '../../common/widgets/choice_array.dart'; class ITBar extends StatefulWidget { final Choreographer choreographer; - const ITBar({super.key, required this.choreographer}); + const ITBar({ + super.key, + required this.choreographer, + }); @override ITBarState createState() => ITBarState(); @@ -193,7 +196,9 @@ class ITBarState extends State with SingleTickerProviderStateMixin { child: child, ), child: CompositedTransformTarget( - link: widget.choreographer.itBarLinkAndKey.link, + link: MatrixState.pAnyState + .layerLinkAndKey(widget.choreographer.itBarTransformTargetKey) + .link, child: Column( children: [ if (!InstructionsEnum.clickBestOption.isToggledOff) ...[ @@ -204,7 +209,9 @@ class ITBarState extends State with SingleTickerProviderStateMixin { const SizedBox(height: 8.0), ], Container( - key: widget.choreographer.itBarLinkAndKey.key, + key: MatrixState.pAnyState + .layerLinkAndKey(widget.choreographer.itBarTransformTargetKey) + .key, decoration: BoxDecoration( borderRadius: const BorderRadius.only( topLeft: Radius.circular(24), diff --git a/lib/pangea/choreographer/text_editing/pangea_text_controller.dart b/lib/pangea/choreographer/text_editing/pangea_text_controller.dart index a07665066..31272bf4e 100644 --- a/lib/pangea/choreographer/text_editing/pangea_text_controller.dart +++ b/lib/pangea/choreographer/text_editing/pangea_text_controller.dart @@ -104,8 +104,8 @@ class PangeaTextController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final subscription = choreographer - .pangeaController.subscriptionController.subscriptionStatus; + final subscription = + MatrixState.pangeaController.subscriptionController.subscriptionStatus; if (subscription == SubscriptionStatus.shouldShowPaywall) { return _buildPaywallSpan(style); diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index dbaa4ef6d..5c7d3f244 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -656,7 +656,7 @@ class PangeaMessageEvent { final bool immersionMode = MatrixState .pangeaController.permissionsController - .isToolEnabled(ToolSetting.immersionMode, room); + .isToolEnabled(ToolSetting.immersionMode); final String? originalLangCode = originalSent?.langCode; diff --git a/lib/pangea/user/controllers/permissions_controller.dart b/lib/pangea/user/controllers/permissions_controller.dart index 169c75a18..f00351043 100644 --- a/lib/pangea/user/controllers/permissions_controller.dart +++ b/lib/pangea/user/controllers/permissions_controller.dart @@ -56,7 +56,7 @@ class PermissionsController extends BaseController { } } - bool isToolEnabled(ToolSetting setting, Room? room) { + bool isToolEnabled(ToolSetting setting) { // Rules can't be edited; default to true return userToolSetting(setting); // if (room?.isSpaceAdmin ?? false) {