From d951d5eee9ffa62ed08291f9c195b909abf4d96b Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:58:08 -0400 Subject: [PATCH] 3921 display unsubscribed errors for users (#3991) * url cleanup * chore: display unsubscribed errors differently --- lib/l10n/intl_en.arb | 5 +- .../activity_room_extension.dart | 21 +- .../activity_finished_status_message.dart | 15 +- .../morph_meaning_widget.dart | 19 +- .../vocab_analytics_details_view.dart | 2 +- .../controllers/it_feedback_controller.dart | 118 +------ .../controllers/word_net_controller.dart | 83 ----- .../choreographer/repo/similarity_repo.dart | 106 ------ lib/pangea/choreographer/repo/word_repo.dart | 45 --- .../widgets/igc/pangea_rich_text.dart | 203 ----------- .../widgets/igc/word_data_card.dart | 70 +--- lib/pangea/choreographer/widgets/it_bar.dart | 2 +- .../common/controllers/pangea_controller.dart | 6 - lib/pangea/common/network/requests.dart | 84 +---- lib/pangea/common/network/urls.dart | 90 +++-- .../common/widgets/error_indicator.dart | 13 +- .../event_wrappers/pangea_message_event.dart | 34 -- .../events/models/pangea_token_model.dart | 16 - .../controllers/language_controller.dart | 2 +- lib/pangea/lemmas/lemma_info_repo.dart | 50 +-- .../phonetic_transcription_widget.dart | 36 +- lib/pangea/toolbar/repo/image_repo.dart | 79 ----- .../widgets/message_unsubscribed_card.dart | 31 +- .../toolbar/widgets/overlay_message.dart | 14 +- .../widgets/reading_assistance_content.dart | 4 +- .../toolbar/widgets/select_mode_buttons.dart | 5 +- .../word_zoom/lemma_meaning_widget.dart | 16 +- .../word_zoom/morphological_list_item.dart | 323 ------------------ .../widgets/word_zoom/word_zoom_widget.dart | 2 +- 29 files changed, 175 insertions(+), 1319 deletions(-) delete mode 100644 lib/pangea/choreographer/controllers/word_net_controller.dart delete mode 100644 lib/pangea/choreographer/repo/similarity_repo.dart delete mode 100644 lib/pangea/choreographer/repo/word_repo.dart delete mode 100644 lib/pangea/choreographer/widgets/igc/pangea_rich_text.dart delete mode 100644 lib/pangea/toolbar/repo/image_repo.dart delete mode 100644 lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e3ee4ecbe..211e46716 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5243,5 +5243,8 @@ } } }, - "inviteFriendsToCourse": "Invite friends to my course" + "inviteFriendsToCourse": "Invite friends to my course", + "subscribeToUnlockActivitySummaries": "Subscribe to unlock activity summaries", + "subscribeToUnlockDefinitions": "Subscribe to unlock definitions", + "subscribeToUnlockTranscriptions": "Subscribe to unlock transcriptions" } diff --git a/lib/pangea/activity_sessions/activity_room_extension.dart b/lib/pangea/activity_sessions/activity_room_extension.dart index 0a356bc1b..185b5977a 100644 --- a/lib/pangea/activity_sessions/activity_room_extension.dart +++ b/lib/pangea/activity_sessions/activity_room_extension.dart @@ -15,6 +15,7 @@ import 'package:fluffychat/pangea/activity_summary/activity_summary_request_mode import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; @@ -190,15 +191,17 @@ extension ActivityRoomExtension on Room { ActivitySummaryRepo.delete(id, activityPlan!); } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: { - "roomID": id, - "activityPlan": activityPlan?.toJson(), - "activityResults": messages.map((m) => m.toJson()).toList(), - }, - ); + if (e is! UnsubscribedException) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "roomID": id, + "activityPlan": activityPlan?.toJson(), + "activityResults": messages.map((m) => m.toJson()).toList(), + }, + ); + } if (activitySummary?.summary == null) { await setActivitySummary( diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart index bad850fe6..c5f2e1639 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/saved_activity_analytics_dialog.dart'; import 'package:fluffychat/pangea/activity_summary/activity_summary_model.dart'; +import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -70,6 +71,9 @@ class ActivityFinishedStatusMessage extends StatelessWidget { } final theme = Theme.of(context); + final isSubscribed = + MatrixState.pangeaController.subscriptionController.isSubscribed; + return AnimatedSize( duration: FluffyThemes.animationDuration, child: Container( @@ -103,7 +107,16 @@ class ActivityFinishedStatusMessage extends StatelessWidget { width: 36.0, child: CircularProgressIndicator(), ), - ] else if (summary?.hasError ?? false) ...[ + ] else if (isSubscribed == false) + ErrorIndicator( + message: L10n.of(context) + .subscribeToUnlockActivitySummaries, + onTap: () { + MatrixState.pangeaController.subscriptionController + .showPaywall(context); + }, + ) + else if (summary?.hasError ?? false) ...[ Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pangea/analytics_details_popup/morph_meaning_widget.dart b/lib/pangea/analytics_details_popup/morph_meaning_widget.dart index a116ca647..d97a2b15d 100644 --- a/lib/pangea/analytics_details_popup/morph_meaning_widget.dart +++ b/lib/pangea/analytics_details_popup/morph_meaning_widget.dart @@ -1,15 +1,15 @@ -import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class MorphMeaningWidget extends StatefulWidget { final MorphFeaturesEnum feature; @@ -63,7 +63,6 @@ class MorphMeaningWidgetState extends State { final response = await _morphMeaning(); _setMeaningText(response); } catch (e) { - debugger(when: kDebugMode); _error = e; } finally { if (mounted) setState(() => _isLoading = false); @@ -117,9 +116,17 @@ class MorphMeaningWidgetState extends State { if (_error != null) { return Center( - child: ErrorIndicator( - message: L10n.of(context).errorFetchingDefinition, - ), + child: _error is UnsubscribedException + ? ErrorIndicator( + message: L10n.of(context).subscribeToUnlockDefinitions, + onTap: () { + MatrixState.pangeaController.subscriptionController + .showPaywall(context); + }, + ) + : ErrorIndicator( + message: L10n.of(context).errorFetchingDefinition, + ), ); } diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 75c751230..014816ee0 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -70,7 +70,7 @@ class VocabDetailsView extends StatelessWidget { }, ), if (MatrixState - .pangeaController.languageController.showTrancription) + .pangeaController.languageController.showTranscription) Padding( padding: const EdgeInsets.only(top: 4.0), child: PhoneticTranscriptionWidget( diff --git a/lib/pangea/choreographer/controllers/it_feedback_controller.dart b/lib/pangea/choreographer/controllers/it_feedback_controller.dart index ae39b7ffc..68ad1b9c5 100644 --- a/lib/pangea/choreographer/controllers/it_feedback_controller.dart +++ b/lib/pangea/choreographer/controllers/it_feedback_controller.dart @@ -1,120 +1,4 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; - -import 'package:collection/collection.dart'; -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/config/environment.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import '../../common/constants/model_keys.dart'; -import '../../common/controllers/pangea_controller.dart'; -import '../../common/network/requests.dart'; -import '../../common/network/urls.dart'; - -class ITFeedbackController { - late PangeaController _pangeaController; - - final List<_ITFeedbackCacheItem> _feedback = []; - - ITFeedbackController(PangeaController pangeaController) { - _pangeaController = pangeaController; - } - - _ITFeedbackCacheItem? _getLocal( - ITFeedbackRequestModel req, - ) => - _feedback.firstWhereOrNull( - (e) => - e.chosenContinuance == req.chosenContinuance && - e.sourceText == req.sourceText, - ); - - Future get( - ITFeedbackRequestModel req, - ) { - final _ITFeedbackCacheItem? localItem = _getLocal(req); - - if (localItem != null) return localItem.data; - - _feedback.add( - _ITFeedbackCacheItem( - chosenContinuance: req.chosenContinuance, - sourceText: req.sourceText, - data: _get(req), - ), - ); - - return _feedback.last.data; - } - - Future _get( - ITFeedbackRequestModel request, - ) async { - try { - final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( - _pangeaController.userController.accessToken, - request, - ); - return res; - } catch (err, stack) { - debugPrint( - "error getting contextual definition for ${request.chosenContinuance} in '${request.sourceText}'", - ); - ErrorHandler.logError(e: err, s: stack, data: request.toJson()); - return null; - } - } -} - -class _ITFeedbackCacheItem { - String chosenContinuance; - String sourceText; - Future data; - - _ITFeedbackCacheItem({ - required this.chosenContinuance, - required this.sourceText, - required this.data, - }); -} - -class _ITFeedbackRepo { - static Future get( - String accessToken, - ITFeedbackRequestModel request, - ) async { - final Requests req = Requests( - choreoApiKey: Environment.choreoApiKey, - accessToken: accessToken, - ); - - final Response res = await req.post( - url: PApiUrls.itFeedback, - body: request.toJson(), - ); - - final ITFeedbackResponseModel response = ITFeedbackResponseModel.fromJson( - jsonDecode( - utf8.decode(res.bodyBytes).toString(), - ), - ); - - if (response.text.isEmpty) { - ErrorHandler.logError( - e: Exception( - "empty text in contextual definition response", - ), - data: { - "request": request.toJson(), - "accessToken": accessToken, - }, - ); - } - - return response; - } -} +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; class ITFeedbackRequestModel { final String sourceText; diff --git a/lib/pangea/choreographer/controllers/word_net_controller.dart b/lib/pangea/choreographer/controllers/word_net_controller.dart deleted file mode 100644 index e4989c966..000000000 --- a/lib/pangea/choreographer/controllers/word_net_controller.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:http/http.dart' as http; - -import 'package:fluffychat/pangea/choreographer/repo/word_repo.dart'; -import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; -import '../../common/controllers/base_controller.dart'; -import '../../common/controllers/pangea_controller.dart'; -import '../models/word_data_model.dart'; - -class WordController extends BaseController { - late PangeaController _pangeaController; - - final List _wordData = []; - - WordController(PangeaController pangeaController) : super() { - _pangeaController = pangeaController; - } - - WordData? getWordDataLocal({ - required String word, - required String fullText, - required String? userL1, - required String? userL2, - }) => - _wordData.firstWhereOrNull( - (e) => e.isMatch( - w: word, - f: fullText, - l1: userL1, - l2: userL2, - ), - ); - - Future getWordDataGlobal({ - required String word, - required String fullText, - required String? userL1, - required String? userL2, - }) async { - if (userL1 == null || - userL2 == null || - userL1 == LanguageKeys.unknownLanguage || - userL2 == LanguageKeys.unknownLanguage) { - throw http.Response("", 405); - } - - final WordData? local = getWordDataLocal( - word: word, - fullText: fullText, - userL1: userL1, - userL2: userL2, - ); - - if (local != null) return local; - - final WordData remote = await WordRepo.getWordNetData( - accessToken: _pangeaController.userController.accessToken, - fullText: fullText, - word: word, - userL1: userL1, - userL2: userL2, - ); - - _addWordData(remote); - - return remote; - } - - _addWordData(WordData w) { - final WordData? local = getWordDataLocal( - word: w.word, - fullText: w.fullText, - userL1: w.userL1, - userL2: w.userL2, - ); - - if (local == null) { - if (_wordData.length > 100) _wordData.clear(); - _wordData.add(w); - setState(null); - } - } -} diff --git a/lib/pangea/choreographer/repo/similarity_repo.dart b/lib/pangea/choreographer/repo/similarity_repo.dart deleted file mode 100644 index 9cc034846..000000000 --- a/lib/pangea/choreographer/repo/similarity_repo.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/config/environment.dart'; -import '../../common/network/requests.dart'; -import '../../common/network/urls.dart'; - -class SimilarityRepo { - static Future get({ - required String accessToken, - required SimilarityRequestModel request, - }) async { - final Requests req = Requests( - choreoApiKey: Environment.choreoApiKey, - accessToken: accessToken, - ); - - final Response res = await req.post( - url: PApiUrls.similarity, - body: request.toJson(), - ); - - final SimilartyResponseModel response = SimilartyResponseModel.fromJson( - jsonDecode( - utf8.decode(res.bodyBytes).toString(), - ), - ); - - return response; - } -} - -class SimilarityRequestModel { - String benchmark; - List toCompare; - - SimilarityRequestModel({required this.benchmark, required this.toCompare}); - - Map toJson() => { - "original": benchmark, - "to_compare": toCompare, - }; -} - -class SimilartyResponseModel { - String benchmark; - List scores; - - SimilartyResponseModel({required this.benchmark, required this.scores}); - - factory SimilartyResponseModel.fromJson( - Map json, - ) => - SimilartyResponseModel( - benchmark: json["original"], - scores: List.from( - json["scores"].map( - (x) => SimilarityScore.fromJson(x), - ), - ), - ); - - SimilarityScore get highestScore { - SimilarityScore highest = scores.first; - for (final SimilarityScore score in scores) { - if (score.score > highest.score) { - highest = score; - } - } - return highest; - } - - bool userTranslationIsDifferentButBetter(String userTranslation) { - return highestScore.text == userTranslation; - } - - bool userTranslationIsSameAsBotTranslation(String userTranslation) { - return highestScore.text == userTranslation && - scores.where((e) => e.text == userTranslation).length == 2; - } - - num userScore(String userTranslation) { - return scores.firstWhere((e) => e.text == userTranslation).score; - } -} - -class SimilarityScore { - String text; - double score; - int index; - - SimilarityScore({ - required this.text, - required this.score, - required this.index, - }); - - factory SimilarityScore.fromJson(Map json) { - return SimilarityScore( - text: json["text"], - score: json["score"], - index: json["index"], - ); - } -} diff --git a/lib/pangea/choreographer/repo/word_repo.dart b/lib/pangea/choreographer/repo/word_repo.dart deleted file mode 100644 index ebe426d52..000000000 --- a/lib/pangea/choreographer/repo/word_repo.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/config/environment.dart'; -import '../../common/constants/model_keys.dart'; -import '../../common/network/requests.dart'; -import '../../common/network/urls.dart'; -import '../models/word_data_model.dart'; - -class WordRepo { - static Future getWordNetData({ - required String accessToken, - required String fullText, - required String word, - required String userL1, - required String userL2, - }) async { - final Requests req = Requests( - choreoApiKey: Environment.choreoApiKey, - accessToken: accessToken, - ); - final Response res = await req.post( - url: PApiUrls.wordNet, - body: { - ModelKey.word: word, - ModelKey.fullText: fullText, - ModelKey.userL1: userL1, - ModelKey.userL2: userL2, - }, - ); - - final json = jsonDecode(utf8.decode(res.bodyBytes)); - - final WordData wordData = WordData.fromJson( - json, - fullText: fullText, - word: word, - userL1: userL1, - userL2: userL2, - ); - - return wordData; - } -} diff --git a/lib/pangea/choreographer/widgets/igc/pangea_rich_text.dart b/lib/pangea/choreographer/widgets/igc/pangea_rich_text.dart deleted file mode 100644 index 03ff38c76..000000000 --- a/lib/pangea/choreographer/widgets/igc/pangea_rich_text.dart +++ /dev/null @@ -1,203 +0,0 @@ -import 'dart:developer'; -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; -import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; -import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_toolbar_selection_area.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../models/pangea_match_model.dart'; - -class PangeaRichText extends StatefulWidget { - final PangeaMessageEvent pangeaMessageEvent; - final bool immersionMode; - final TextStyle? style; - final bool isOverlay; - final ChatController controller; - final Event? nextEvent; - final Event? prevEvent; - - const PangeaRichText({ - super.key, - required this.pangeaMessageEvent, - required this.immersionMode, - required this.isOverlay, - required this.controller, - this.nextEvent, - this.prevEvent, - this.style, - }); - - @override - PangeaRichTextState createState() => PangeaRichTextState(); -} - -class PangeaRichTextState extends State { - final PangeaController pangeaController = MatrixState.pangeaController; - bool _fetchingRepresentation = false; - double get blur => (_fetchingRepresentation && widget.immersionMode) || - !pangeaController.languageController.languagesSet - ? 5 - : 0; - String textSpan = ""; - PangeaRepresentation? repEvent; - - @override - void initState() { - super.initState(); - setTextSpan(); - } - - @override - void didUpdateWidget(PangeaRichText oldWidget) { - super.didUpdateWidget(oldWidget); - setTextSpan(); - } - - void _setTextSpan(String newTextSpan) { - try { - if (!mounted) return; // Early exit if the widget is no longer in the tree - setState(() { - textSpan = newTextSpan; - }); - } catch (error, stackTrace) { - ErrorHandler.logError( - e: PangeaWarningError(error), - s: stackTrace, - m: "Error setting text span in PangeaRichText", - data: { - "newTextSpan": newTextSpan, - }, - ); - } - } - - void setTextSpan() { - if (_fetchingRepresentation) { - _setTextSpan( - widget.pangeaMessageEvent.event - .getDisplayEvent(widget.pangeaMessageEvent.timeline) - .body, - ); - return; - } - - if (widget.pangeaMessageEvent.eventId.contains("webdebug")) { - debugger(when: kDebugMode); - } - - repEvent = widget.pangeaMessageEvent.messageDisplayRepresentation?.content; - if (repEvent == null) { - setState(() => _fetchingRepresentation = true); - widget.pangeaMessageEvent - .representationByLanguageGlobal( - langCode: widget.pangeaMessageEvent.messageDisplayLangCode, - ) - .onError((error, stackTrace) { - ErrorHandler.logError( - e: PangeaWarningError(error), - s: stackTrace, - m: "Error fetching representation", - data: { - "langCode": widget.pangeaMessageEvent.messageDisplayLangCode, - }, - ); - return null; - }).then((event) { - if (!mounted) return; - repEvent = event; - _setTextSpan(repEvent?.text ?? widget.pangeaMessageEvent.body); - }).whenComplete(() { - if (mounted) { - setState(() => _fetchingRepresentation = false); - } - }); - - _setTextSpan(widget.pangeaMessageEvent.body); - } else { - _setTextSpan(repEvent!.text); - } - } - - @override - Widget build(BuildContext context) { - if (blur > 0) { - instructionsShowPopup( - context, - InstructionsEnum.blurMeansTranslate, - widget.pangeaMessageEvent.eventId, - ); - } - - //TODO - take out of build function of every message - final Widget richText = ToolbarSelectionArea( - event: widget.pangeaMessageEvent.event, - isOverlay: widget.isOverlay, - pangeaMessageEvent: widget.pangeaMessageEvent, - controller: widget.controller, - nextEvent: widget.nextEvent, - prevEvent: widget.prevEvent, - child: RichText( - text: TextSpan( - text: textSpan, - style: widget.style, - children: [ - if (_fetchingRepresentation) - const WidgetSpan( - child: Padding( - padding: EdgeInsets.only(left: 5.0), - child: SizedBox( - height: 14, - width: 14, - child: CircularProgressIndicator( - strokeWidth: 2.0, - color: AppConfig.secondaryColor, - ), - ), - ), - ), - ], - ), - ), - ); - - return blur > 0 - ? ImageFiltered( - imageFilter: ImageFilter.blur( - sigmaX: blur, - sigmaY: blur, - ), - child: richText, - ) - : richText; - } - - Future onIgnore() async { - debugPrint("PTODO implement onIgnore"); - } - - Future onITStart() async { - debugPrint("PTODO implement onITStart"); - } - - Future onReplacementSelect( - PangeaMatch pangeaMatch, - String replacement, - ) async { - debugPrint("PTODO implement onReplacementSelect"); - } - - Future onSentenceRewrite(String sentenceRewrite) async { - debugPrint("PTODO implement onSentenceRewrite"); - } -} diff --git a/lib/pangea/choreographer/widgets/igc/word_data_card.dart b/lib/pangea/choreographer/widgets/igc/word_data_card.dart index bfa48c9f5..d4228270f 100644 --- a/lib/pangea/choreographer/widgets/igc/word_data_card.dart +++ b/lib/pangea/choreographer/widgets/igc/word_data_card.dart @@ -48,11 +48,8 @@ class WordDataCard extends StatefulWidget { class WordDataCardController extends State { final PangeaController controller = MatrixState.pangeaController; - bool isLoadingWordNet = false; bool isLoadingContextualDefinition = false; ContextualDefinitionResponseModel? contextualDefinitionRes; - WordData? wordData; - Object? wordNetError; Object? definitionError; LanguageModel? activeL1; @@ -66,12 +63,9 @@ class WordDataCardController extends State { activeL1 = controller.languageController.activeL1Model()!; activeL2 = controller.languageController.activeL2Model()!; if (activeL1 == null || activeL2 == null) { - wordNetError = noLanguages; definitionError = noLanguages; - } else if (!widget.hasInfo) { - getContextualDefinition(); } else { - getWordNet(); + getContextualDefinition(); } super.initState(); } @@ -80,11 +74,7 @@ class WordDataCardController extends State { void didUpdateWidget(covariant WordDataCard oldWidget) { // debugger(when: kDebugMode); if (oldWidget.word != widget.word) { - if (!widget.hasInfo) { - getContextualDefinition(); - } else { - getWordNet(); - } + getContextualDefinition(); } super.didUpdateWidget(oldWidget); } @@ -127,32 +117,6 @@ class WordDataCardController extends State { } } - Future getWordNet() async { - if (mounted) { - setState(() { - wordData = null; - isLoadingWordNet = true; - }); - } - try { - wordData = await controller.wordNet.getWordDataGlobal( - word: widget.word, - fullText: widget.fullText, - userL1: activeL1?.langCode, - userL2: activeL2?.langCode, - ); - } catch (err) { - ErrorHandler.logError( - e: err, - s: StackTrace.current, - data: {"word": widget.word, "hasInfo": widget.hasInfo}, - ); - wordNetError = err; - } finally { - if (mounted) setState(() => isLoadingWordNet = false); - } - } - void handleGetDefinitionButtonPress() { if (isLoadingContextualDefinition) return; getContextualDefinition(); @@ -172,12 +136,6 @@ class WordDataCardView extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.wordNetError != null) { - return CardErrorWidget( - error: controller.wordNetError!.toString(), - maxWidth: AppConfig.toolbarMinWidth, - ); - } if (controller.activeL1 == null || controller.activeL2 == null) { ErrorHandler.logError( m: "should not be here", @@ -210,30 +168,6 @@ class WordDataCardView extends StatelessWidget { style: BotStyle.text(context), ), const SizedBox(height: 5.0), - if (controller.wordData != null && - controller.wordNetError == null && - controller.activeL1 != null && - controller.activeL2 != null) - WordNetInfo( - wordData: controller.wordData!, - activeL1: controller.activeL1!, - activeL2: controller.activeL2!, - ), - if (controller.isLoadingWordNet) - const ToolbarContentLoadingIndicator(), - const SizedBox(height: 5.0), - // if (controller.widget.hasInfo && - // !controller.isLoadingContextualDefinition && - // controller.contextualDefinitionRes == null) - // Material( - // type: MaterialType.transparency, - // child: ListTile( - // leading: const BotFace( - // width: 40, expression: BotExpression.surprised), - // title: Text(L10n.of(context).askPangeaBot), - // onTap: controller.handleGetDefinitionButtonPress, - // ), - // ), if (controller.isLoadingContextualDefinition) const ToolbarContentLoadingIndicator(), if (controller.contextualDefinitionRes != null) diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index eb713ba9a..b9954e630 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart'; +import 'package:fluffychat/pangea/choreographer/controllers/it_feedback_controller.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/word_data_card.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -17,7 +18,6 @@ import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart' import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../common/utils/overlay.dart'; -import '../controllers/it_feedback_controller.dart'; import '../models/it_response_model.dart'; import 'choice_array.dart'; diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index b8d4b5867..b63cfca8d 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -12,7 +12,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart'; -import 'package:fluffychat/pangea/choreographer/controllers/word_net_controller.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; @@ -28,7 +27,6 @@ import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart'; import 'package:fluffychat/pangea/user/controllers/user_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import '../../choreographer/controllers/it_feedback_controller.dart'; import '../utils/firebase_analytics.dart'; class PangeaController { @@ -39,12 +37,10 @@ class PangeaController { late PermissionsController permissionsController; late GetAnalyticsController getAnalytics; late PutAnalyticsController putAnalytics; - late WordController wordNet; late MessageDataController messageData; // TODO: make these static so we can remove from here late ContextualDefinitionController definitions; - late ITFeedbackController itFeedback; late SubscriptionController subscriptionController; late TextToSpeechController textToSpeech; late SpeechToTextController speechToText; @@ -91,10 +87,8 @@ class PangeaController { getAnalytics = GetAnalyticsController(this); putAnalytics = PutAnalyticsController(this); messageData = MessageDataController(this); - wordNet = WordController(this); definitions = ContextualDefinitionController(this); subscriptionController = SubscriptionController(this); - itFeedback = ITFeedbackController(this); textToSpeech = TextToSpeechController(this); speechToText = SpeechToTextController(this); PAuthGaurd.pController = this; diff --git a/lib/pangea/common/network/requests.dart b/lib/pangea/common/network/requests.dart index 9e3162814..69eeb5196 100644 --- a/lib/pangea/common/network/requests.dart +++ b/lib/pangea/common/network/requests.dart @@ -10,14 +10,11 @@ import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_en import 'package:fluffychat/widgets/matrix.dart'; class Requests { - late String? baseUrl; - // Matrix access token late String? accessToken; late String? choreoApiKey; - //Question: How can we make baseUrl optional? + Requests({ this.accessToken, - this.baseUrl = '', this.choreoApiKey, }); @@ -31,84 +28,31 @@ class Requests { dynamic encoded; encoded = jsonEncode(body); - debugPrint(baseUrl! + url); - final http.Response response = await http.post( - _uriBuilder(url), - body: encoded, - headers: _headers, - ); - handleError(response, body: body); - - return response; - } - - Future put({ - required String url, - required Map body, - }) async { - body[ModelKey.cefrLevel] = MatrixState - .pangeaController.userController.profile.userSettings.cefrLevel.string; - - dynamic encoded; - encoded = jsonEncode(body); - - debugPrint(baseUrl! + url); - - final http.Response response = await http.put( - _uriBuilder(url), + Uri.parse(url), body: encoded, headers: _headers, ); handleError(response, body: body); - return response; } - Future patch({ - required String url, - required Map body, - }) async { - body[ModelKey.cefrLevel] = MatrixState - .pangeaController.userController.profile.userSettings.cefrLevel.string; - - dynamic encoded; - encoded = jsonEncode(body); - - debugPrint(baseUrl! + url); - - final http.Response response = await http.patch( - _uriBuilder(url), - body: encoded, - headers: _headers, - ); - - handleError(response, body: body); - - return response; - } - - Future get({required String url, String objectId = ""}) async { + Future get({required String url}) async { final http.Response response = - await http.get(_uriBuilder(url + objectId), headers: _headers); - - handleError(response, objectId: objectId); + await http.get(Uri.parse(url), headers: _headers); + handleError(response); return response; } - Uri _uriBuilder(url) => - baseUrl != null ? Uri.parse(baseUrl! + url) : Uri.parse(url); - void addBreadcrumb( http.Response response, { Map? body, - String? objectId, }) { debugPrint("Error - code: ${response.statusCode}"); debugPrint("api: ${response.request?.url}"); - debugPrint("request body: ${body ?? objectId}"); + debugPrint("request body: $body"); Sentry.addBreadcrumb( Breadcrumb.http( url: response.request?.url ?? Uri(path: "not available"), @@ -117,20 +61,23 @@ class Requests { ), ); Sentry.addBreadcrumb( - Breadcrumb(data: {"body": body, "objectId": objectId}), + Breadcrumb(data: {"body": body}), ); } void handleError( http.Response response, { Map? body, - String? objectId, }) { - //PTODO - handle 401 error - unauthorized call - //kick them back to login? + if (response.statusCode == 401) { + final responseBody = jsonDecode(utf8.decode(response.bodyBytes)); + if (responseBody['detail'] == 'No active subscription found') { + throw UnsubscribedException(); + } + } if (response.statusCode >= 400) { - addBreadcrumb(response, body: body, objectId: objectId); + addBreadcrumb(response, body: body); throw response; } } @@ -142,7 +89,6 @@ class Requests { }; if (accessToken != null) { headers["Authorization"] = 'Bearer ${accessToken!}'; - //headers["Matrix-Access-Token"] = accessToken!; } if (choreoApiKey != null) { headers['api_key'] = choreoApiKey!; @@ -150,3 +96,5 @@ class Requests { return headers; } } + +class UnsubscribedException implements Exception {} diff --git a/lib/pangea/common/network/urls.dart b/lib/pangea/common/network/urls.dart index d64a48d8a..5e436ee15 100644 --- a/lib/pangea/common/network/urls.dart +++ b/lib/pangea/common/network/urls.dart @@ -9,79 +9,73 @@ import 'package:fluffychat/pangea/common/config/environment.dart'; /// /// https://api.staging.pangea.chat/api/v1/ class PApiUrls { - static String choreoPrefix = "/choreo"; - static String subscriptionPrefix = "/subscription"; - static String accountPrefix = "/account"; + static const String _choreoPrefix = "/choreo"; + static const String _subscriptionPrefix = "/subscription"; - static String get choreoEndpoint => - "${Environment.choreoApi}${PApiUrls.choreoPrefix}"; - static String get subscriptionEndpoint => - "${Environment.choreoApi}${PApiUrls.subscriptionPrefix}"; - static String get accountEndpoint => - "${Environment.choreoApi}${PApiUrls.accountPrefix}"; + static String get _choreoEndpoint => + "${Environment.choreoApi}${PApiUrls._choreoPrefix}"; + static String get _subscriptionEndpoint => + "${Environment.choreoApi}${PApiUrls._subscriptionPrefix}"; /// ---------------------- Util -------------------------------------- - static String appVersion = "${PApiUrls.choreoEndpoint}/version"; + static String appVersion = "${PApiUrls._choreoEndpoint}/version"; /// ---------------------- Languages -------------------------------------- - static String getLanguages = "${PApiUrls.choreoEndpoint}/languages_v2"; + static String getLanguages = "${PApiUrls._choreoEndpoint}/languages_v2"; /// ---------------------- Users -------------------------------------- - static String paymentLink = "${PApiUrls.subscriptionEndpoint}/payment_link"; + static String paymentLink = "${PApiUrls._subscriptionEndpoint}/payment_link"; static String languageDetection = - "${PApiUrls.choreoEndpoint}/language_detection"; + "${PApiUrls._choreoEndpoint}/language_detection"; - static String igcLite = "${PApiUrls.choreoEndpoint}/grammar_lite"; - static String spanDetails = "${PApiUrls.choreoEndpoint}/span_details"; + static String igcLite = "${PApiUrls._choreoEndpoint}/grammar_lite"; + static String spanDetails = "${PApiUrls._choreoEndpoint}/span_details"; - static String wordNet = "${PApiUrls.choreoEndpoint}/wordnet"; static String simpleTranslation = - "${PApiUrls.choreoEndpoint}/translation/direct"; - static String tokenize = "${PApiUrls.choreoEndpoint}/tokenize"; + "${PApiUrls._choreoEndpoint}/translation/direct"; + static String tokenize = "${PApiUrls._choreoEndpoint}/tokenize"; static String contextualDefinition = - "${PApiUrls.choreoEndpoint}/contextual_definition"; - static String similarity = "${PApiUrls.choreoEndpoint}/similarity"; + "${PApiUrls._choreoEndpoint}/contextual_definition"; - static String itFeedback = "${PApiUrls.choreoEndpoint}/translation/feedback"; + static String firstStep = "${PApiUrls._choreoEndpoint}/it_initialstep"; - static String firstStep = "${PApiUrls.choreoEndpoint}/it_initialstep"; - - static String textToSpeech = "${PApiUrls.choreoEndpoint}/text_to_speech"; - static String speechToText = "${PApiUrls.choreoEndpoint}/speech_to_text"; + static String textToSpeech = "${PApiUrls._choreoEndpoint}/text_to_speech"; + static String speechToText = "${PApiUrls._choreoEndpoint}/speech_to_text"; + static String phoneticTranscription = + "${PApiUrls._choreoEndpoint}/phonetic_transcription"; static String messageActivityGeneration = - "${PApiUrls.choreoEndpoint}/practice"; + "${PApiUrls._choreoEndpoint}/practice"; - static String lemmaDictionary = "${PApiUrls.choreoEndpoint}/lemma_definition"; + static String lemmaDictionary = + "${PApiUrls._choreoEndpoint}/lemma_definition"; static String lemmaDictionaryEdit = - "${PApiUrls.choreoEndpoint}/lemma_definition/edit"; - static String morphDictionary = "${PApiUrls.choreoEndpoint}/morph_meaning"; + "${PApiUrls._choreoEndpoint}/lemma_definition/edit"; + static String morphDictionary = "${PApiUrls._choreoEndpoint}/morph_meaning"; - static String activityPlan = "${PApiUrls.choreoEndpoint}/activity_plan"; - static String activityPlanGeneration = - "${PApiUrls.choreoEndpoint}/activity_plan/generate"; - static String activityPlanSearch = - "${PApiUrls.choreoEndpoint}/activity_plan/search"; + // static String activityPlan = "${PApiUrls._choreoEndpoint}/activity_plan"; + // static String activityPlanGeneration = + // "${PApiUrls._choreoEndpoint}/activity_plan/generate"; + // static String activityPlanSearch = + // "${PApiUrls._choreoEndpoint}/activity_plan/search"; + // static String activityModeList = "${PApiUrls._choreoEndpoint}/modes"; + // static String objectiveList = "${PApiUrls._choreoEndpoint}/objectives"; + // static String topicList = "${PApiUrls._choreoEndpoint}/topics"; - static String activityModeList = "${PApiUrls.choreoEndpoint}/modes"; - static String objectiveList = "${PApiUrls.choreoEndpoint}/objectives"; - static String topicList = "${PApiUrls.choreoEndpoint}/topics"; + static String activitySummary = + "${PApiUrls._choreoEndpoint}/activity_summary"; - static String activitySummary = "${PApiUrls.choreoEndpoint}/activity_summary"; - - static String morphFeaturesAndTags = "${PApiUrls.choreoEndpoint}/morphs"; + static String morphFeaturesAndTags = "${PApiUrls._choreoEndpoint}/morphs"; static String constructSummary = - "${PApiUrls.choreoEndpoint}/construct_summary"; + "${PApiUrls._choreoEndpoint}/construct_summary"; ///-------------------------------- revenue cat -------------------------- - static String rcAppsChoreo = "${PApiUrls.subscriptionEndpoint}/app_ids"; + static String rcAppsChoreo = "${PApiUrls._subscriptionEndpoint}/app_ids"; static String rcProductsChoreo = - "${PApiUrls.subscriptionEndpoint}/all_products"; - static String rcProductsTrial = "${PApiUrls.subscriptionEndpoint}/free_trial"; + "${PApiUrls._subscriptionEndpoint}/all_products"; + static String rcProductsTrial = + "${PApiUrls._subscriptionEndpoint}/free_trial"; - static String rcSubscription = PApiUrls.subscriptionEndpoint; - - static String phoneticTranscription = - "${PApiUrls.choreoEndpoint}/phonetic_transcription"; + static String rcSubscription = PApiUrls._subscriptionEndpoint; } diff --git a/lib/pangea/common/widgets/error_indicator.dart b/lib/pangea/common/widgets/error_indicator.dart index e5913eed0..8e9f30397 100644 --- a/lib/pangea/common/widgets/error_indicator.dart +++ b/lib/pangea/common/widgets/error_indicator.dart @@ -4,17 +4,19 @@ class ErrorIndicator extends StatelessWidget { final String message; final double? iconSize; final TextStyle? style; + final VoidCallback? onTap; const ErrorIndicator({ super.key, required this.message, this.iconSize, this.style, + this.onTap, }); @override Widget build(BuildContext context) { - return Row( + final content = Row( mainAxisSize: MainAxisSize.min, children: [ Icon( @@ -32,5 +34,14 @@ class ErrorIndicator extends StatelessWidget { ), ], ); + + if (onTap != null) { + return TextButton( + onPressed: onTap, + child: content, + ); + } + + return content; } } diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index 22d977359..aafdad1f3 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -506,40 +506,6 @@ class PangeaMessageEvent { (filter?.call(element) ?? true), ); - Future representationByLanguageGlobal({ - required String langCode, - }) async { - final RepresentationEvent? repLocal = representationByLanguage(langCode); - - if (repLocal != null || - langCode == LanguageKeys.unknownLanguage || - langCode == LanguageKeys.mixedLanguage || - langCode == LanguageKeys.multiLanguage) { - return repLocal?.content; - } - - if (eventId.contains("Pangea Chat")) return null; - - // should this just be the original event body? - // worth a conversation with the team - final PangeaRepresentation? basis = originalSent?.content; - - // clear representations cache so the new representation event can be added - // when next requested - _representations = null; - - return MatrixState.pangeaController.messageData.getPangeaRepresentation( - req: FullTextTranslationRequestModel( - text: basis?.text ?? _latestEdit.body, - srcLang: basis?.langCode, - tgtLang: langCode, - userL2: l2Code ?? LanguageKeys.unknownLanguage, - userL1: l1Code ?? LanguageKeys.unknownLanguage, - ), - messageEvent: _event, - ); - } - Future representationByDetectedLanguage() async { LanguageDetectionResponse? resp; try { diff --git a/lib/pangea/events/models/pangea_token_model.dart b/lib/pangea/events/models/pangea_token_model.dart index c96d9626f..c71bcdc55 100644 --- a/lib/pangea/events/models/pangea_token_model.dart +++ b/lib/pangea/events/models/pangea_token_model.dart @@ -14,9 +14,6 @@ import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/constructs/construct_level_enum.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; -import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; -import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart'; -import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart'; import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_repo.dart'; @@ -438,19 +435,6 @@ class PangeaToken { .cast() .toList(); - Future> getEmojiChoices() => LemmaInfoRepo.get( - LemmaInfoRequest( - lemma: lemma.text, - partOfSpeech: pos, - lemmaLang: MatrixState - .pangeaController.languageController.userL2?.langCode ?? - LanguageKeys.unknownLanguage, - userL1: MatrixState - .pangeaController.languageController.userL1?.langCode ?? - LanguageKeys.defaultLanguage, - ), - ).then((onValue) => onValue.emoji); - ConstructIdentifier get vocabConstructID => ConstructIdentifier( lemma: lemma.text, type: ConstructTypeEnum.vocab, diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index 68dd7d021..e76270af6 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -113,7 +113,7 @@ class LanguageController { // return model; } - bool get showTrancription => + bool get showTranscription => (_pangeaController.languageController.userL1 != null && _pangeaController.languageController.userL2 != null && _pangeaController.languageController.userL1?.script != diff --git a/lib/pangea/lemmas/lemma_info_repo.dart b/lib/pangea/lemmas/lemma_info_repo.dart index d3acc5144..489d6b35b 100644 --- a/lib/pangea/lemmas/lemma_info_repo.dart +++ b/lib/pangea/lemmas/lemma_info_repo.dart @@ -1,7 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart'; @@ -9,7 +6,6 @@ import 'package:http/http.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/network/urls.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/lemmas/lemma_edit_request.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; @@ -40,7 +36,8 @@ class LemmaInfoRepo { return null; } - static Future _fetch(LemmaInfoRequest request) async { + /// Get lemma info, prefering user set data over fetched data + static Future get(LemmaInfoRequest request) async { final cached = getCached(request); if (cached != null) return cached; @@ -58,52 +55,9 @@ class LemmaInfoRepo { final response = LemmaInfoResponse.fromJson(decodedBody); set(request, response); - - // debugPrint( - // 'fetched data for ${request.lemma} ${response.toJson()}', - // ); - return response; } - /// Get lemma info, prefering user set data over fetched data - static Future get(LemmaInfoRequest request) async { - try { - return await _fetch(request); - // if the user has either emojis or meaning in the past, use those first - // final UserSetLemmaInfo? userSetLemmaInfo = request.cId.userLemmaInfo; - - // final List emojis = userSetLemmaInfo?.emojis ?? []; - // String? meaning = userSetLemmaInfo?.meaning; - - // if the user has not set these, fetch from the server - // if (emojis.length < maxEmojisPerLemma || meaning == null) { - // final LemmaInfoResponse fetched = await _fetch(request); - - // while (emojis.length < maxEmojisPerLemma && fetched.emoji.isNotEmpty) { - // final maybeToAdd = fetched.emoji.removeAt(0); - // if (!emojis.contains(maybeToAdd)) { - // emojis.add(maybeToAdd); - // } - // } - // meaning ??= fetched.meaning; - // } else { - // // debugPrint( - // // 'using user set data for ${request.lemma} ${userSetLemmaInfo?.toJson()}', - // // ); - // } - - // return LemmaInfoResponse( - // emoji: emojis, - // meaning: meaning, - // ); - } catch (e) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: e, data: request.toJson()); - rethrow; - } - } - static Future edit(LemmaEditRequest request) async { final Requests req = Requests( choreoApiKey: Environment.choreoApiKey, diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 56e6e910a..59d71d1d2 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; @@ -98,14 +99,16 @@ class _PhoneticTranscriptionWidgetState .first.phoneticL1Transcription.content; } catch (e, s) { _error = e; - ErrorHandler.logError( - e: e, - s: s, - data: { - 'text': widget.text, - 'textLanguageCode': widget.textLanguage.langCode, - }, - ); + if (e is! UnsubscribedException) { + ErrorHandler.logError( + e: e, + s: s, + data: { + 'text': widget.text, + 'textLanguageCode': widget.textLanguage.langCode, + }, + ); + } } finally { if (mounted) { setState(() { @@ -157,9 +160,20 @@ class _PhoneticTranscriptionWidgetState mainAxisSize: MainAxisSize.min, children: [ if (_error != null) - ErrorIndicator( - message: L10n.of(context).failedToFetchTranscription, - ) + _error is UnsubscribedException + ? ErrorIndicator( + message: L10n.of(context) + .subscribeToUnlockTranscriptions, + onTap: () { + MatrixState + .pangeaController.subscriptionController + .showPaywall(context); + }, + ) + : ErrorIndicator( + message: + L10n.of(context).failedToFetchTranscription, + ) else if (_isLoading || _transcription == null) const SizedBox( width: 16, diff --git a/lib/pangea/toolbar/repo/image_repo.dart b/lib/pangea/toolbar/repo/image_repo.dart deleted file mode 100644 index d44fc9104..000000000 --- a/lib/pangea/toolbar/repo/image_repo.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:convert'; -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; - -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../common/config/environment.dart'; -import '../../common/network/requests.dart'; - -class GenerateImageeResponse { - final String imageUrl; - final String prompt; - - GenerateImageeResponse({ - required this.imageUrl, - required this.prompt, - }); - - factory GenerateImageeResponse.fromJson(Map json) { - return GenerateImageeResponse( - imageUrl: json['image_url'], - prompt: json['prompt'], - ); - } - - factory GenerateImageeResponse.error() { - // TODO: Implement better error handling - return GenerateImageeResponse( - imageUrl: 'https://i.imgur.com/2L2JYqk.png', - prompt: 'Error', - ); - } -} - -class GenerateImageRequest { - String prompt; - - GenerateImageRequest({required this.prompt}); - - Map toJson() => { - 'prompt': prompt, - }; -} - -class ImageRepo { - static Future fetchImage( - GenerateImageRequest request, - ) async { - final Requests req = Requests( - choreoApiKey: Environment.choreoApiKey, - accessToken: MatrixState.pangeaController.userController.accessToken, - ); // Set your API base URL - final requestBody = request.toJson(); - - try { - final Response res = await req.post( - url: '/generate-image/', // Endpoint in your FastAPI server - body: requestBody, - ); - - if (res.statusCode == 200) { - final decodedBody = jsonDecode(utf8.decode(res.bodyBytes)); - return GenerateImageeResponse.fromJson( - decodedBody, - ); // Convert response to ImageModel - } else { - throw Exception('Failed to load image'); - } - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack, data: requestBody); - return GenerateImageeResponse - .error(); // Return an error model or handle accordingly - } - } -} diff --git a/lib/pangea/toolbar/widgets/message_unsubscribed_card.dart b/lib/pangea/toolbar/widgets/message_unsubscribed_card.dart index a2f369224..d9b2528e5 100644 --- a/lib/pangea/toolbar/widgets/message_unsubscribed_card.dart +++ b/lib/pangea/toolbar/widgets/message_unsubscribed_card.dart @@ -3,22 +3,13 @@ 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/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/widgets/matrix.dart'; class MessageUnsubscribedCard extends StatelessWidget { - final MessageOverlayController controller; - - const MessageUnsubscribedCard({ - super.key, - required this.controller, - }); + const MessageUnsubscribedCard({super.key}); @override Widget build(BuildContext context) { - final bool inTrialWindow = - MatrixState.pangeaController.userController.inTrialWindow(); - return Container( constraints: const BoxConstraints( maxWidth: AppConfig.toolbarMinWidth, @@ -31,31 +22,11 @@ class MessageUnsubscribedCard extends StatelessWidget { L10n.of(context).subscribedToUnlockTools, textAlign: TextAlign.center, ), - if (inTrialWindow) ...[ - const SizedBox(height: 10), - SizedBox( - width: double.infinity, - child: TextButton( - onPressed: () { - MatrixState.pangeaController.subscriptionController - .activateNewUserTrial(); - controller.updateToolbarMode(controller.toolbarMode); - }, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (Theme.of(context).colorScheme.primary).withAlpha(25), - ), - ), - child: Text(L10n.of(context).activateTrial), - ), - ), - ], const SizedBox(height: 10), SizedBox( width: double.infinity, child: TextButton( onPressed: () { - controller.widget.chatController.clearSelectedEvents(); MatrixState.pangeaController.subscriptionController .showPaywall(context); }, diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 546ab1258..34fdad126 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -137,14 +137,20 @@ class OverlayMessage extends StatelessWidget { event.numberEmotes > 0 && event.numberEmotes <= 3); + final isSubscribed = + MatrixState.pangeaController.subscriptionController.isSubscribed; + final showTranslation = overlayController.showTranslation && - overlayController.translation != null; + overlayController.translation != null && + isSubscribed != false; final showTranscription = - overlayController.pangeaMessageEvent?.isAudioMessage == true; + overlayController.pangeaMessageEvent?.isAudioMessage == true && + isSubscribed != false; final showSpeechTranslation = overlayController.showSpeechTranslation && - overlayController.speechTranslation != null; + overlayController.speechTranslation != null && + isSubscribed != false; final transcription = showTranscription ? Container( @@ -200,7 +206,7 @@ class OverlayMessage extends StatelessWidget { isSelected: overlayController.isTokenSelected, ), if (MatrixState.pangeaController - .languageController.showTrancription) + .languageController.showTranscription) PhoneticTranscriptionWidget( text: overlayController .transcription!.transcript.text, diff --git a/lib/pangea/toolbar/widgets/reading_assistance_content.dart b/lib/pangea/toolbar/widgets/reading_assistance_content.dart index c9b0401b3..77923c843 100644 --- a/lib/pangea/toolbar/widgets/reading_assistance_content.dart +++ b/lib/pangea/toolbar/widgets/reading_assistance_content.dart @@ -42,9 +42,7 @@ class ReadingAssistanceContentState extends State { MatrixState.pangeaController.subscriptionController.isSubscribed; if (subscribed != null && !subscribed) { - return MessageUnsubscribedCard( - controller: widget.overlayController, - ); + return const MessageUnsubscribedCard(); } if (widget.overlayController.practiceSelection?.hasHiddenWordActivity ?? diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 8226ce3ba..cc810c2d2 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -549,12 +549,15 @@ class SelectModeButtonsState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final modes = widget.overlayController.showLanguageAssistance + final isSubscribed = + MatrixState.pangeaController.subscriptionController.isSubscribed; + List modes = widget.overlayController.showLanguageAssistance ? messageEvent?.isAudioMessage == true ? audioModes : textModes : []; + if (isSubscribed == false) modes = []; return Material( type: MaterialType.transparency, child: SizedBox( diff --git a/lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart index 7ab3a8013..136c0ccee 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart @@ -1,13 +1,12 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class LemmaMeaningWidget extends StatelessWidget { final LemmaMeaningBuilderState controller; @@ -31,7 +30,16 @@ class LemmaMeaningWidget extends StatelessWidget { } if (controller.error != null) { - debugger(when: kDebugMode); + if (controller.error is UnsubscribedException) { + return ErrorIndicator( + message: L10n.of(context).subscribeToUnlockDefinitions, + style: style, + onTap: () { + MatrixState.pangeaController.subscriptionController + .showPaywall(context); + }, + ); + } return ErrorIndicator( message: L10n.of(context).errorFetchingDefinition, style: style, diff --git a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart deleted file mode 100644 index 15529cad5..000000000 --- a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart +++ /dev/null @@ -1,323 +0,0 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:go_router/go_router.dart'; -import 'package:material_symbols_icons/symbols.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/common/utils/overlay.dart'; -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; -import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart'; -import 'package:fluffychat/pangea/morphs/edit_morph_widget.dart'; -import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; -import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; -import 'package:fluffychat/pangea/morphs/morph_icon.dart'; -import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart'; -import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; -import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; -import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class MorphologicalListItem extends StatelessWidget { - final MorphFeaturesEnum morphFeature; - final PangeaToken token; - final MessageOverlayController overlayController; - // final VoidCallback editMorph; - - const MorphologicalListItem({ - required this.morphFeature, - required this.token, - required this.overlayController, - // required this.editMorph, - super.key, - }); - - bool get shouldDoActivity => - overlayController.hideWordCardContent && - overlayController.practiceSelection?.hasActiveActivityByToken( - ActivityTypeEnum.morphId, - token, - morphFeature, - ) == - true; - - bool get isSelected => - overlayController.toolbarMode == MessageMode.wordMorph && - overlayController.selectedMorph?.morph == morphFeature; - - String get morphTag => token.getMorphTag(morphFeature) ?? "X"; - - ConstructIdentifier get cId => - token.morphIdByFeature(morphFeature) ?? - ConstructIdentifier( - type: ConstructTypeEnum.morph, - category: morphFeature.name, - lemma: morphTag, - ); - - void _openDefintionPopup(BuildContext context) async { - const width = 300.0; - const height = 200.0; - - try { - if (overlayController.pangeaMessageEvent == null) { - return; - } - - OverlayUtil.showPositionedCard( - context: context, - cardToShow: MorphMeaningPopup( - token: token, - pangeaMessageEvent: overlayController.pangeaMessageEvent!, - cId: cId, - width: width, - height: height, - refresh: () { - overlayController.onMorphActivitySelect( - MorphSelection(token, morphFeature), - ); - }, - ), - transformTargetId: cId.string, - backDropToDismiss: true, - borderColor: Theme.of(context).colorScheme.primary, - closePrevOverlay: false, - addBorder: false, - maxHeight: height, - maxWidth: width, - ); - } catch (e, s) { - debugger(when: kDebugMode); - ErrorHandler.logError( - data: cId.toJson(), - e: e, - s: s, - ); - } - } - - @override - Widget build(BuildContext context) { - return CompositedTransformTarget( - link: MatrixState.pAnyState.layerLinkAndKey(cId.string).link, - child: SizedBox( - key: MatrixState.pAnyState.layerLinkAndKey(cId.string).key, - width: 40, - height: 40, - child: WordZoomActivityButton( - icon: shouldDoActivity - ? const Icon(Symbols.toys_and_games) - : MorphIcon( - morphFeature: morphFeature, - morphTag: token.getMorphTag(morphFeature), - size: const Size(24, 24), - ), - isSelected: isSelected, - onPressed: () { - overlayController - .onMorphActivitySelect(MorphSelection(token, morphFeature)); - _openDefintionPopup(context); - }, - tooltip: shouldDoActivity - ? morphFeature.getDisplayCopy(context) - : getGrammarCopy( - category: morphFeature.name, - lemma: morphTag, - context: context, - ), - opacity: isSelected ? 1 : 0.7, - ), - ), - ); - } -} - -class MorphMeaningPopup extends StatefulWidget { - final PangeaToken token; - final PangeaMessageEvent pangeaMessageEvent; - final ConstructIdentifier cId; - final double width; - final double height; - final VoidCallback refresh; - - const MorphMeaningPopup({ - super.key, - required this.token, - required this.pangeaMessageEvent, - required this.cId, - required this.width, - required this.height, - required this.refresh, - }); - - @override - State createState() => MorphMeaningPopupState(); -} - -class MorphMeaningPopupState extends State { - MorphFeaturesEnum get _morphFeature => - MorphFeaturesEnumExtension.fromString(widget.cId.category); - - String get _morphTag => widget.cId.lemma; - String? _defintion; - - bool _isEditMode = false; - - @override - void initState() { - super.initState(); - _fetchDefinition(); - } - - @override - void didUpdateWidget(covariant MorphMeaningPopup oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.cId != widget.cId) { - _fetchDefinition(); - } - } - - Future _fetchDefinition() async { - try { - final response = await MorphInfoRepo.get( - feature: _morphFeature, - tag: _morphTag, - ); - - if (mounted) { - setState( - () => _defintion = response ?? L10n.of(context).meaningNotFound, - ); - } - } catch (e, s) { - debugger(when: kDebugMode); - ErrorHandler.logError( - data: widget.cId.toJson(), - e: e, - s: s, - ); - } - } - - void _setEditMode(bool editing) { - setState(() => _isEditMode = editing); - } - - @override - Widget build(BuildContext context) { - return Material( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - child: Stack( - children: [ - Container( - padding: const EdgeInsets.all(8), - height: widget.height, - width: widget.width, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - boxShadow: [ - BoxShadow( - color: Theme.of(context).colorScheme.onSurface.withAlpha(50), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - alignment: Alignment.center, - child: _isEditMode - ? EditMorphWidget( - token: widget.token, - pangeaMessageEvent: widget.pangeaMessageEvent, - morphFeature: _morphFeature, - onClose: () { - _setEditMode(false); - _fetchDefinition(); - widget.refresh(); - }, - ) - : SingleChildScrollView( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 16.0, - children: [ - SizedBox( - width: 24.0, - height: 24.0, - child: MorphIcon( - morphFeature: _morphFeature, - morphTag: _morphTag, - ), - ), - Flexible( - child: Text( - textAlign: TextAlign.center, - getGrammarCopy( - category: _morphFeature.name, - lemma: _morphTag, - context: context, - ) ?? - _morphTag, - style: Theme.of(context).textTheme.titleMedium, - ), - ), - if (MatrixState.pangeaController.getAnalytics - .constructListModel - .getConstructUses(widget.cId) != - null) - ConstructXpWidget( - id: widget.cId, - onTap: () => context.go( - "/rooms/analytics/${ConstructTypeEnum.morph.string}/${widget.cId.string}", - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: _defintion != null - ? Text( - _defintion!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ) - : const LinearProgressIndicator(), - ), - ], - ), - ), - ), - if (!_isEditMode) - Positioned( - top: 12.0, - right: 12.0, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - child: Icon( - Icons.edit_outlined, - size: 20.0, - color: Theme.of(context).disabledColor, - ), - onTap: () { - _setEditMode(true); - }, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index 1a7ac9df4..94a8166ad 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -190,7 +190,7 @@ class WordZoomWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (MatrixState.pangeaController.languageController - .showTrancription) + .showTranscription) PhoneticTranscriptionWidget( text: token.text.content, textLanguage: PLanguageStore.byLangCode(