diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index f3f9749a1..b57e4bdd6 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -50,10 +50,7 @@ class _SpaceViewState extends State { final String _chatCountsKey = 'chatCounts'; Map get chatCounts => Map.from( - widget.controller.pangeaController.pStoreService.read( - _chatCountsKey, - local: true, - ) ?? + widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ?? {}, ); // Pangea# @@ -550,7 +547,6 @@ class _SpaceViewState extends State { await widget.controller.pangeaController.pStoreService.save( _chatCountsKey, updatedChatCounts, - local: true, ); } diff --git a/lib/pangea/choreographer/controllers/alternative_translator.dart b/lib/pangea/choreographer/controllers/alternative_translator.dart index 4221a3479..ccba733e2 100644 --- a/lib/pangea/choreographer/controllers/alternative_translator.dart +++ b/lib/pangea/choreographer/controllers/alternative_translator.dart @@ -1,15 +1,14 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; +import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:http/http.dart' as http; -import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; -import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import '../../repo/similarity_repo.dart'; class AlternativeTranslator { @@ -90,9 +89,19 @@ class AlternativeTranslator { final String? goldRouteTranslation = choreographer.itController.goldRouteTracker.fullTranslation; + final accessToken = await choreographer.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + m: "accessToken null in setTranslationFeedback", + s: StackTrace.current, + ); + translationFeedbackKey = FeedbackKey.loadingPleaseWait; + return; + } + final FullTextTranslationResponseModel results = await FullTextTranslationRepo.translate( - accessToken: await choreographer.accessToken, + accessToken: accessToken, request: FullTextTranslationRequestModel( text: choreographer.itController.sourceText!, tgtLang: choreographer.l2LangCode!, @@ -118,7 +127,7 @@ class AlternativeTranslator { } similarityResponse = await SimilarityRepo.get( - accessToken: await choreographer.accessToken, + accessToken: accessToken, request: SimilarityRequestModel( benchmark: results.bestTranslation, toCompare: [userTranslation!], diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 24756f7a6..db317cd4e 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -407,7 +407,8 @@ class Choreographer { PangeaTextController get textController => _textController; - Future get accessToken => pangeaController.userController.accessToken; + Future get accessToken => + pangeaController.userController.accessToken; clear() { choreoMode = ChoreoMode.igc; @@ -514,11 +515,7 @@ class Choreographer { chatController.room, ); - bool get itAutoPlayEnabled => - pangeaController.pStoreService.read( - MatrixProfile.itAutoPlay.title, - ) ?? - false; + bool get itAutoPlayEnabled => MatrixProfile.itAutoPlay; bool get definitionsEnabled => pangeaController.permissionsController.isToolEnabled( diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index 5424310a8..840843ed5 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -72,8 +72,18 @@ class ITFeedbackCardController extends State { setState(() { isTranslating = true; }); + + final String? accessToken = await controller.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + m: "Cannot translate feedback because accessToken is null", + ); + error = "Cannot translate feedback because accessToken is null"; + return; + } + FullTextTranslationRepo.translate( - accessToken: await controller.userController.accessToken, + accessToken: accessToken, request: FullTextTranslationRequestModel( text: res!.text, tgtLang: controller.languageController.userL1?.langCode ?? @@ -197,7 +207,7 @@ class TranslateButton extends StatelessWidget { return TextButton( onPressed: loading ? null : onPress, style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( AppConfig.primaryColor.withOpacity(0.1), ), ), diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart index 40dd393c5..b8c1a4f5d 100644 --- a/lib/pangea/constants/local.key.dart +++ b/lib/pangea/constants/local.key.dart @@ -1,16 +1,8 @@ class PLocalKey { static const String user = 'user'; - - static const String classes = 'classes'; - static const String cachedClassCodeToJoin = "cachedclasscodetojoin"; static const String beganWebPayment = "beganWebPayment"; - - // making this a random string so that it's harder to guess - static const String activatedTrialKey = '7C4EuKIsph'; static const String dismissedPaywall = 'dismissedPaywall'; static const String paywallBackoff = 'paywallBackoff'; - static const String autoPlayMessages = 'autoPlayMessages'; - static const String itAutoPlay = 'itAutoPlay'; static const String messagesSinceUpdate = 'messagesSinceLastUpdate'; } diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index 372e72606..08164d9f6 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -17,6 +17,12 @@ class ModelKey { static const String publicProfile = 'public'; static const String userId = 'user_id'; + // matrix profile keys + // making this a random string so that it's harder to guess + static const String activatedTrialKey = '7C4EuKIsph'; + static const String autoPlayMessages = 'autoPlayMessages'; + static const String itAutoPlay = 'itAutoPlay'; + static const String clientClassCity = "city"; static const String clientClassCountry = "country"; static const String clientClassDominantLanguage = "dominantLanguage"; diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index 64a8340ae..5fa59622d 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -48,15 +48,13 @@ class ClassController extends BaseController { Future checkForClassCodeAndSubscription(BuildContext context) async { final String? classCode = _pangeaController.pStoreService.read( PLocalKey.cachedClassCodeToJoin, - addClientIdToKey: false, - local: true, + isAccountData: false, ); if (classCode != null) { await _pangeaController.pStoreService.delete( PLocalKey.cachedClassCodeToJoin, - addClientIdToKey: false, - local: true, + isAccountData: false, ); await joinClasswithCode( context, diff --git a/lib/pangea/controllers/contextual_definition_controller.dart b/lib/pangea/controllers/contextual_definition_controller.dart index c13a428b8..ecc08a234 100644 --- a/lib/pangea/controllers/contextual_definition_controller.dart +++ b/lib/pangea/controllers/contextual_definition_controller.dart @@ -1,12 +1,11 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; -import 'package:http/http.dart'; - import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; + import '../constants/model_keys.dart'; import '../network/requests.dart'; import '../network/urls.dart'; @@ -50,9 +49,17 @@ class ContextualDefinitionController { ContextualDefinitionRequestModel request, ) async { try { + final accessToken = await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in contextual definition controller", + s: StackTrace.current, + ); + return null; + } final ContextualDefinitionResponseModel res = await _ContextualDefinitionRepo.define( - await _pangeaController.userController.accessToken, + accessToken, request, ); return res; diff --git a/lib/pangea/controllers/it_feedback_controller.dart b/lib/pangea/controllers/it_feedback_controller.dart index ffef9123f..c2cd96cad 100644 --- a/lib/pangea/controllers/it_feedback_controller.dart +++ b/lib/pangea/controllers/it_feedback_controller.dart @@ -51,8 +51,17 @@ class ITFeedbackController { ITFeedbackRequestModel request, ) async { try { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in it feedback controller", + s: StackTrace.current, + ); + return null; + } final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( - await _pangeaController.userController.accessToken, + accessToken, request, ); return res; diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index a3e07b0a3..5ba0f8879 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart' as http; import '../network/requests.dart'; @@ -125,7 +126,7 @@ class LanguageDetectionController { _cacheClearTimer?.cancel(); } - Future detectLanguage( + Future detectLanguage( String fullText, String? userL2, String? userL1, @@ -138,14 +139,23 @@ class LanguageDetectionController { return get(params); } - Future get( + Future get( LanguageDetectionRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in language detection controller", + s: StackTrace.current, + ); + return null; + } final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + accessToken, params, ); _cache[params] = _LanguageDetectionCacheItem(data: response); diff --git a/lib/pangea/controllers/local_settings.dart b/lib/pangea/controllers/local_settings.dart deleted file mode 100644 index 95b88ae14..000000000 --- a/lib/pangea/controllers/local_settings.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/models/space_model.dart'; - -class LocalSettings { - late PangeaController _pangeaController; - - LocalSettings(PangeaController pangeaController) : super() { - _pangeaController = pangeaController; - } - - bool userLanguageToolSetting(ToolSetting setting) { - final profileSetting = - _pangeaController.pStoreService.read(setting.toString()); - if (profileSetting != null) { - return profileSetting; - } - return setting == ToolSetting.immersionMode ? false : true; - } - - // bool get userEnableIT => - // _pangeaController.pStoreService.read(ToolSetting.interactiveTranslator.toString()) ?? true; - - // bool get userEnableIGC => - // _pangeaController.pStoreService.read(ToolSetting.interactiveGrammar.toString()) ?? true; - - // bool get userImmersionMode => - // _pangeaController.pStoreService.read(ToolSetting.immersionMode.toString()) ?? true; - - // bool get userTranslationsTool => - // _pangeaController.pStoreService.read(ToolSetting.translations.toString()) ?? true; - - // bool get userDefinitionsTool => - // _pangeaController.pStoreService.read(ToolSetting.definitions.toString()) ?? true; -} diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 3739b2596..f58a56698 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -42,7 +42,6 @@ class AnalyticsController extends BaseController { try { final String? str = _pangeaController.pStoreService.read( _analyticsTimeSpanKey, - local: true, ); return str != null ? TimeSpan.values.firstWhere((e) { @@ -60,7 +59,6 @@ class AnalyticsController extends BaseController { await _pangeaController.pStoreService.save( _analyticsTimeSpanKey, timeSpan.toString(), - local: true, ); setState(); } @@ -72,7 +70,6 @@ class AnalyticsController extends BaseController { try { final String? str = _pangeaController.pStoreService.read( _analyticsSpaceLangKey, - local: true, ); return str != null ? PangeaLanguage.byLangCode(str) @@ -88,7 +85,6 @@ class AnalyticsController extends BaseController { await _pangeaController.pStoreService.save( _analyticsSpaceLangKey, lang.langCode, - local: true, ); setState(); } diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart index 7b3a3e6d2..7ad3a748f 100644 --- a/lib/pangea/controllers/message_data_controller.dart +++ b/lib/pangea/controllers/message_data_controller.dart @@ -44,6 +44,14 @@ class MessageDataController extends BaseController { ) async { final accessToken = await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in _getTokens", + s: StackTrace.current, + ); + return null; + } + final TokensResponseModel igcTextData = await TokensRepo.tokenize(accessToken, req); @@ -193,9 +201,19 @@ class MessageDataController extends BaseController { ); try { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in _getPangeaRepresentation", + s: StackTrace.current, + ); + return null; + } + final FullTextTranslationResponseModel res = await FullTextTranslationRepo.translate( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, request: req, ); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index f7017ad0e..058bad359 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -121,7 +121,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, currentCache, - local: true, ); } @@ -155,7 +154,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); } @@ -167,14 +165,12 @@ class MyAnalyticsController { Logs().d('Reading messages since update from local storage'); final dynamic locallySaved = _pangeaController.pStoreService.read( PLocalKey.messagesSinceUpdate, - local: true, ); if (locallySaved == null) { Logs().d('No locally saved messages found, initializing empty list.'); _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); return []; } @@ -201,7 +197,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); return []; } diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 7fbf91fd0..7e24fa955 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/controllers/contextual_definition_controller.d import 'package:fluffychat/pangea/controllers/language_controller.dart'; import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/controllers/local_settings.dart'; import 'package:fluffychat/pangea/controllers/message_data_controller.dart'; import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/permissions_controller.dart'; @@ -47,7 +46,6 @@ class PangeaController { late AnalyticsController analytics; late MyAnalyticsController myAnalytics; late WordController wordNet; - late LocalSettings localSettings; late MessageDataController messageData; late ContextualDefinitionController definitions; late ITFeedbackController itFeedback; @@ -60,7 +58,7 @@ class PangeaController { late PracticeGenerationController practiceGenerationController; ///store Services - late PLocalStore pStoreService; + late PStore pStoreService; final pLanguageStore = PangeaLanguage(); ///Matrix Variables @@ -89,10 +87,9 @@ class PangeaController { /// Initialize controllers _addRefInObjects() { - pStoreService = PLocalStore(pangeaController: this); + pStoreService = PStore(pangeaController: this); userController = UserController(this); languageController = LanguageController(this); - localSettings = LocalSettings(this); classController = ClassController(this); permissionsController = PermissionsController(this); analytics = AnalyticsController(this); diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart index 363accf8e..3932eb7d2 100644 --- a/lib/pangea/controllers/permissions_controller.dart +++ b/lib/pangea/controllers/permissions_controller.dart @@ -31,14 +31,11 @@ class PermissionsController extends BaseController { } /// Returns false if user is null - bool isUser18() { - final dob = _pangeaController.pStoreService.read( - MatrixProfile.dateOfBirth.title, - ); - return dob != null - ? DateTime.parse(dob).isAtLeastYearsOld(AgeLimits.toAccessFeatures) - : false; - } + bool isUser18() => + MatrixProfile.dateOfBirth?.isAtLeastYearsOld( + AgeLimits.toAccessFeatures, + ) ?? + false; /// A user can private chat if /// 1) they are 18 and outside a class context or @@ -99,18 +96,32 @@ class PermissionsController extends BaseController { return classPermission == 0; } - bool userToolSetting(ToolSetting setting) => - _pangeaController.localSettings.userLanguageToolSetting(setting); + bool userToolSetting(MatrixProfileEnum setting) { + switch (setting.asToolSetting) { + case ToolSetting.interactiveTranslator: + return MatrixProfile.interactiveTranslator; + case ToolSetting.interactiveGrammar: + return MatrixProfile.interactiveGrammar; + case ToolSetting.immersionMode: + return MatrixProfile.immersionMode; + case ToolSetting.definitions: + return MatrixProfile.definitions; + case ToolSetting.autoIGC: + return MatrixProfile.autoIGC; + default: + return false; + } + } bool isToolEnabled(ToolSetting setting, Room? room) { if (room?.isSpaceAdmin ?? false) { - return userToolSetting(setting); + return userToolSetting(setting.asMatrixProfileField); } final int? classPermission = room != null ? classLanguageToolPermission(room, setting) : 1; if (classPermission == 0) return false; if (classPermission == 2) return true; - return userToolSetting(setting); + return userToolSetting(setting.asMatrixProfileField); } bool isWritingAssistanceEnabled(Room? room) { diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 67462bcef..3daeada07 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -44,7 +44,7 @@ class SpeechToTextController { _cacheClearTimer?.cancel(); } - Future get( + Future get( SpeechToTextRequestModel requestModel, ) async { final int cacheKey = requestModel.hashCode; @@ -52,8 +52,18 @@ class SpeechToTextController { if (_cache.containsKey(cacheKey)) { return _cache[cacheKey]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: 'null accessToken in speech to text controller', + s: StackTrace.current, + ); + return null; + } + final Future response = _fetchResponse( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, requestModel: requestModel, ); _cache[cacheKey] = _SpeechToTextCacheItem(data: response); diff --git a/lib/pangea/controllers/subscription_controller.dart b/lib/pangea/controllers/subscription_controller.dart index 36e965137..9e5d59b05 100644 --- a/lib/pangea/controllers/subscription_controller.dart +++ b/lib/pangea/controllers/subscription_controller.dart @@ -97,12 +97,10 @@ class SubscriptionController extends BaseController { } else { final bool? beganWebPayment = _pangeaController.pStoreService.read( PLocalKey.beganWebPayment, - local: true, ); if (beganWebPayment ?? false) { await _pangeaController.pStoreService.delete( PLocalKey.beganWebPayment, - local: true, ); if (_pangeaController.subscriptionController.isSubscribed) { subscriptionStream.add(true); @@ -142,7 +140,6 @@ class SubscriptionController extends BaseController { await _pangeaController.pStoreService.save( PLocalKey.beganWebPayment, true, - local: true, ); setState(); launchUrlString( @@ -184,18 +181,12 @@ class SubscriptionController extends BaseController { bool get _activatedNewUserTrial => _pangeaController.userController.inTrialWindow && - (_pangeaController.pStoreService.read( - MatrixProfile.activatedFreeTrial.title, - ) ?? - false); + MatrixProfile.activatedFreeTrial; void activateNewUserTrial() { - _pangeaController.pStoreService - .save( - MatrixProfile.activatedFreeTrial.title, - true, - ) - .then((_) { + MatrixProfile.saveProfileData({ + MatrixProfileEnum.activatedFreeTrial.title: true, + }).then((_) { setNewUserTrial(); trialActivationStream.add(true); }); @@ -242,7 +233,6 @@ class SubscriptionController extends BaseController { DateTime? get _lastDismissedPaywall { final lastDismissed = _pangeaController.pStoreService.read( PLocalKey.dismissedPaywall, - local: true, ); if (lastDismissed == null) return null; return DateTime.tryParse(lastDismissed); @@ -251,7 +241,6 @@ class SubscriptionController extends BaseController { int? get _paywallBackoff { final backoff = _pangeaController.pStoreService.read( PLocalKey.paywallBackoff, - local: true, ); if (backoff == null) return null; return backoff; @@ -269,20 +258,17 @@ class SubscriptionController extends BaseController { await _pangeaController.pStoreService.save( PLocalKey.dismissedPaywall, DateTime.now().toString(), - local: true, ); if (_paywallBackoff == null) { await _pangeaController.pStoreService.save( PLocalKey.paywallBackoff, 1, - local: true, ); } else { await _pangeaController.pStoreService.save( PLocalKey.paywallBackoff, _paywallBackoff! + 1, - local: true, ); } } diff --git a/lib/pangea/controllers/text_to_speech_controller.dart b/lib/pangea/controllers/text_to_speech_controller.dart index b34092618..b00fa23a4 100644 --- a/lib/pangea/controllers/text_to_speech_controller.dart +++ b/lib/pangea/controllers/text_to_speech_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart'; import '../network/requests.dart'; @@ -93,14 +94,23 @@ class TextToSpeechController { _cacheClearTimer?.cancel(); } - Future get( + Future get( TextToSpeechRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in text to speech controller", + s: StackTrace.current, + ); + return null; + } final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + accessToken, params, ); _cache[params] = _TextToSpeechCacheItem(data: response); diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index 873397969..fa72e6b58 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -5,7 +5,7 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:flutter/material.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:matrix/matrix.dart' as matrix; @@ -13,14 +13,44 @@ import '../constants/local.key.dart'; import '../models/user_model.dart'; import '../repo/user_repo.dart'; +/// Controller that manages saving and reading of user/profile information class UserController extends BaseController { late PangeaController _pangeaController; - final Completer _completer = Completer(); UserController(PangeaController pangeaController) : super() { _pangeaController = pangeaController; } - Future createPangeaUser({required String dob}) async { + /// Convenience function that returns the user ID currently stored in the client. + String? get userId => _pangeaController.matrixState.client.userID; + + /// Convenience function that returns the accessToken currently stored in the client. + String? get _matrixAccessToken => + _pangeaController.matrixState.client.accessToken; + + /// Returns the [PUserModel] object representing the current user. + /// + /// This method retrieves the user data from the local storage using the [PLocalKey.user] key. + /// If the data exists, it is converted to a [PUserModel] object using the [PUserModel.fromJson] method. + /// If the data is null, indicating that the user is not logged in (or that + /// profile fetching has not yet completed, or had an error), null is returned. + PUserModel? get userModel { + final data = _pangeaController.pStoreService.read(PLocalKey.user); + return data != null ? PUserModel.fromJson(data) : null; + } + + /// Creates a user pangea chat profile, saves the user's profile information + /// locally, and set the user's DOB in their matrix profile. + /// + /// The [dob] parameter is required and represents the date of birth of the user. + /// This method creates a new [PUserModel] using the [PUserRepo.repoCreatePangeaUser] method, + /// and saves the user model in local storage. + /// It also updates the user's matrix profile using the [updateMatrixProfile] method. + Future createProfile({required String dob}) async { + if (userId == null || _matrixAccessToken == null) { + ErrorHandler.logError( + e: "calling createProfile with userId == null or matrixAccessToken == null", + ); + } final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser( userID: userId!, fullName: fullname, @@ -28,139 +58,167 @@ class UserController extends BaseController { matrixAccessToken: _matrixAccessToken!, ); newUserModel.save(_pangeaController); - await updateMatrixProfile(dateOfBirth: dob); + await MatrixProfile.saveProfileData( + {MatrixProfileEnum.dateOfBirth.title: dob}, + waitForDataInSync: true, + ); } + /// A boolean flag indicating whether the profile data is currently being fetched. + bool _isFetching = false; + + /// A completer for the profile model of a user. + Completer _profileCompleter = Completer(); + + /// Fetches the user model. + /// + /// This method retrieves the user model asynchronously. If the profile completer is already completed, + /// it returns the future value of the completer. If the user model is currently being fetched, + /// it waits for the completion of the completer and returns the future value. Otherwise, it sets + /// the fetching flag, fetches the user model, completes the profile completer with the fetched user model, + /// and returns the future value of the completer. + /// + /// Returns the future value of the user model completer. Future fetchUserModel() async { + if (_profileCompleter.isCompleted) return _profileCompleter.future; + if (_isFetching) { + await _profileCompleter.future; + return _profileCompleter.future; + } + + _isFetching = true; + PUserModel? fetchedUserModel; + try { - if (_matrixAccessToken == null) { - throw Exception( - "calling fetchUserModel with matrixAccesstoken == null", - ); - } + fetchedUserModel = await _fetchUserModel(); + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + return null; + } - final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( - userID: userId!, - matrixAccessToken: _matrixAccessToken!, - ); - newUserModel?.save(_pangeaController); - await migrateMatrixProfile(); - _completeCompleter(); - - return newUserModel; - } catch (err) { - debugPrint( - "User model not found. Probably first signup and needs Pangea account", + _isFetching = false; + _profileCompleter.complete(fetchedUserModel); + return _profileCompleter.future; + } + + /// Fetches the user model asynchronously. + /// + /// This method fetches the user model by calling the [fetchPangeaUserInfo] method + /// from the [PUserRepo] class. It requires the [_matrixAccessToken] and [userId] + /// to be non-null. If either of them is null, an error is logged. + /// + /// The fetched [newUserModel] is then saved locally. + /// The [migrateMatrixProfile] method is called, to migrate any information that is + /// already saved in the user's pangea profile but is not yet saved in the + /// user's matrix profile. Finally, the [newUserModel] is returned. + Future _fetchUserModel() async { + if (_matrixAccessToken == null || userId == null) { + ErrorHandler.logError( + e: "calling fetchUserModel with userId == null or matrixAccessToken == null", ); - rethrow; + return null; } + final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( + userID: userId!, + matrixAccessToken: _matrixAccessToken!, + ); + newUserModel?.save(_pangeaController); + await migrateMatrixProfile(); + return newUserModel; } - dynamic migratedProfileInfo(MatrixProfile key) { - final dynamic localValue = _pangeaController.pStoreService.read( - key.title, - local: true, - ); - final dynamic matrixValue = _pangeaController.pStoreService.read( - key.title, - ); - return localValue != null && matrixValue != localValue ? localValue : null; + /// Reinitializes the user's profile + /// + /// This method sets up the necessary variables and fetches the user model. + /// It completes the [_profileCompleter] with the fetched user model. + /// This method should be called whenever the user's login status changes + Future reinitialize() async { + _profileCompleter = Completer(); + _isFetching = false; + await fetchUserModel(); } + /// Migrates the user's profile from Pangea to Matrix. + /// + /// This method retrieves the user's profile / local settings information from Pangea and checks for corresponding information stored in Matrix. + /// If any of the profile fields in Pangea have information, but the corresponding fields in Matrix are null, the values are updated in Matrix. + /// The profile fields that are checked for migration include date of birth, creation date, target language, source language, country, and public profile. + /// Additionally, several profile settings related to auto play, trial activation, interactive features, and instructional messages are also checked for migration. + /// + /// This method calls the [updateMatrixProfile] method to update the user's profile in Matrix with the migrated values. + /// + /// Note: This method assumes that the [userModel] and [_pangeaController] instances are properly initialized before calling this method. Future migrateMatrixProfile() async { + // This function relies on the client's account data being loaded. + // The account data is loaded during + // the first sync, so wait for that to complete. + final client = _pangeaController.matrixState.client; + if (client.prevBatch == null) { + await client.onSync.stream.first; + } + + final Map profileUpdates = {}; final Profile? pangeaProfile = userModel?.profile; - final String? pangeaDob = pangeaProfile?.dateOfBirth; - final String? matrixDob = _pangeaController.pStoreService.read( - ModelKey.userDateOfBirth, - ); - final String? dob = - pangeaDob != null && matrixDob != pangeaDob ? pangeaDob : null; + for (final field in MatrixProfile.pangeaProfileFields) { + final dynamic matrixValue = MatrixProfile.getProfileData(field); + dynamic pangeaValue; + switch (field) { + case MatrixProfileEnum.dateOfBirth: + pangeaValue = pangeaProfile?.dateOfBirth; + break; + case MatrixProfileEnum.createdAt: + pangeaValue = pangeaProfile?.createdAt; + break; + case MatrixProfileEnum.targetLanguage: + pangeaValue = pangeaProfile?.targetLanguage; + break; + case MatrixProfileEnum.sourceLanguage: + pangeaValue = pangeaProfile?.sourceLanguage; + break; + case MatrixProfileEnum.country: + pangeaValue = pangeaProfile?.country; + break; + case MatrixProfileEnum.publicProfile: + pangeaValue = pangeaProfile?.publicProfile; + break; + default: + break; + } + if (pangeaValue != null && matrixValue == null) { + profileUpdates[field.title] = pangeaValue; + } + } - final pangeaCreatedAt = pangeaProfile?.createdAt; - final matrixCreatedAt = _pangeaController.pStoreService.read( - MatrixProfile.createdAt.title, - ); - final String? createdAt = - pangeaCreatedAt != null && matrixCreatedAt != pangeaCreatedAt - ? pangeaCreatedAt - : null; - - final String? pangeaTargetLanguage = pangeaProfile?.targetLanguage; - final String? matrixTargetLanguage = _pangeaController.pStoreService.read( - MatrixProfile.targetLanguage.title, - ); - final String? targetLanguage = pangeaTargetLanguage != null && - matrixTargetLanguage != pangeaTargetLanguage - ? pangeaTargetLanguage - : null; - - final String? pangeaSourceLanguage = pangeaProfile?.sourceLanguage; - final String? matrixSourceLanguage = _pangeaController.pStoreService.read( - MatrixProfile.sourceLanguage.title, - ); - final String? sourceLanguage = pangeaSourceLanguage != null && - matrixSourceLanguage != pangeaSourceLanguage - ? pangeaSourceLanguage - : null; - - final String? pangeaCountry = pangeaProfile?.country; - final String? matrixCountry = _pangeaController.pStoreService.read( - MatrixProfile.country.title, - ); - final String? country = - pangeaCountry != null && matrixCountry != pangeaCountry - ? pangeaCountry - : null; - - final bool? pangeaPublicProfile = pangeaProfile?.publicProfile; - final bool? matrixPublicProfile = _pangeaController.pStoreService.read( - MatrixProfile.publicProfile.title, - ); - final bool? publicProfile = pangeaPublicProfile != null && - matrixPublicProfile != pangeaPublicProfile - ? pangeaPublicProfile - : null; - - final bool? autoPlay = migratedProfileInfo(MatrixProfile.autoPlayMessages); - final bool? itAutoPlay = migratedProfileInfo(MatrixProfile.itAutoPlay); - final bool? trial = migratedProfileInfo(MatrixProfile.activatedFreeTrial); - final bool? interactiveTranslator = - migratedProfileInfo(MatrixProfile.interactiveTranslator); - final bool? interactiveGrammar = - migratedProfileInfo(MatrixProfile.interactiveGrammar); - final bool? immersionMode = - migratedProfileInfo(MatrixProfile.immersionMode); - final bool? definitions = migratedProfileInfo(MatrixProfile.definitions); - // final bool? translations = migratedProfileInfo(MatrixProfile.translations); - final bool? showItInstructions = - migratedProfileInfo(MatrixProfile.showedItInstructions); - final bool? showClickMessage = - migratedProfileInfo(MatrixProfile.showedClickMessage); - final bool? showBlurMeansTranslate = - migratedProfileInfo(MatrixProfile.showedBlurMeansTranslate); - - await updateMatrixProfile( - dateOfBirth: dob, - autoPlayMessages: autoPlay, - itAutoPlay: itAutoPlay, - activatedFreeTrial: trial, - interactiveTranslator: interactiveTranslator, - interactiveGrammar: interactiveGrammar, - immersionMode: immersionMode, - definitions: definitions, - // translations: translations, - showedItInstructions: showItInstructions, - showedClickMessage: showClickMessage, - showedBlurMeansTranslate: showBlurMeansTranslate, - createdAt: createdAt, - targetLanguage: targetLanguage, - sourceLanguage: sourceLanguage, - country: country, - publicProfile: publicProfile, + for (final value in MatrixProfileEnum.values) { + if (profileUpdates.containsKey(value.title)) continue; + final dynamic localValue = + _pangeaController.pStoreService.read(value.title); + final dynamic matrixValue = MatrixProfile.getProfileData(value); + final dynamic unmigratedValue = + localValue != null && matrixValue == null ? localValue : null; + if (unmigratedValue != null) { + profileUpdates[value.title] = unmigratedValue; + } + } + + await MatrixProfile.saveProfileData( + profileUpdates, + waitForDataInSync: true, ); } + /// Updates the user's profile with the provided information. + /// + /// The [dateOfBirth] parameter is the new date of birth for the user. + /// The [targetLanguage] parameter is the new target language for the user. + /// The [sourceLanguage] parameter is the new source language for the user. + /// The [country] parameter is the new country for the user. + /// The [interests] parameter is a list of new interests for the user. + /// The [speaks] parameter is a list of new languages the user speaks. + /// The [publicProfile] parameter indicates whether the user's profile should be public or not. + /// + /// Throws an error if [userModel] or [accessToken] is null. Future updateUserProfile({ String? dateOfBirth, String? targetLanguage, @@ -170,7 +228,14 @@ class UserController extends BaseController { List? speaks, bool? publicProfile, }) async { - if (userModel == null) throw Exception("Local userModel not defined"); + final String? accessToken = await this.accessToken; + if (userModel == null || accessToken == null) { + ErrorHandler.logError( + e: "calling updateUserProfile with userModel == null or accessToken == null", + ); + return; + } + final profileJson = userModel!.profile!.toJson(); if (dateOfBirth != null) { @@ -194,230 +259,95 @@ class UserController extends BaseController { if (publicProfile != null) { profileJson[ModelKey.publicProfile] = publicProfile; } + final Profile updatedUserProfile = await PUserRepo.updateUserProfile( Profile.fromJson(profileJson), - await accessToken, + accessToken, ); PUserModel( - access: await accessToken, + access: accessToken, refresh: userModel!.refresh, profile: updatedUserProfile, ).save(_pangeaController); - await updateMatrixProfile( - dateOfBirth: dateOfBirth, - targetLanguage: targetLanguage, - sourceLanguage: sourceLanguage, - country: country, - publicProfile: publicProfile, - ); - } - - PUserModel? get userModel { - final data = _pangeaController.pStoreService.read( - PLocalKey.user, - local: true, - ); - return data != null ? PUserModel.fromJson(data) : null; - } - - Future updateMatrixProfile({ - String? dateOfBirth, - bool? autoPlayMessages, - bool? itAutoPlay, - bool? activatedFreeTrial, - bool? interactiveTranslator, - bool? interactiveGrammar, - bool? immersionMode, - bool? definitions, - // bool? translations, - bool? showedItInstructions, - bool? showedClickMessage, - bool? showedBlurMeansTranslate, - bool? showedTooltipInstructions, - String? createdAt, - String? targetLanguage, - String? sourceLanguage, - String? country, - bool? publicProfile, - }) async { - if (dateOfBirth != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.dateOfBirth.title, - dateOfBirth, - ); - } - if (autoPlayMessages != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.autoPlayMessages.title, - autoPlayMessages, - ); - } - if (itAutoPlay != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.itAutoPlay.title, - itAutoPlay, - ); - } - if (activatedFreeTrial != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.activatedFreeTrial.title, - activatedFreeTrial, - ); - } - if (interactiveTranslator != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.interactiveTranslator.title, - interactiveTranslator, - ); - } - if (interactiveGrammar != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.interactiveGrammar.title, - interactiveGrammar, - ); - } - if (immersionMode != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.immersionMode.title, - immersionMode, - ); - } - if (definitions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.definitions.title, - definitions, - ); - } - // if (translations != null) { - // await _pangeaController.pStoreService.save( - // MatrixProfile.translations.title, - // translations, - // ); - // } - if (showedItInstructions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedItInstructions.title, - showedItInstructions, - ); - } - if (showedClickMessage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedClickMessage.title, - showedClickMessage, - ); - } - if (showedBlurMeansTranslate != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedBlurMeansTranslate.title, - showedBlurMeansTranslate, - ); - } - if (showedTooltipInstructions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedTooltipInstructions.title, - showedTooltipInstructions, - ); - } - if (createdAt != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.createdAt.title, - createdAt, - ); - } - if (targetLanguage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.targetLanguage.title, - targetLanguage, - ); - } - if (sourceLanguage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.sourceLanguage.title, - sourceLanguage, - ); - } - if (country != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.country.title, - country, - ); - } - if (publicProfile != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.publicProfile.title, - publicProfile, - ); - } - } - - void _completeCompleter() { - if (!_completer.isCompleted) { - _completer.complete(null); - } - } - - Future get completer async { - if (await isPUserDataAvailable) { - _completeCompleter(); - } - return _completer; + MatrixProfile.saveProfileData({ + MatrixProfileEnum.dateOfBirth.title: dateOfBirth, + MatrixProfileEnum.targetLanguage.title: targetLanguage, + MatrixProfileEnum.sourceLanguage.title: sourceLanguage, + MatrixProfileEnum.country.title: country, + MatrixProfileEnum.publicProfile.title: publicProfile, + }); } + /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. + /// It checks if the `userModel` has a non-null `access` token and if the token is expired using the `Jwt.isExpired()` method. + /// If the `userModel` is null or the `access` token is null, it returns true indicating that a new JWT is needed. bool get needNewJWT => userModel?.access != null ? Jwt.isExpired(userModel!.access) : true; - Future get accessToken async { - await (await completer).future; - // if userModel null or access token expired then fetchUserModel + /// Retrieves the access token for the user. + /// + /// If the locally stored user model is null or the access token has + /// expired, it fetches the user model. + /// If the user model is still null after fetching, an error is logged. + /// + /// Returns the access token as a string, or null if the user model is null. + Future get accessToken async { final PUserModel? useThisOne = needNewJWT ? await fetchUserModel() : userModel; if (useThisOne == null) { - //debugger(when: kDebugMode); - throw Exception("trying to get accessToken with userModel = null"); + ErrorHandler.logError( + e: "trying to get accessToken with userModel = null", + ); } - return useThisOne.access; - } - - String? get userId { - return _pangeaController.matrixState.client.userID; + return useThisOne?.access; } - String get fullname { - final String? userID = userId; - if (userID == null) { - throw Exception('User ID not found'); + /// Returns the full name of the user. + /// If the [userId] is null, an error will be logged and null will be returned. + /// The full name is obtained by extracting the substring before the first occurrence of ":" in the [userId] + /// and then replacing all occurrences of "@" with an empty string. + String? get fullname { + if (userId == null) { + ErrorHandler.logError( + e: "calling fullname with userId == null", + ); + return null; } - return userID.substring(0, userID.indexOf(":")).replaceAll("@", ""); + return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", ""); } + /// Checks if the user data is available. + /// Returns a [Future] that completes with a [bool] value + /// indicating whether the user data is available or not. Future get isPUserDataAvailable async { try { final PUserModel? toCheck = userModel ?? (await fetchUserModel()); - return toCheck != null ? true : false; - } catch (err) { + return toCheck != null; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } + /// Checks if user data is available and the date of birth is set. + /// Returns a [Future] that completes with a [bool] value indicating + /// whether the user data is available and the date of birth is set. Future get isUserDataAvailableAndDateOfBirthSet async { try { - final client = _pangeaController.matrixState.client; - if (client.prevBatch == null) { - await client.onSync.stream.first; - } + // the function fetchUserModel() uses a completer, so it shouldn't + // re-call the endpoint if it has already been called await fetchUserModel(); - final localAccountData = _pangeaController.pStoreService.read( - ModelKey.userDateOfBirth, - ); - return localAccountData != null; - } catch (err) { + return MatrixProfile.dateOfBirth != null; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } + /// Returns a boolean value indicating whether the user is currently in the trial window. bool get inTrialWindow { final String? createdAt = userModel?.profile?.createdAt; if (createdAt == null) { @@ -428,6 +358,14 @@ class UserController extends BaseController { ); } + /// Checks if the user's languages are set. + /// Returns a [Future] that completes with a [bool] value + /// indicating whether the user's languages are set. + /// + /// A user's languages are considered set if the source and target languages + /// are not null, not empty, and not equal to the [LanguageKeys.unknownLanguage] constant. + /// + /// If an error occurs during the process, it logs the error and returns `false`. Future get areUserLanguagesSet async { try { final PUserModel? toCheck = userModel ?? (await fetchUserModel()); @@ -442,18 +380,29 @@ class UserController extends BaseController { tgtLang.isNotEmpty && srcLang != LanguageKeys.unknownLanguage && tgtLang != LanguageKeys.unknownLanguage; - } catch (err) { + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } - String? get _matrixAccessToken => - _pangeaController.matrixState.client.accessToken; - + /// Returns a boolean value indicating whether the user's profile is public. bool get isPublic => _pangeaController.userController.userModel?.profile?.publicProfile ?? false; + /// Retrieves the user's email address. + /// + /// This method fetches the user's email address by making a request to the + /// Matrix server. It uses the `_pangeaController` instance to access the + /// Matrix client and retrieve the account's third-party identifiers. It then + /// filters the identifiers to find the first one with the medium set to + /// `ThirdPartyIdentifierMedium.email`. Finally, it returns the email address + /// associated with the identifier, or `null` if no email address is found. + /// + /// Returns: + /// - The user's email address as a [String], or `null` if no email address + /// is found. Future get userEmail async { final List? identifiers = await _pangeaController.matrixState.client.getAccount3PIDs(); diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart index a67941465..43d1a66b0 100644 --- a/lib/pangea/controllers/word_net_controller.dart +++ b/lib/pangea/controllers/word_net_controller.dart @@ -1,8 +1,9 @@ import 'package:collection/collection.dart'; -import 'package:http/http.dart' as http; - import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/repo/word_repo.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:http/http.dart' as http; + import '../models/word_data_model.dart'; import 'base_controller.dart'; import 'pangea_controller.dart'; @@ -31,7 +32,7 @@ class WordController extends BaseController { ), ); - Future getWordDataGlobal({ + Future getWordDataGlobal({ required String word, required String fullText, required String? userL1, @@ -53,8 +54,18 @@ class WordController extends BaseController { if (local != null) return local; + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in word controller", + s: StackTrace.current, + ); + return null; + } + final WordData remote = await WordRepo.getWordNetData( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, fullText: fullText, word: word, userL1: userL1, diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 334c0fa78..06f34cf1f 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -88,7 +88,7 @@ class PangeaMessageEvent { return _latestEdit; } - Future getMatrixAudioFile( + Future getMatrixAudioFile( String langCode, BuildContext context, ) async { @@ -102,11 +102,15 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse response = + final TextToSpeechResponse? response = await MatrixState.pangeaController.textToSpeech.get( params, ); + if (response == null) { + return null; + } + final audioBytes = base64.decode(response.audioContent); final eventIdParam = _event.eventId; final fileName = @@ -177,10 +181,13 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse response = + final TextToSpeechResponse? response = await MatrixState.pangeaController.textToSpeech.get( params, ); + if (response == null) { + return null; + } final audioBytes = base64.decode(response.audioContent); @@ -323,7 +330,7 @@ class PangeaMessageEvent { debugPrint("mimeType ${matrixFile.mimeType}"); debugPrint("encoding ${mimeTypeToAudioEncoding(matrixFile.mimeType)}"); - final SpeechToTextModel response = + final SpeechToTextModel? response = await MatrixState.pangeaController.speechToText.get( SpeechToTextRequestModel( audioContent: matrixFile.bytes, @@ -339,6 +346,10 @@ class PangeaMessageEvent { ), ); + if (response == null) { + return null; + } + _representations?.add( RepresentationEvent( timeline: timeline, diff --git a/lib/pangea/models/space_model.dart b/lib/pangea/models/space_model.dart index 946da6dc7..fae097ee0 100644 --- a/lib/pangea/models/space_model.dart +++ b/lib/pangea/models/space_model.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -309,4 +310,19 @@ extension SettingCopy on ToolSetting { return L10n.of(context)!.autoIGCToolDescription; } } + + MatrixProfileEnum get asMatrixProfileField { + switch (this) { + case ToolSetting.interactiveTranslator: + return MatrixProfileEnum.interactiveTranslator; + case ToolSetting.interactiveGrammar: + return MatrixProfileEnum.interactiveGrammar; + case ToolSetting.immersionMode: + return MatrixProfileEnum.immersionMode; + case ToolSetting.definitions: + return MatrixProfileEnum.definitions; + case ToolSetting.autoIGC: + return MatrixProfileEnum.autoIGC; + } + } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 3e3a508cd..af7de1ee5 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -1,22 +1,18 @@ -import 'dart:convert'; - import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; import '../constants/language_constants.dart'; import 'language_model.dart'; -PUserModel pUserModelFromJson(String str) => - PUserModel.fromJson(json.decode(str)); - -String pUserModelToJson(PUserModel data) => json.encode(data.toJson()); - class PUserModel { String access; String refresh; @@ -46,12 +42,12 @@ class PUserModel { await pangeaController.pStoreService.save( PLocalKey.user, toJson(), - local: true, ); } } -enum MatrixProfile { +/// A list of all the fields in the user profile saved to matrix +enum MatrixProfileEnum { dateOfBirth, autoPlayMessages, itAutoPlay, @@ -60,7 +56,6 @@ enum MatrixProfile { interactiveGrammar, immersionMode, definitions, - // translations, showedItInstructions, showedClickMessage, showedBlurMeansTranslate, @@ -73,49 +68,182 @@ enum MatrixProfile { autoIGC, } -extension MatrixProfileExtension on MatrixProfile { +extension MatrixProfileEnumExtension on MatrixProfileEnum { String get title { switch (this) { - case MatrixProfile.dateOfBirth: + case MatrixProfileEnum.dateOfBirth: return ModelKey.userDateOfBirth; - case MatrixProfile.autoPlayMessages: - return PLocalKey.autoPlayMessages; - case MatrixProfile.itAutoPlay: - return PLocalKey.itAutoPlay; - case MatrixProfile.activatedFreeTrial: - return PLocalKey.activatedTrialKey; - case MatrixProfile.interactiveTranslator: + case MatrixProfileEnum.autoPlayMessages: + return ModelKey.autoPlayMessages; + case MatrixProfileEnum.itAutoPlay: + return ModelKey.itAutoPlay; + case MatrixProfileEnum.activatedFreeTrial: + return ModelKey.activatedTrialKey; + case MatrixProfileEnum.interactiveTranslator: return ToolSetting.interactiveTranslator.toString(); - case MatrixProfile.interactiveGrammar: + case MatrixProfileEnum.interactiveGrammar: return ToolSetting.interactiveGrammar.toString(); - case MatrixProfile.immersionMode: + case MatrixProfileEnum.immersionMode: return ToolSetting.immersionMode.toString(); - case MatrixProfile.definitions: + case MatrixProfileEnum.definitions: return ToolSetting.definitions.toString(); - // case MatrixProfile.translations: - // return ToolSetting.translations.toString(); - case MatrixProfile.autoIGC: + case MatrixProfileEnum.autoIGC: return ToolSetting.autoIGC.toString(); - case MatrixProfile.showedItInstructions: + case MatrixProfileEnum.showedItInstructions: return InstructionsEnum.itInstructions.toString(); - case MatrixProfile.showedClickMessage: + case MatrixProfileEnum.showedClickMessage: return InstructionsEnum.clickMessage.toString(); - case MatrixProfile.showedBlurMeansTranslate: + case MatrixProfileEnum.showedBlurMeansTranslate: return InstructionsEnum.blurMeansTranslate.toString(); - case MatrixProfile.showedTooltipInstructions: + case MatrixProfileEnum.showedTooltipInstructions: return InstructionsEnum.tooltipInstructions.toString(); - case MatrixProfile.createdAt: + case MatrixProfileEnum.createdAt: return ModelKey.userCreatedAt; - case MatrixProfile.targetLanguage: + case MatrixProfileEnum.targetLanguage: return ModelKey.l2LanguageKey; - case MatrixProfile.sourceLanguage: + case MatrixProfileEnum.sourceLanguage: return ModelKey.l1LanguageKey; - case MatrixProfile.country: + case MatrixProfileEnum.country: return ModelKey.userCountry; - case MatrixProfile.publicProfile: + case MatrixProfileEnum.publicProfile: return ModelKey.publicProfile; } } + + ToolSetting? get asToolSetting { + switch (this) { + case MatrixProfileEnum.interactiveTranslator: + return ToolSetting.interactiveTranslator; + case MatrixProfileEnum.interactiveGrammar: + return ToolSetting.interactiveGrammar; + case MatrixProfileEnum.immersionMode: + return ToolSetting.immersionMode; + case MatrixProfileEnum.definitions: + return ToolSetting.definitions; + case MatrixProfileEnum.autoIGC: + return ToolSetting.autoIGC; + default: + return null; + } + } +} + +/// A wrapper around the matrix account data for the user profile. +/// Enables easy access to the profile data and saving new data. +/// The matrix profile doesn't function exactly the same as a 'model', +/// since all the data here is already stored in the client as account +/// data, and duplicating that data could lead to some inconsistenies. +/// So this class is more of a helper class to make it easier to +/// access and save the data. +class MatrixProfile { + /// Returns the profile of the user. + /// + /// The profile is retrieved from the `MatrixState.pangeaController.matrixState.client.accountData` + /// using the key `ModelKey.userProfile`. It returns a `Map` object + /// representing the user's profile information. + static Map? get profile => MatrixState.pangeaController + .matrixState.client.accountData[ModelKey.userProfile]?.content; + + static dynamic getProfileData(MatrixProfileEnum key) => profile?[key.title]; + + /// Saves the profile data by updating the current user's profile with the provided updates. + /// + /// The [updates] parameter is a map containing the key-value pairs of the profile fields to be updated. + /// Only non-null values in the [updates] map will be applied to the current profile. + /// + /// If the updated profile is equal to the current profile, no changes will be made. + /// + /// If [waitForDataInSync] is true, the function will wait for the updated data in a sync update + /// If this is set to false, after this function completes there may be a gap where the + /// data has been sent but is not in the client's account data, as the sync update has not yet been received. + static Future saveProfileData( + Map updates, { + waitForDataInSync = false, + }) async { + final currentProfile = toJson(); + for (final entry in updates.entries) { + if (entry.value == null) continue; + currentProfile[entry.key] = entry.value; + } + if (mapEquals(MatrixProfile.toJson(), currentProfile)) return; + + final PangeaController pangeaController = MatrixState.pangeaController; + final Client client = pangeaController.matrixState.client; + + final List profileKeys = + MatrixProfileEnum.values.map((e) => e.title).toList(); + + Future? waitForUpdate; + if (waitForDataInSync) { + waitForUpdate = client.onSync.stream.firstWhere( + (sync) => + sync.accountData != null && + sync.accountData!.any( + (event) => event.content.keys.any((k) => profileKeys.contains(k)), + ), + ); + } + await client.setAccountData( + client.userID!, + ModelKey.userProfile, + currentProfile, + ); + if (waitForDataInSync) await waitForUpdate; + } + + /// Converts the Matrix Profile to a JSON representation. + static Map toJson() { + final Map json = {}; + for (final value in MatrixProfileEnum.values) { + json[value.title] = getProfileData(value); + } + return json; + } + + // below are some convenience methods for accessing the profile data + // getProfileData could be used directly, but these methods reduce the + // need for repeating the same code (like parsing DateTimes or + // assigning default values to null booleans) when accessing specific values. + + static DateTime? get dateOfBirth { + final dob = getProfileData(MatrixProfileEnum.dateOfBirth); + return dob != null ? DateTime.parse(dob) : null; + } + + static bool get autoPlayMessages => + getProfileData(MatrixProfileEnum.autoPlayMessages) ?? false; + static bool get itAutoPlay => + getProfileData(MatrixProfileEnum.itAutoPlay) ?? false; + static bool get activatedFreeTrial => + getProfileData(MatrixProfileEnum.activatedFreeTrial) ?? false; + static bool get interactiveTranslator => + getProfileData(MatrixProfileEnum.interactiveTranslator) ?? true; + static bool get interactiveGrammar => + getProfileData(MatrixProfileEnum.interactiveGrammar) ?? true; + static bool get immersionMode => + getProfileData(MatrixProfileEnum.immersionMode) ?? false; + static bool get definitions => + getProfileData(MatrixProfileEnum.definitions) ?? true; + static bool get autoIGC => getProfileData(MatrixProfileEnum.autoIGC) ?? false; + + /// A list of all the fields in MatrixProfileEnum that correspond to tool settings + static List get toolSettings => [ + MatrixProfileEnum.interactiveTranslator, + MatrixProfileEnum.interactiveGrammar, + MatrixProfileEnum.immersionMode, + MatrixProfileEnum.definitions, + MatrixProfileEnum.autoIGC, + ]; + + /// A list of all the fields in MatrixProfileEnum that correspond to pangea profile values + static List pangeaProfileFields = [ + MatrixProfileEnum.dateOfBirth, + MatrixProfileEnum.createdAt, + MatrixProfileEnum.targetLanguage, + MatrixProfileEnum.sourceLanguage, + MatrixProfileEnum.country, + MatrixProfileEnum.publicProfile, + ]; } class Profile { diff --git a/lib/pangea/pages/find_partner/find_partner.dart b/lib/pangea/pages/find_partner/find_partner.dart index f2bd7e8c1..44274a608 100644 --- a/lib/pangea/pages/find_partner/find_partner.dart +++ b/lib/pangea/pages/find_partner/find_partner.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/user_model.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/material.dart'; import '../../../widgets/matrix.dart'; @@ -91,9 +92,19 @@ class FindPartnerController extends State { if (loading || nextUrl == null) return; setState(() => loading = true); + final String? accessToken = + await pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in find partner controller", + s: StackTrace.current, + ); + return; + } + final UserProfileSearchResponse response = await PUserRepo.searchUserProfiles( - accessToken: await pangeaController.userController.accessToken, + accessToken: accessToken, targetLanguage: targetLanguageSearch.langCode, sourceLanguage: sourceLanguageSearch.langCode, country: countrySearch, diff --git a/lib/pangea/pages/p_user_age/p_user_age.dart b/lib/pangea/pages/p_user_age/p_user_age.dart index 54e98ab94..adb060e38 100644 --- a/lib/pangea/pages/p_user_age/p_user_age.dart +++ b/lib/pangea/pages/p_user_age/p_user_age.dart @@ -88,7 +88,7 @@ class PUserAgeController extends State { final String date = DateFormat('yyyy-MM-dd').format(selectedDate!); if (pangeaController.userController.userModel?.access == null) { - await pangeaController.userController.createPangeaUser(dob: date); + await pangeaController.userController.createProfile(dob: date); } else { await pangeaController.userController.updateUserProfile( dateOfBirth: date, diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 191bd76db..8198e70dc 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,5 +1,5 @@ -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; @@ -52,34 +52,30 @@ class SettingsLearningView extends StatelessWidget { ListTile( subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), ), - for (final setting in ToolSetting.values) - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.localSettings - .userLanguageToolSetting(setting), - title: setting.toolName(context), - subtitle: setting.toolDescription(context), - pStoreKey: setting.toString(), - local: false, - ), - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.pStoreService.read( - PLocalKey.itAutoPlay, - ) ?? - false, - title: L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, + for (final setting in MatrixProfile.toolSettings) + setting.asToolSetting != null + ? ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller + .pangeaController.permissionsController + .userToolSetting(setting), + title: setting.asToolSetting!.toolName(context), + subtitle: + setting.asToolSetting!.toolDescription(context), + profileKey: setting.asToolSetting!.asMatrixProfileField, + ) + : const SizedBox(), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: MatrixProfile.itAutoPlay, + title: + L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc, - pStoreKey: PLocalKey.itAutoPlay, - local: false, + profileKey: MatrixProfileEnum.itAutoPlay, ), - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.pStoreService.read( - PLocalKey.autoPlayMessages, - ) ?? - false, + ProfileSettingsSwitchListTile.adaptive( + defaultValue: MatrixProfile.autoPlayMessages, title: L10n.of(context)!.autoPlayTitle, subtitle: L10n.of(context)!.autoPlayDesc, - pStoreKey: PLocalKey.autoPlayMessages, - local: false, + profileKey: MatrixProfileEnum.autoPlayMessages, ), ], ), diff --git a/lib/pangea/utils/p_store.dart b/lib/pangea/utils/p_store.dart index f92e41176..0dfe0d5cd 100644 --- a/lib/pangea/utils/p_store.dart +++ b/lib/pangea/utils/p_store.dart @@ -1,133 +1,71 @@ import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -class PLocalStore { +/// Utility to save and read data both in the matrix profile (this is the default +/// behavior) and in the local storage (local needs to be specificied). An +/// instance of this class is created in the PangeaController. +class PStore { final GetStorage _box = GetStorage(); final PangeaController pangeaController; - PLocalStore({required this.pangeaController}); + PStore({required this.pangeaController}); - /// save data in local + /// Saves the provided [data] with the specified [key] in the local storage. + /// + /// By default, the [data] is considered as account data, but you can set + /// [isAccountData] to false if it's not account-related data. + /// + /// Example usage: + /// ```dart + /// await save('user', {'name': 'John Doe', 'age': 25}); + /// ``` Future save( String key, dynamic data, { - bool addClientIdToKey = true, - bool local = false, + bool isAccountData = true, }) async { - local - ? await saveLocal( - key, - data, - addClientIdToKey: addClientIdToKey, - ) - : await saveProfile(key, data); + await _box.write(_key(key, isAccountData: isAccountData), data); } - /// fetch data from local - dynamic read( - String key, { - bool addClientIdToKey = true, - local = false, - }) { - return local - ? readLocal( - key, - addClientIdToKey: addClientIdToKey, - ) - : readProfile(key); - } - - /// delete data from local - Future delete( - String key, { - bool addClientIdToKey = true, - local = false, - }) async { - return local - ? deleteLocal( - key, - addClientIdToKey: addClientIdToKey, - ) - : deleteProfile(key); - } - - /// save data in local - Future saveLocal( - String key, - dynamic data, { - bool addClientIdToKey = true, - }) async { - await _box.write(_key(key, addClientIdToKey: addClientIdToKey), data); - } - - Future saveProfile( - String key, - dynamic data, - ) async { - final waitForAccountSync = - pangeaController.matrixState.client.onSync.stream.firstWhere( - (sync) => - sync.accountData != null && - sync.accountData!.any( - (event) => event.content.keys.any( - (k) => k == key, - ), - ), - ); - await pangeaController.matrixState.client.setAccountData( - pangeaController.matrixState.client.userID!, - key, - {key: data}, - ); - await waitForAccountSync; - await pangeaController.matrixState.client.onSyncStatus.stream.firstWhere( - (syncStatus) => syncStatus.status == SyncStatus.finished, - ); - } - - /// fetch data from local - dynamic readLocal(String key, {bool addClientIdToKey = true}) { - return pangeaController.matrixState.client.userID != null - ? _box.read(_key(key, addClientIdToKey: addClientIdToKey)) - : null; - } - - dynamic readProfile(String key) { - try { - return pangeaController.matrixState.client.accountData[key]?.content[key]; - } catch (err) { - ErrorHandler.logError(e: err); - return null; - } - } - - /// delete data from local - Future deleteLocal(String key, {bool addClientIdToKey = true}) async { + /// Reads the value associated with the given [key] from the local store. + /// + /// If [isAccountData] is true, tries to find key assosiated with the logged in user. + /// Otherwise, it is read from the general store. + /// + /// Returns the value associated with the [key], or + /// null if the user ID is null or value hasn't been set. + dynamic read(String key, {bool isAccountData = true}) { return pangeaController.matrixState.client.userID != null - ? _box.remove(_key(key, addClientIdToKey: addClientIdToKey)) + ? _box.read(_key(key, isAccountData: isAccountData)) : null; } - Future deleteProfile(key) async { + /// Deletes the value associated with the given [key] from the local store. + /// + /// If [isAccountData] is true (default), will try to use key assosiated with the logged in user's ID + /// + /// Returns a [Future] that completes when the value is successfully deleted. + /// If the user is not logged in, the value will not be deleted and the [Future] will complete with null. + Future delete(String key, {bool isAccountData = true}) async { return pangeaController.matrixState.client.userID != null - ? pangeaController.matrixState.client.setAccountData( - pangeaController.matrixState.client.userID!, - key, - {key: null}, - ) + ? _box.remove(_key(key, isAccountData: isAccountData)) : null; } - _key(String key, {bool addClientIdToKey = true}) { - return addClientIdToKey + /// Returns the key for storing data in the pangea store. + /// + /// The [key] parameter represents the base key for the data. + /// The [isAccountData] parameter indicates whether the data is account-specific. + /// If [isAccountData] is true, the account-specific key is returned by appending the user ID to the base key. + /// If [isAccountData] is false, the base key is returned as is. + String _key(String key, {bool isAccountData = true}) { + return isAccountData ? pangeaController.matrixState.client.userID! + key : key; } - /// clear all local storage - clearStorage() { + /// Clears the storage by erasing all data in the box. + void clearStorage() { _box.erase(); } } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 8d2d66b7d..ad38e688d 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -3,9 +3,9 @@ import 'dart:developer'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; @@ -329,17 +329,12 @@ class MessageToolbarState extends State { }); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - final bool autoplay = MatrixState.pangeaController.pStoreService.read( - PLocalKey.autoPlayMessages, - ) ?? - false; - if (widget.pangeaMessageEvent.isAudioMessage) { updateMode(MessageMode.speechToText); return; } - autoplay + MatrixProfile.autoPlayMessages ? updateMode(MessageMode.textToSpeech) : updateMode(MessageMode.translation); }); diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index fd80df134..9ee15faf7 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -58,9 +58,17 @@ class MessageTranslationCardState extends State { } oldSelectedText = widget.selection.selectedText; - final String accessToken = + final String? accessToken = await MatrixState.pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in translateSelection", + s: StackTrace.current, + ); + return; + } + final resp = await FullTextTranslationRepo.translate( accessToken: accessToken, request: FullTextTranslationRequestModel( diff --git a/lib/pangea/widgets/class/join_with_link.dart b/lib/pangea/widgets/class/join_with_link.dart index 804c9b991..a20f7f1e1 100644 --- a/lib/pangea/widgets/class/join_with_link.dart +++ b/lib/pangea/widgets/class/join_with_link.dart @@ -43,8 +43,7 @@ class _JoinClassWithLinkState extends State { await _pangeaController.pStoreService.save( PLocalKey.cachedClassCodeToJoin, classCode, - addClientIdToKey: false, - local: true, + isAccountData: false, ); context.go("/home"); }); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 2960d229c..823de6914 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -1,9 +1,9 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/match_copy.dart'; @@ -154,21 +154,18 @@ class WordMatchContent extends StatelessWidget { .selected = true; controller.setState( - () => ( - controller.currentExpression = - controller - .widget - .scm - .choreographer - .igc - .igcTextData - !.matches[controller.widget.scm.matchIndex] - .match - .choices![index] - .isBestCorrection + () => (controller.currentExpression = controller + .widget + .scm + .choreographer + .igc + .igcTextData! + .matches[controller.widget.scm.matchIndex] + .match + .choices![index] + .isBestCorrection ? BotExpression.gold - : BotExpression.surprised - ), + : BotExpression.surprised), ); // if (controller.widget.scm.pangeaMatch.match.choices![index].type == // SpanChoiceType.distractor) { @@ -510,12 +507,11 @@ class DontShowSwitchListTileState extends State { activeColor: AppConfig.activeToggleColor, title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader), value: switchValue, - onChanged: (value) => { - widget.controller.pStoreService.save( - PLocalKey.itAutoPlay.toString(), - value, - ), - setState(() => switchValue = value), + onChanged: (value) { + MatrixProfile.saveProfileData( + {MatrixProfileEnum.itAutoPlay.title: value}, + ); + setState(() => switchValue = value); }, ); } diff --git a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart index 4c3e77599..85dbb7e36 100644 --- a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart +++ b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart @@ -1,37 +1,34 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -class PSettingsSwitchListTile extends StatefulWidget { +class ProfileSettingsSwitchListTile extends StatefulWidget { final bool defaultValue; - final String pStoreKey; + final MatrixProfileEnum profileKey; final String title; final String? subtitle; - final bool local; - const PSettingsSwitchListTile.adaptive({ + const ProfileSettingsSwitchListTile.adaptive({ super.key, this.defaultValue = false, - required this.pStoreKey, + required this.profileKey, required this.title, this.subtitle, - this.local = false, }); @override PSettingsSwitchListTileState createState() => PSettingsSwitchListTileState(); } -class PSettingsSwitchListTileState extends State { +class PSettingsSwitchListTileState + extends State { bool currentValue = true; @override void initState() { - currentValue = MatrixState.pangeaController.pStoreService.read( - widget.pStoreKey, - local: widget.local, + currentValue = MatrixProfile.getProfileData( + widget.profileKey, ) ?? widget.defaultValue; super.initState(); @@ -39,7 +36,6 @@ class PSettingsSwitchListTileState extends State { @override Widget build(BuildContext context) { - final PangeaController pangeaController = MatrixState.pangeaController; return SwitchListTile.adaptive( value: currentValue, title: Text(widget.title), @@ -47,20 +43,17 @@ class PSettingsSwitchListTileState extends State { subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null, onChanged: (bool newValue) async { try { - await pangeaController.pStoreService.save( - widget.pStoreKey, - newValue, - local: widget.local, - ); - currentValue = newValue; + MatrixProfile.saveProfileData({ + widget.profileKey.title: newValue, + }); + setState(() => currentValue = newValue); } catch (err, s) { ErrorHandler.logError( e: err, - m: "Failed to updates user setting ${widget.pStoreKey}", + m: "Failed to updates user setting ${widget.profileKey.title}", s: s, ); } - setState(() {}); }, ); } diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 5c11d17ba..0f85950ff 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -342,8 +342,11 @@ class MatrixState extends State with WidgetsBindingObserver { } else { // #Pangea if (state == LoginState.loggedIn) { - await (await pangeaController.userController.completer).future; - await pangeaController.subscriptionController.reinitialize(); + final futures = [ + pangeaController.userController.reinitialize(), + pangeaController.subscriptionController.reinitialize(), + ]; + await Future.wait(futures); } String routeDestination; if (state == LoginState.loggedIn) {