diff --git a/README.md b/README.md index 8dec17573..7c4970e00 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ +Pangea Chat Client Setup: + +* Download VSCode if you do not already have it installed +* Download flutter on your device using this guide: https://docs.flutter.dev/get-started/install +* Test to make sure that flutter is properly installed by running “flutter –version” + * You may need to add flutter to your path manually. Instructions can be found here: https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download#add-flutter-to-your-path +* Ensure that Google Chrome is installed +* Install the latest version of XCode + * After downloading XCode, ensure that the iOS simulator runtime is installed. To do this, after initially downloading XCode, a screen will open where you can select the platforms you wish to develop for. Selected iOS and download from there. +* Install the latest version of Android Studio + * After downloading Android Studio, open Android Studio and go through setup wizard +* In Android Studio, open settings -> Android SDK -> SDK tools, then click “Android SDK Command Line Tools” and click OK to run the download +* If you do not have homebrew install on your device, install homebrew by follow the instructions here: https://brew.sh/ +* Run “brew install cocoapods” to install cocoapods +* Run “flutter doctor” and for any missing components, follow the instructions from the print out to install / setup +* Clone the client repo +* Copy the .env file (and the .env.prod file, if you want to run production builds), into the root folder of the client and the assets/ folder +* Uncomment the lines in the pubspec.yaml file in the assets section with paths to .env file +* To run on iOS: + * Run “flutter precache --ios” + * Go to the iOS folder and run “pod install” +* To run on Android: + * Download Android File Transfer here: ​​https://www.android.com/filetransfer/ +* To run the app from VSCode terminal: + * On web, run `flutter run -d chrome –hot` + * On mobile device or simulator, run `flutter run –hot -d ` + ![Screenshot](https://github.com/krille-chan/fluffychat/blob/main/assets/banner_transparent.png?raw=true) [FluffyChat](https://fluffychat.im) is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of the app is to create an easy to use instant messenger which is open source and accessible for everyone. diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index c0e3aa28b..e793832fc 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2123,9 +2123,13 @@ "placeholders": {} }, "writeAMessage": "Write a message…", - "@writeAMessage": { + "writeAMessageFlag": "Write a message in {l1flag} or {l2flag}…", + "@writeAMessageFlag": { "type": "text", - "placeholders": {} + "placeholders": { + "l1flag": {}, + "l2flag": {} + } }, "yes": "Yes", "@yes": { @@ -3989,5 +3993,6 @@ "unread": {} } }, - "messageAnalytics": "Message Analytics" + "messageAnalytics": "Message Analytics", + "noPaymentInfo": "No payment info necessary!" } \ No newline at end of file diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index fa2a801c0..0af014aef 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -27,6 +27,11 @@ class ChatInputRow extends StatelessWidget { const height = 48.0; // #Pangea + final activel1 = + controller.pangeaController.languageController.activeL1Model(); + final activel2 = + controller.pangeaController.languageController.activeL2Model(); + return Column( children: [ ITBar( @@ -325,7 +330,16 @@ class ChatInputRow extends StatelessWidget { bottom: 6.0, top: 3.0, ), - hintText: L10n.of(context)!.writeAMessage, + hintText: activel1 != null && activel2 != null + ? L10n.of(context)!.writeAMessageFlag( + activel1.languageEmoji ?? + activel1.getDisplayName(context) ?? + activel1.langCode, + activel2.languageEmoji ?? + activel2.getDisplayName(context) ?? + activel2.langCode, + ) + : L10n.of(context)!.writeAMessage, hintMaxLines: 1, border: InputBorder.none, enabledBorder: InputBorder.none, diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 1af102768..896b6dc36 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -297,22 +297,21 @@ class ClientChooserButton extends StatelessWidget { // onKeysPressed: () => _previousAccount(matrix, context), // child: const SizedBox.shrink(), // ), - // Pangea# - PopupMenuButton( - onSelected: (o) => _clientSelected(o, context), - itemBuilder: _bundleMenuItems, - // #Pangea + ClipRRect( + borderRadius: BorderRadius.circular(16), child: Material( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(12), - bottomRight: Radius.circular(12), - ), - clipBehavior: Clip.hardEdge, - child: ListTile( - tileColor: Theme.of(context).scaffoldBackgroundColor, - hoverColor: Theme.of(context).colorScheme.onSurfaceVariant, - leading: const Icon(Icons.settings_outlined), - title: Text(L10n.of(context)!.mainMenu), + color: Colors.transparent, + child: + // Pangea# + PopupMenuButton( + onSelected: (o) => _clientSelected(o, context), + itemBuilder: _bundleMenuItems, + // #Pangea + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon(Icons.settings_outlined), + title: Text(L10n.of(context)!.mainMenu), + ), ), ), // child: Material( diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index 402ea3e5b..be3e32dc5 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -203,8 +203,9 @@ class NewSpaceController extends State { final newChatRoomId = await Matrix.of(context).client.createGroupChat( enableEncryption: false, preset: sdk.CreateRoomPreset.publicChat, + // Welcome chat name is '[space name acronym]: Welcome Chat' groupName: - '${nameController.text.trim()}: ${L10n.of(context)!.classWelcomeChat}', + '${nameController.text.trim().split(RegExp(r"\s+")).map((s) => s[0]).join()}: ${L10n.of(context)!.classWelcomeChat}', ); GoogleAnalytics.createChat(newChatRoomId); diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 162c75393..e5be2d194 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -75,7 +75,7 @@ class Choreographer { OverlayUtil.showPositionedCard( context: context, cardToShow: const PaywallCard(), - cardSize: const Size(325, 375), + cardSize: const Size(325, 325), transformTargetId: inputTransformTargetKey, ); return; diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart index 839e6d89d..c0390c2ba 100644 --- a/lib/pangea/constants/local.key.dart +++ b/lib/pangea/constants/local.key.dart @@ -1,6 +1,5 @@ class PLocalKey { static const String user = 'user'; - static const String matrixProfile = 'matrixProfile'; static const String classes = 'classes'; diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index a767036f7..2c684b459 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -49,12 +49,14 @@ class ClassController extends BaseController { final String? classCode = _pangeaController.pStoreService.read( PLocalKey.cachedClassCodeToJoin, addClientIdToKey: false, + local: true, ); if (classCode != null) { - _pangeaController.pStoreService.delete( + await _pangeaController.pStoreService.delete( PLocalKey.cachedClassCodeToJoin, addClientIdToKey: false, + local: true, ); await joinClasswithCode( context, diff --git a/lib/pangea/controllers/language_controller.dart b/lib/pangea/controllers/language_controller.dart index b96c82b8b..5b1a89490 100644 --- a/lib/pangea/controllers/language_controller.dart +++ b/lib/pangea/controllers/language_controller.dart @@ -1,17 +1,16 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; - import 'package:fluffychat/pangea/constants/language_keys.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/class_model.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + import '../widgets/user_settings/p_language_dialog.dart'; class LanguageController { @@ -31,16 +30,19 @@ class LanguageController { ); return; } - if (_userL1Code == null || - _userL2Code == null || - _userL1Code!.isEmpty || - _userL2Code!.isEmpty || - _userL1Code == LanguageKeys.unknownLanguage || - _userL2Code == LanguageKeys.unknownLanguage) { + if (!languagesSet) { pLanguageDialog(dialogContext, callback); } } + bool get languagesSet => + _userL1Code != null && + _userL2Code != null && + _userL1Code!.isNotEmpty && + _userL2Code!.isNotEmpty && + _userL1Code != LanguageKeys.unknownLanguage && + _userL2Code != LanguageKeys.unknownLanguage; + String? get _userL1Code { final source = _pangeaController.userController.userModel?.profile?.sourceLanguage; diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index d20e0bf36..c71af9a92 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -33,8 +33,10 @@ class AnalyticsController extends BaseController { TimeSpan get currentAnalyticsTimeSpan { try { - final String? str = - _pangeaController.pStoreService.read(_analyticsTimeSpanKey); + final String? str = _pangeaController.pStoreService.read( + _analyticsTimeSpanKey, + local: true, + ); return str != null ? TimeSpan.values.firstWhere((e) { final spanString = e.toString(); @@ -48,8 +50,11 @@ class AnalyticsController extends BaseController { } Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { - await _pangeaController.pStoreService - .save(_analyticsTimeSpanKey, timeSpan.toString()); + await _pangeaController.pStoreService.save( + _analyticsTimeSpanKey, + timeSpan.toString(), + local: true, + ); } Future> allClassAnalytics() async { diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 0e495b501..c1fe9333b 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -136,7 +136,12 @@ class PangeaController { _logOutfromPangea(); } Sentry.configureScope( - (scope) => scope.setUser(SentryUser(id: matrixState.client.userID)), + (scope) => scope.setUser( + SentryUser( + id: matrixState.client.userID, + name: matrixState.client.userID, + ), + ), ); GoogleAnalytics.analyticsUserUpdate(matrixState.client.userID); } diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart index 314b17c29..6c9a06396 100644 --- a/lib/pangea/controllers/permissions_controller.dart +++ b/lib/pangea/controllers/permissions_controller.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/class_model.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/p_extension.dart'; import 'package:matrix/matrix.dart'; @@ -31,7 +32,9 @@ class PermissionsController extends BaseController { /// Returns false if user is null bool isUser18() { - final dob = _pangeaController.userController.matrixProfile?.dateOfBirth; + final dob = _pangeaController.pStoreService.read( + MatrixProfile.dateOfBirth.title, + ); return dob != null ? DateTime.parse(dob).isAtLeastYearsOld(AgeLimits.toAccessFeatures) : false; diff --git a/lib/pangea/controllers/subscription_controller.dart b/lib/pangea/controllers/subscription_controller.dart index 3cf40f8f5..eed1cb371 100644 --- a/lib/pangea/controllers/subscription_controller.dart +++ b/lib/pangea/controllers/subscription_controller.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/base_subscription_info.dart'; import 'package:fluffychat/pangea/models/mobile_subscriptions.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/models/web_subscriptions.dart'; import 'package:fluffychat/pangea/network/requests.dart'; import 'package:fluffychat/pangea/network/urls.dart'; @@ -95,9 +96,13 @@ class SubscriptionController extends BaseController { } else { final bool? beganWebPayment = _pangeaController.pStoreService.read( PLocalKey.beganWebPayment, + local: true, ); if (beganWebPayment ?? false) { - _pangeaController.pStoreService.delete(PLocalKey.beganWebPayment); + await _pangeaController.pStoreService.delete( + PLocalKey.beganWebPayment, + local: true, + ); if (_pangeaController.subscriptionController.isSubscribed) { subscriptionStream.add(true); } @@ -133,9 +138,10 @@ class SubscriptionController extends BaseController { selectedSubscription.duration!, isPromo: isPromo, ); - _pangeaController.pStoreService.save( + await _pangeaController.pStoreService.save( PLocalKey.beganWebPayment, true, + local: true, ); setState(); launchUrlString( @@ -177,12 +183,18 @@ class SubscriptionController extends BaseController { bool get _activatedNewUserTrial => _pangeaController.userController.inTrialWindow && - (_pangeaController.pStoreService.read(PLocalKey.activatedTrialKey) ?? + (_pangeaController.pStoreService.read( + MatrixProfile.activatedFreeTrial.title, + ) ?? false); void activateNewUserTrial() { - _pangeaController.pStoreService.save(PLocalKey.activatedTrialKey, true); - setNewUserTrial(); + _pangeaController.pStoreService + .save( + MatrixProfile.activatedFreeTrial.title, + true, + ) + .then((_) => setNewUserTrial()); } void setNewUserTrial() { @@ -226,6 +238,7 @@ 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); @@ -234,6 +247,7 @@ class SubscriptionController extends BaseController { int? get _paywallBackoff { final backoff = _pangeaController.pStoreService.read( PLocalKey.paywallBackoff, + local: true, ); if (backoff == null) return null; return backoff; @@ -247,18 +261,24 @@ class SubscriptionController extends BaseController { (24 * (_paywallBackoff ?? 1))); } - void dismissPaywall() { - _pangeaController.pStoreService.save( + void dismissPaywall() async { + await _pangeaController.pStoreService.save( PLocalKey.dismissedPaywall, DateTime.now().toString(), + local: true, ); if (_paywallBackoff == null) { - _pangeaController.pStoreService.save(PLocalKey.paywallBackoff, 1); + await _pangeaController.pStoreService.save( + PLocalKey.paywallBackoff, + 1, + local: true, + ); } else { - _pangeaController.pStoreService.save( + await _pangeaController.pStoreService.save( PLocalKey.paywallBackoff, _paywallBackoff! + 1, + local: true, ); } } diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index e1b326eab..d3a17d365 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -1,14 +1,11 @@ import 'dart:async'; -import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/language_keys.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/fluffy_chat_app.dart'; +import 'package:flutter/material.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:matrix/matrix.dart' as matrix; @@ -23,6 +20,17 @@ class UserController extends BaseController { _pangeaController = pangeaController; } + Future createPangeaUser({required String dob}) async { + final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser( + userID: userId!, + fullName: fullname, + dob: dob, + matrixAccessToken: _matrixAccessToken!, + ); + newUserModel.save(_pangeaController); + await updateMatrixProfile(dateOfBirth: dob); + } + Future fetchUserModel() async { try { if (_matrixAccessToken == null) { @@ -30,48 +38,301 @@ class UserController extends BaseController { "calling fetchUserModel with matrixAccesstoken == null", ); } + final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( userID: userId!, matrixAccessToken: _matrixAccessToken!, ); - - if (newUserModel != null) { - _savePUserModel(newUserModel); - if (newUserModel.profile!.dateOfBirth != null) { - await setMatrixProfile(newUserModel.profile!.dateOfBirth!); - } - final MatrixProfile? matrixProfile = await getMatrixProfile(); - _saveMatrixProfile(matrixProfile); - } + newUserModel?.save(_pangeaController); + await migrateMatrixProfile(); _completeCompleter(); return newUserModel; } catch (err) { - log("User model not found. Probably first signup and needs Pangea account"); + debugPrint( + "User model not found. Probably first signup and needs Pangea account", + ); rethrow; } } - Future setMatrixProfile(String dob) async { - await _pangeaController.matrixState.client.setAccountData( - userId!, - PangeaEventTypes.userAge, - {ModelKey.userDateOfBirth: dob}, + dynamic migratedProfileInfo(MatrixProfile key) { + final dynamic localValue = _pangeaController.pStoreService.read( + key.title, + local: true, ); - final MatrixProfile? matrixProfile = await getMatrixProfile(); - _saveMatrixProfile(matrixProfile); + final dynamic matrixValue = _pangeaController.pStoreService.read( + key.title, + ); + return localValue != null && matrixValue != localValue ? localValue : null; } - Future getMatrixProfile() async { - try { - final Map accountData = - await _pangeaController.matrixState.client.getAccountData( - userId!, - PangeaEventTypes.userAge, + Future migrateMatrixProfile() async { + 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; + + 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? 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, + 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, + ); + } + + Future updateUserProfile({ + String? dateOfBirth, + String? targetLanguage, + String? sourceLanguage, + String? country, + List? interests, + List? speaks, + bool? publicProfile, + }) async { + if (userModel == null) throw Exception("Local userModel not defined"); + final profileJson = userModel!.profile!.toJson(); + + if (dateOfBirth != null) { + profileJson[ModelKey.userDateOfBirth] = dateOfBirth; + } + if (targetLanguage != null) { + profileJson[ModelKey.userTargetLanguage] = targetLanguage; + } + if (sourceLanguage != null) { + profileJson[ModelKey.userSourceLanguage] = sourceLanguage; + } + if (interests != null) { + profileJson[ModelKey.userInterests] = interests.toString(); + } + if (speaks != null) { + profileJson[ModelKey.userSpeaks] = speaks.toString(); + } + if (country != null) { + profileJson[ModelKey.userCountry] = country; + } + if (publicProfile != null) { + profileJson[ModelKey.publicProfile] = publicProfile; + } + final Profile updatedUserProfile = await PUserRepo.updateUserProfile( + Profile.fromJson(profileJson), + await accessToken, + ); + + PUserModel( + access: await 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? activatedFreeTrial, + bool? interactiveTranslator, + bool? interactiveGrammar, + bool? immersionMode, + bool? definitions, + bool? translations, + bool? showedItInstructions, + bool? showedClickMessage, + bool? showedBlurMeansTranslate, + 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 (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 (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, ); - return MatrixProfile.fromJson(accountData); - } catch (_) { - return null; } } @@ -116,16 +377,6 @@ class UserController extends BaseController { return userID.substring(0, userID.indexOf(":")).replaceAll("@", ""); } - PUserModel? get userModel { - final data = _pangeaController.pStoreService.read(PLocalKey.user); - return data != null ? PUserModel.fromJson(data) : null; - } - - MatrixProfile? get matrixProfile { - final data = _pangeaController.pStoreService.read(PLocalKey.matrixProfile); - return data != null ? MatrixProfile.fromJson(data) : null; - } - Future get isPUserDataAvailable async { try { final PUserModel? toCheck = userModel ?? (await fetchUserModel()); @@ -137,10 +388,15 @@ class UserController extends BaseController { Future get isUserDataAvailableAndDateOfBirthSet async { try { - if (matrixProfile == null) { - await fetchUserModel(); + final client = _pangeaController.matrixState.client; + if (client.prevBatch == null) { + await client.onSync.stream.first; } - return matrixProfile?.dateOfBirth != null ? true : false; + await fetchUserModel(); + final localAccountData = _pangeaController.pStoreService.read( + ModelKey.userDateOfBirth, + ); + return localAccountData != null; } catch (err) { return false; } @@ -175,99 +431,6 @@ class UserController extends BaseController { } } - redirectToUserInfo() { - // _pangeaController.matrix.router!.currentState!.to( - // "/home/connect/user_age", - // queryParameters: - // _pangeaController.matrix.router!.currentState!.queryParameters, - // ); - FluffyChatApp.router.go("/rooms/user_age"); - } - - _saveMatrixProfile(MatrixProfile? matrixProfile) { - if (matrixProfile != null) { - _pangeaController.pStoreService.save( - PLocalKey.matrixProfile, - matrixProfile.toJson(), - ); - setState(data: matrixProfile); - } - } - - _savePUserModel(PUserModel? pUserModel) { - if (pUserModel == null) { - ErrorHandler.logError(e: "trying to save null userModel"); - return; - } - final jsonUser = pUserModel.toJson(); - _pangeaController.pStoreService.save(PLocalKey.user, jsonUser); - - setState(data: pUserModel); - } - - Future updateUserProfile({ - String? dateOfBirth, - String? targetLanguage, - String? sourceLanguage, - String? country, - List? interests, - List? speaks, - bool? publicProfile, - }) async { - if (userModel == null) throw Exception("Local userModel not defined"); - final profileJson = userModel!.profile!.toJson(); - - if (dateOfBirth != null) { - profileJson[ModelKey.userDateOfBirth] = dateOfBirth; - } - if (targetLanguage != null) { - profileJson[ModelKey.userTargetLanguage] = targetLanguage; - } - if (sourceLanguage != null) { - profileJson[ModelKey.userSourceLanguage] = sourceLanguage; - } - if (interests != null) { - profileJson[ModelKey.userInterests] = interests.toString(); - } - if (speaks != null) { - profileJson[ModelKey.userSpeaks] = speaks.toString(); - } - if (country != null) { - profileJson[ModelKey.userCountry] = country; - } - if (publicProfile != null) { - profileJson[ModelKey.publicProfile] = publicProfile; - } - final Profile updatedUserProfile = await PUserRepo.updateUserProfile( - Profile.fromJson(profileJson), - await accessToken, - ); - - await _savePUserModel( - PUserModel( - access: await accessToken, - refresh: userModel!.refresh, - profile: updatedUserProfile, - ), - ); - - if (dateOfBirth != null) { - await setMatrixProfile(dateOfBirth); - } - } - - Future createPangeaUser({required String dob}) async { - final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser( - userID: userId!, - fullName: fullname, - dob: dob, - matrixAccessToken: _matrixAccessToken!, - ); - await _savePUserModel(newUserModel); - - await setMatrixProfile(dob); - } - String? get _matrixAccessToken => _pangeaController.matrixState.client.accessToken; diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 79e69e1fb..93d5d33ac 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -490,7 +490,11 @@ extension PangeaRoom on Room { final String migratedAnalyticsKey = "MIGRATED_ANALYTICS_KEY${id.localpart}"; - if (storageService?.read(migratedAnalyticsKey) ?? false) return; + if (storageService?.read( + migratedAnalyticsKey, + local: true, + ) ?? + false) return; if (!isPangeaClass && !isExchange) { throw Exception( @@ -522,7 +526,11 @@ extension PangeaRoom on Room { ); myAnalEvent.bulkUpdate(updateMessages); - storageService?.save(migratedAnalyticsKey, true); + await storageService?.save( + migratedAnalyticsKey, + true, + local: true, + ); } catch (err, s) { if (kDebugMode) rethrow; // debugger(when: kDebugMode); diff --git a/lib/pangea/guard/p_vguard.dart b/lib/pangea/guard/p_vguard.dart index 3e5368fad..ce3805a63 100644 --- a/lib/pangea/guard/p_vguard.dart +++ b/lib/pangea/guard/p_vguard.dart @@ -1,10 +1,9 @@ import 'dart:async'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - import 'package:go_router/go_router.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import '../controllers/pangea_controller.dart'; class PAuthGaurd { @@ -16,10 +15,10 @@ class PAuthGaurd { GoRouterState state, ) async { if (pController != null) { - final bool setDob = await pController! - .userController.isUserDataAvailableAndDateOfBirthSet; if (Matrix.of(context).client.isLogged()) { - return !setDob ? '/user_age' : '/rooms'; + final bool dobIsSet = await pController! + .userController.isUserDataAvailableAndDateOfBirthSet; + return dobIsSet ? '/rooms' : '/user_age'; } return null; } else { @@ -34,13 +33,12 @@ class PAuthGaurd { GoRouterState state, ) async { if (pController != null) { - final bool setDob = await pController! + if (!Matrix.of(context).client.isLogged()) { + return '/home'; + } + final bool dobIsSet = await pController! .userController.isUserDataAvailableAndDateOfBirthSet; - return !Matrix.of(context).client.isLogged() - ? '/home' - : !setDob - ? '/user_age' - : null; + return dobIsSet ? null : '/user_age'; } else { debugPrint("controller is null in pguard check"); return Matrix.of(context).client.isLogged() ? null : '/home'; diff --git a/lib/pangea/models/language_model.dart b/lib/pangea/models/language_model.dart index ba7709d5e..2505d1d2c 100644 --- a/lib/pangea/models/language_model.dart +++ b/lib/pangea/models/language_model.dart @@ -11,6 +11,7 @@ class LanguageModel { final String langCode; final String languageFlag; final String displayName; + final String? languageEmoji; final bool l2; final bool l1; @@ -20,6 +21,7 @@ class LanguageModel { required this.displayName, required this.l2, required this.l1, + this.languageEmoji, }); factory LanguageModel.fromJson(json) { @@ -37,6 +39,7 @@ class LanguageModel { ), l2: json["l2"] ?? code.contains("es") || code.contains("en"), l1: json["l1"] ?? !code.contains("es") && !code.contains("en"), + languageEmoji: json['language_emoji'], ); } @@ -46,6 +49,7 @@ class LanguageModel { 'language_flag': languageFlag, 'l2': l2, 'l1': l1, + 'language_emoji': languageEmoji, }; // Discuss with Jordan - adding langCode field to language objects as separate from displayName @@ -81,6 +85,7 @@ class LanguageModel { l1: false, langCode: LanguageKeys.multiLanguage, languageFlag: 'assets/colors.png', + languageEmoji: "🌎", ); // Discuss with Jordan diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index c0e5a58f0..2169c6f70 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -1,7 +1,11 @@ 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/models/class_model.dart'; +import 'package:fluffychat/pangea/utils/instructions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -37,23 +41,71 @@ class PUserModel { } return data; } + + Future save(PangeaController pangeaController) async { + await pangeaController.pStoreService.save( + PLocalKey.user, + toJson(), + local: true, + ); + } } -class MatrixProfile { - String dateOfBirth; +enum MatrixProfile { + dateOfBirth, + autoPlayMessages, + activatedFreeTrial, + interactiveTranslator, + interactiveGrammar, + immersionMode, + definitions, + translations, + showedItInstructions, + showedClickMessage, + showedBlurMeansTranslate, + createdAt, + targetLanguage, + sourceLanguage, + country, + publicProfile, +} - MatrixProfile({ - required this.dateOfBirth, - }); - - factory MatrixProfile.fromJson(Map json) => MatrixProfile( - dateOfBirth: json[ModelKey.userDateOfBirth], - ); - - Map toJson() { - final Map data = {}; - data[ModelKey.userDateOfBirth] = dateOfBirth; - return data; +extension MatrixProfileExtension on MatrixProfile { + String get title { + switch (this) { + case MatrixProfile.dateOfBirth: + return ModelKey.userDateOfBirth; + case MatrixProfile.autoPlayMessages: + return PLocalKey.autoPlayMessages; + case MatrixProfile.activatedFreeTrial: + return PLocalKey.activatedTrialKey; + case MatrixProfile.interactiveTranslator: + return ToolSetting.interactiveTranslator.toString(); + case MatrixProfile.interactiveGrammar: + return ToolSetting.interactiveGrammar.toString(); + case MatrixProfile.immersionMode: + return ToolSetting.immersionMode.toString(); + case MatrixProfile.definitions: + return ToolSetting.definitions.toString(); + case MatrixProfile.translations: + return ToolSetting.translations.toString(); + case MatrixProfile.showedItInstructions: + return InstructionsEnum.itInstructions.toString(); + case MatrixProfile.showedClickMessage: + return InstructionsEnum.clickMessage.toString(); + case MatrixProfile.showedBlurMeansTranslate: + return InstructionsEnum.blurMeansTranslate.toString(); + case MatrixProfile.createdAt: + return ModelKey.userCreatedAt; + case MatrixProfile.targetLanguage: + return ModelKey.l2LanguageKey; + case MatrixProfile.sourceLanguage: + return ModelKey.l1LanguageKey; + case MatrixProfile.country: + return ModelKey.userCountry; + case MatrixProfile.publicProfile: + return ModelKey.publicProfile; + } } } diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index f397a834a..125b2ac99 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -59,6 +59,7 @@ class SettingsLearningView extends StatelessWidget { title: setting.toolName(context), subtitle: setting.toolDescription(context), pStoreKey: setting.toString(), + local: false, ), PSettingsSwitchListTile.adaptive( defaultValue: controller.pangeaController.pStoreService.read( @@ -68,6 +69,7 @@ class SettingsLearningView extends StatelessWidget { title: L10n.of(context)!.autoPlayTitle, subtitle: L10n.of(context)!.autoPlayDesc, pStoreKey: PLocalKey.autoPlayMessages, + local: false, ), ], ), diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 0d84aa270..f1fa8b59f 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -25,8 +25,14 @@ class InstructionsController { _instructionsClosed[key] ?? false; - void updateEnableInstructions(InstructionsEnum key, bool value) => - _pangeaController.pStoreService.save(key.toString(), value); + Future updateEnableInstructions( + InstructionsEnum key, + bool value, + ) async => + await _pangeaController.pStoreService.save( + key.toString(), + value, + ); Future show( BuildContext context, @@ -149,9 +155,11 @@ class InstructionsToggleState extends State { title: Text(L10n.of(context)!.doNotShowAgain), value: pangeaController.instructions .wereInstructionsTurnedOff(widget.instructionsKey), - onChanged: ((value) { - pangeaController.instructions - .updateEnableInstructions(widget.instructionsKey, value); + onChanged: ((value) async { + await pangeaController.instructions.updateEnableInstructions( + widget.instructionsKey, + value, + ); setState(() {}); }), ); diff --git a/lib/pangea/utils/p_store.dart b/lib/pangea/utils/p_store.dart index bdc0efc6c..f92e41176 100644 --- a/lib/pangea/utils/p_store.dart +++ b/lib/pangea/utils/p_store.dart @@ -1,6 +1,7 @@ -import 'package:get_storage/get_storage.dart'; - 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 { final GetStorage _box = GetStorage(); @@ -13,24 +14,112 @@ class PLocalStore { String key, dynamic data, { bool addClientIdToKey = true, + bool local = false, + }) async { + local + ? await saveLocal( + key, + data, + addClientIdToKey: addClientIdToKey, + ) + : await saveProfile(key, 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 read(String key, {bool addClientIdToKey = true}) { + 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 delete(String key, {bool addClientIdToKey = true}) async { + Future deleteLocal(String key, {bool addClientIdToKey = true}) async { return pangeaController.matrixState.client.userID != null ? _box.remove(_key(key, addClientIdToKey: addClientIdToKey)) : null; } + Future deleteProfile(key) async { + return pangeaController.matrixState.client.userID != null + ? pangeaController.matrixState.client.setAccountData( + pangeaController.matrixState.client.userID!, + key, + {key: null}, + ) + : null; + } + _key(String key, {bool addClientIdToKey = true}) { return addClientIdToKey ? pangeaController.matrixState.client.userID! + key diff --git a/lib/pangea/widgets/class/join_with_link.dart b/lib/pangea/widgets/class/join_with_link.dart index 8011d718b..15b52bd41 100644 --- a/lib/pangea/widgets/class/join_with_link.dart +++ b/lib/pangea/widgets/class/join_with_link.dart @@ -1,12 +1,11 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; - import 'package:fluffychat/pangea/constants/url_query_parameter_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/utils/class_code.dart'; import 'package:fluffychat/widgets/layouts/empty_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; + import '../../../widgets/matrix.dart'; import '../../constants/local.key.dart'; import '../../utils/error_handler.dart'; @@ -49,6 +48,7 @@ class _JoinClassWithLinkState extends State { PLocalKey.cachedClassCodeToJoin, classCode, addClientIdToKey: false, + local: true, ); context.go("/home"); }); diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index 38e79706e..3ae2597d4 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -37,7 +37,10 @@ class PangeaRichText extends StatefulWidget { class PangeaRichTextState extends State { final PangeaController pangeaController = MatrixState.pangeaController; bool _fetchingRepresentation = false; - double get blur => _fetchingRepresentation && widget.immersionMode ? 5 : 0; + double get blur => (_fetchingRepresentation && widget.immersionMode) || + !pangeaController.languageController.languagesSet + ? 5 + : 0; String textSpan = ""; PangeaRepresentation? repEvent; diff --git a/lib/pangea/widgets/igc/pangea_text_controller.dart b/lib/pangea/widgets/igc/pangea_text_controller.dart index dad55b670..a40e2cc15 100644 --- a/lib/pangea/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/widgets/igc/pangea_text_controller.dart @@ -51,7 +51,7 @@ class PangeaTextController extends TextEditingController { OverlayUtil.showPositionedCard( context: context, cardToShow: const PaywallCard(), - cardSize: const Size(325, 375), + cardSize: const Size(325, 325), transformTargetId: choreographer.inputTransformTargetKey, ); } diff --git a/lib/pangea/widgets/igc/paywall_card.dart b/lib/pangea/widgets/igc/paywall_card.dart index a9dc1a86d..a6707222b 100644 --- a/lib/pangea/widgets/igc/paywall_card.dart +++ b/lib/pangea/widgets/igc/paywall_card.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/pangea/widgets/igc/card_header.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:shimmer/shimmer.dart'; class PaywallCard extends StatelessWidget { const PaywallCard({ @@ -31,13 +30,17 @@ class PaywallCard extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const OptionsShimmer(), - const SizedBox(height: 15.0), Text( L10n.of(context)!.subscriptionPopupDesc, style: BotStyle.text(context), textAlign: TextAlign.center, ), + if (inTrialWindow) + Text( + L10n.of(context)!.noPaymentInfo, + style: BotStyle.text(context), + textAlign: TextAlign.center, + ), const SizedBox(height: 15.0), SizedBox( width: double.infinity, @@ -88,46 +91,3 @@ class PaywallCard extends StatelessWidget { ); } } - -class OptionsShimmer extends StatelessWidget { - const OptionsShimmer({super.key}); - - @override - Widget build(BuildContext context) { - return Shimmer.fromColors( - baseColor: Theme.of(context).colorScheme.primary.withOpacity(0.5), - highlightColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), - direction: ShimmerDirection.ltr, - child: Wrap( - alignment: WrapAlignment.center, - children: List.generate( - 3, - (_) => Container( - margin: const EdgeInsets.all(2), - padding: EdgeInsets.zero, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(horizontal: 7), - ), - backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.primary.withOpacity(0.1), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - onPressed: () {}, - child: Text( - "", - style: BotStyle.text(context), - ), - ), - ), - ), - ), - ); - } -} 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 1ae2277a0..4c3e77599 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,14 +1,15 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; class PSettingsSwitchListTile extends StatefulWidget { final bool defaultValue; final String pStoreKey; final String title; final String? subtitle; + final bool local; const PSettingsSwitchListTile.adaptive({ super.key, @@ -16,6 +17,7 @@ class PSettingsSwitchListTile extends StatefulWidget { required this.pStoreKey, required this.title, this.subtitle, + this.local = false, }); @override @@ -23,17 +25,41 @@ class PSettingsSwitchListTile extends StatefulWidget { } class PSettingsSwitchListTileState extends State { + bool currentValue = true; + + @override + void initState() { + currentValue = MatrixState.pangeaController.pStoreService.read( + widget.pStoreKey, + local: widget.local, + ) ?? + widget.defaultValue; + super.initState(); + } + @override Widget build(BuildContext context) { final PangeaController pangeaController = MatrixState.pangeaController; return SwitchListTile.adaptive( - value: pangeaController.pStoreService.read(widget.pStoreKey) ?? - widget.defaultValue, + value: currentValue, title: Text(widget.title), activeColor: AppConfig.activeToggleColor, subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null, onChanged: (bool newValue) async { - pangeaController.pStoreService.save(widget.pStoreKey, newValue); + try { + await pangeaController.pStoreService.save( + widget.pStoreKey, + newValue, + local: widget.local, + ); + currentValue = newValue; + } catch (err, s) { + ErrorHandler.logError( + e: err, + m: "Failed to updates user setting ${widget.pStoreKey}", + s: s, + ); + } setState(() {}); }, ); diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 634695a18..445044a3c 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -24,6 +24,7 @@ import 'package:intl/intl.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; import 'package:url_launcher/url_launcher_string.dart'; @@ -241,6 +242,14 @@ class MatrixState extends State with WidgetsBindingObserver { } initLoadingDialog(); // #Pangea + Sentry.configureScope( + (scope) => scope.setUser( + SentryUser( + id: client.userID, + name: client.userID, + ), + ), + ); pangeaController = PangeaController(matrix: widget, matrixState: this); // PAuthGaurd.isLogged = client.isLogged(); // Pangea#