Merge branch 'main' into filter-analytics-events

This commit is contained in:
ggurdin 2024-07-15 14:07:14 -04:00 committed by GitHub
commit b4d3d398fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1436 additions and 1532 deletions

View file

@ -50,10 +50,7 @@ class _SpaceViewState extends State<SpaceView> {
final String _chatCountsKey = 'chatCounts';
Map<String, int> 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<SpaceView> {
await widget.controller.pangeaController.pStoreService.save(
_chatCountsKey,
updatedChatCounts,
local: true,
);
}

View file

@ -1,15 +1,14 @@
import 'dart:developer';
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 '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 '../../repo/similarity_repo.dart';
class AlternativeTranslator {

View file

@ -14,7 +14,6 @@ import 'package:fluffychat/pangea/models/it_step.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.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';
@ -514,11 +513,9 @@ class Choreographer {
chatController.room,
);
bool get itAutoPlayEnabled =>
pangeaController.pStoreService.read(
MatrixProfile.itAutoPlay.title,
) ??
false;
bool get itAutoPlayEnabled {
return pangeaController.userController.profile.userSettings.itAutoPlay;
}
bool get definitionsEnabled =>
pangeaController.permissionsController.isToolEnabled(

View file

@ -197,7 +197,7 @@ class TranslateButton extends StatelessWidget {
return TextButton(
onPressed: loading ? null : onPress,
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
backgroundColor: WidgetStateProperty.all<Color>(
AppConfig.primaryColor.withOpacity(0.1),
),
),

View file

@ -1,16 +1,8 @@
class PLocalKey {
static const String user = 'user';
static const String classes = 'classes';
static const String access = "access";
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';
}

View file

@ -16,6 +16,15 @@ class ModelKey {
static const String l1LanguageKey = 'source_language';
static const String publicProfile = 'public';
static const String userId = 'user_id';
static const String toolSettings = 'tool_settings';
static const String userSettings = 'user_settings';
static const String instructionsSettings = 'instructions_settings';
// 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";

View file

@ -48,15 +48,13 @@ class ClassController extends BaseController {
Future<void> 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,

View file

@ -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';

View file

@ -1,12 +1,8 @@
import 'dart:developer';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../widgets/user_settings/p_language_dialog.dart';
@ -18,15 +14,6 @@ class LanguageController {
}
//show diloag when user does not have languages selected
showDialogOnEmptyLanguage(BuildContext dialogContext, Function callback) {
if (_pangeaController.userController.userModel?.profile == null) {
debugger(when: kDebugMode);
Sentry.addBreadcrumb(
Breadcrumb(
message: 'calling showDialogOnEmptyLanguagae with empty user',
),
);
return;
}
if (!languagesSet) {
pLanguageDialog(dialogContext, callback);
}
@ -42,13 +29,13 @@ class LanguageController {
String? get _userL1Code {
final source =
_pangeaController.userController.userModel?.profile?.sourceLanguage;
_pangeaController.userController.profile.userSettings.sourceLanguage;
return source == null || source.isEmpty ? null : source;
}
String? get _userL2Code {
final target =
_pangeaController.userController.userModel?.profile?.targetLanguage;
_pangeaController.userController.profile.userSettings.targetLanguage;
return target == null || target.isEmpty ? null : target;
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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 [];
}

View file

@ -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);

View file

@ -4,7 +4,6 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/p_extension.dart';
import 'package:matrix/matrix.dart';
@ -32,12 +31,9 @@ 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;
final DateTime? dob =
_pangeaController.userController.profile.userSettings.dateOfBirth;
return dob?.isAtLeastYearsOld(AgeLimits.toAccessFeatures) ?? false;
}
/// A user can private chat if
@ -99,8 +95,26 @@ class PermissionsController extends BaseController {
return classPermission == 0;
}
bool userToolSetting(ToolSetting setting) =>
_pangeaController.localSettings.userLanguageToolSetting(setting);
bool userToolSetting(ToolSetting setting) {
switch (setting) {
case ToolSetting.interactiveTranslator:
return _pangeaController
.userController.profile.toolSettings.interactiveTranslator;
case ToolSetting.interactiveGrammar:
return _pangeaController
.userController.profile.toolSettings.interactiveGrammar;
case ToolSetting.immersionMode:
return _pangeaController
.userController.profile.toolSettings.immersionMode;
case ToolSetting.definitions:
return _pangeaController
.userController.profile.toolSettings.definitions;
case ToolSetting.autoIGC:
return _pangeaController.userController.profile.toolSettings.autoIGC;
default:
return false;
}
}
bool isToolEnabled(ToolSetting setting, Room? room) {
if (room?.isSpaceAdmin ?? false) {

View file

@ -8,7 +8,6 @@ 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';
@ -97,12 +96,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 +139,6 @@ class SubscriptionController extends BaseController {
await _pangeaController.pStoreService.save(
PLocalKey.beganWebPayment,
true,
local: true,
);
setState();
launchUrlString(
@ -182,37 +178,35 @@ class SubscriptionController extends BaseController {
}
}
bool get _activatedNewUserTrial =>
_pangeaController.userController.inTrialWindow &&
(_pangeaController.pStoreService.read(
MatrixProfile.activatedFreeTrial.title,
) ??
false);
bool get _activatedNewUserTrial {
final bool activated = _pangeaController
.userController.profile.userSettings.activatedFreeTrial;
return _pangeaController.userController.inTrialWindow && activated;
}
void activateNewUserTrial() {
_pangeaController.pStoreService
.save(
MatrixProfile.activatedFreeTrial.title,
true,
)
.then((_) {
setNewUserTrial();
trialActivationStream.add(true);
});
_pangeaController.userController.updateProfile(
(profile) {
profile.userSettings.activatedFreeTrial = true;
return profile;
},
);
setNewUserTrial();
trialActivationStream.add(true);
}
void setNewUserTrial() {
if (_pangeaController.userController.userModel?.profile == null) {
final DateTime? createdAt =
_pangeaController.userController.profile.userSettings.createdAt;
if (createdAt == null) {
ErrorHandler.logError(
m: "Null user profile in subscription settings",
m: "Null user profile createAt in subscription settings",
s: StackTrace.current,
);
return;
}
final String profileCreatedAt =
_pangeaController.userController.userModel!.profile!.createdAt;
final DateTime creationTimestamp = DateTime.parse(profileCreatedAt);
final DateTime expirationDate = creationTimestamp.add(
final DateTime expirationDate = createdAt.add(
const Duration(days: 7),
);
subscription?.setTrial(expirationDate);
@ -242,7 +236,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 +244,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 +261,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,
);
}
}

View file

@ -2,10 +2,10 @@ import 'dart:async';
import 'package:collection/collection.dart';
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/extensions/client_extension/client_extension.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:matrix/matrix.dart' as matrix;
@ -13,447 +13,247 @@ 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<void> 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);
/// 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;
/// Cached version of the user profile, so it doesn't have
/// to be read in from client's account data each time it is accessed.
Profile? _cachedProfile;
/// Listens for account updates and updates the cached profile
StreamSubscription? _profileListener;
/// Listen for updates to account data in syncs and update the cached profile
void addProfileListener() {
_profileListener ??= _pangeaController.matrixState.client.onSync.stream
.where((sync) => sync.accountData != null)
.listen((sync) {
final Profile? fromAccountData = Profile.fromAccountData();
if (fromAccountData != null) {
_cachedProfile = fromAccountData;
}
});
}
Future<PUserModel?> fetchUserModel() async {
try {
if (_matrixAccessToken == null) {
throw Exception(
"calling fetchUserModel with matrixAccesstoken == null",
);
}
/// The user's profile. Will be empty if the client's accountData hasn't
/// been loaded yet (if the first sync hasn't gone through yet)
/// or if the user hasn't yer set their date of birth.
Profile get profile {
/// if the profile is cached, return it
if (_cachedProfile != null) return _cachedProfile!;
final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo(
/// if account data is empty, return an empty profile
if (_pangeaController.matrixState.client.accountData.isEmpty) {
return Profile.emptyProfile;
}
/// try to get the account data in the up-to-date format
final Profile? fromAccountData = Profile.fromAccountData();
if (fromAccountData != null) {
_cachedProfile = fromAccountData;
return fromAccountData;
}
_cachedProfile = Profile.migrateFromAccountData();
_cachedProfile?.saveProfileData();
return _cachedProfile ?? Profile.emptyProfile;
}
/// Updates the user's profile with the given [update] function and saves it.
void updateProfile(Profile Function(Profile) update) {
final Profile updatedProfile = update(profile);
updatedProfile.saveProfileData();
}
/// Creates a new profile for the user with the given date of birth.
Future<void> createProfile({required DateTime dob}) async {
final userSettings = UserSettings(
dateOfBirth: dob,
createdAt: DateTime.now(),
);
final newProfile = Profile(userSettings: userSettings);
await newProfile.saveProfileData(waitForDataInSync: true);
}
/// A completer for the profile model of a user.
Completer<void>? _profileCompleter;
/// Initializes the user's profile. Runs a function to wait for account data to load,
/// read account data into profile, and migrate any missing info from the pangea profile.
/// Finally, it adds a listen to update the profile data when new account data comes in.
Future<void> initialize() async {
if (_profileCompleter?.isCompleted ?? false) {
return _profileCompleter!.future;
}
if (_profileCompleter != null) {
await _profileCompleter!.future;
return _profileCompleter!.future;
}
_profileCompleter = Completer<void>();
try {
await _initialize();
addProfileListener();
} catch (err, s) {
ErrorHandler.logError(e: err, s: s);
} finally {
_profileCompleter!.complete();
}
return _profileCompleter!.future;
}
/// Initializes the user's profile by waiting for account data to load, reading in account
/// data to profile, and migrating from the pangea profile if the account data is not present.
Future<void> _initialize() async {
await _pangeaController.matrixState.client.waitForAccountData();
if (profile.userSettings.dateOfBirth != null) {
return;
}
final PangeaProfileResponse? resp = await PUserRepo.fetchPangeaUserInfo(
userID: userId!,
matrixAccessToken: _matrixAccessToken!,
);
if (resp?.profile == null) {
return;
}
final userSetting = UserSettings.fromJson(resp!.profile.toJson());
final newProfile = Profile(userSettings: userSetting);
await newProfile.saveProfileData(waitForDataInSync: true);
}
/// Reinitializes the user's profile
/// This method should be called whenever the user's login status changes
Future<void> reinitialize() async {
_profileCompleter = null;
_cachedProfile = null;
await initialize();
}
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
bool needNewJWT(String token) => Jwt.isExpired(token);
/// Retrieves the access token for the user. Looks for it locally,
/// and if it's not found or expired, fetches it from the server.
Future<String> get accessToken async {
final localAccessToken =
_pangeaController.pStoreService.read(PLocalKey.access);
if (localAccessToken == null || needNewJWT(localAccessToken)) {
final PangeaProfileResponse? userModel =
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",
if (userModel?.access == null) {
throw ("Trying to get accessToken with null userModel");
}
_pangeaController.pStoreService.save(
PLocalKey.access,
userModel!.access,
);
rethrow;
return userModel.access;
}
return localAccessToken;
}
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;
}
Future<void> 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? 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,
);
}
Future<void> updateUserProfile({
String? dateOfBirth,
String? targetLanguage,
String? sourceLanguage,
String? country,
List<String>? interests,
List<String>? 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<void> 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<Completer> get completer async {
if (await isPUserDataAvailable) {
_completeCompleter();
}
return _completer;
}
bool get needNewJWT =>
userModel?.access != null ? Jwt.isExpired(userModel!.access) : true;
Future<String> get accessToken async {
await (await completer).future;
// if userModel null or access token expired then fetchUserModel
final PUserModel? useThisOne =
needNewJWT ? await fetchUserModel() : userModel;
if (useThisOne == null) {
//debugger(when: kDebugMode);
throw Exception("trying to get accessToken with userModel = null");
}
return useThisOne.access;
}
String? get userId {
return _pangeaController.matrixState.client.userID;
}
String get fullname {
final String? userID = userId;
if (userID == null) {
throw Exception('User ID not found');
}
return userID.substring(0, userID.indexOf(":")).replaceAll("@", "");
}
Future<bool> get isPUserDataAvailable async {
try {
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
return toCheck != null ? true : false;
} catch (err) {
return false;
}
/// 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("@", "");
}
/// 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<bool> get isUserDataAvailableAndDateOfBirthSet async {
try {
final client = _pangeaController.matrixState.client;
if (client.prevBatch == null) {
await client.onSync.stream.first;
}
await fetchUserModel();
final localAccountData = _pangeaController.pStoreService.read(
ModelKey.userDateOfBirth,
);
return localAccountData != null;
} catch (err) {
// the function fetchUserModel() uses a completer, so it shouldn't
// re-call the endpoint if it has already been called
await initialize();
return profile.userSettings.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;
final DateTime? createdAt = profile.userSettings.createdAt;
if (createdAt == null) {
return false;
}
return DateTime.parse(createdAt).isAfter(
return createdAt.isAfter(
DateTime.now().subtract(const Duration(days: 7)),
);
}
/// 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<bool> get areUserLanguagesSet async {
try {
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
if (toCheck?.profile == null) {
return false;
}
final String? srcLang = toCheck!.profile!.sourceLanguage;
final String? tgtLang = toCheck.profile!.targetLanguage;
final String? srcLang = profile.userSettings.sourceLanguage;
final String? tgtLang = profile.userSettings.targetLanguage;
return srcLang != null &&
tgtLang != null &&
srcLang.isNotEmpty &&
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;
bool get isPublic =>
_pangeaController.userController.userModel?.profile?.publicProfile ??
false;
/// Returns a boolean value indicating whether the user's profile is public.
bool get isPublic {
return profile.userSettings.publicProfile;
}
/// 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<String?> get userEmail async {
final List<matrix.ThirdPartyIdentifier>? identifiers =
await _pangeaController.matrixState.client.getAccount3PIDs();

View file

@ -1,8 +1,8 @@
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:http/http.dart' as http;
import '../models/word_data_model.dart';
import 'base_controller.dart';
import 'pangea_controller.dart';

View file

@ -80,4 +80,6 @@ extension PangeaClient on Client {
String? powerLevelName(int powerLevel, L10n l10n) =>
_powerLevelName(powerLevel, l10n);
Future<void> waitForAccountData() async => await _waitForAccountData();
}

View file

@ -78,4 +78,11 @@ extension GeneralInfoClientExtension on Client {
50: l10n.moderator,
100: l10n.admin,
}[powerLevel];
/// Account data comes through in the first sync, so wait for that
Future<void> _waitForAccountData() async {
if (prevBatch == null) {
await onSync.stream.first;
}
}
}

View file

@ -109,46 +109,6 @@ class PangeaRoomRules {
this.autoIGC = ClassDefaultValues.languageToolPermissions,
});
updatePermission(String key, bool value) {
switch (key) {
case 'isPublic':
isPublic = value;
break;
case 'isOpenEnrollment':
isOpenEnrollment = value;
break;
case 'oneToOneChatClass':
oneToOneChatClass = value;
break;
case 'isCreateRooms':
isCreateRooms = value;
break;
case 'isShareVideo':
isShareVideo = value;
break;
case 'isSharePhoto':
isSharePhoto = value;
break;
case 'isShareFiles':
isShareFiles = value;
break;
case 'isShareLocation':
isShareLocation = value;
break;
case 'isCreateStories':
isCreateStories = value;
break;
case 'isVoiceNotes':
isVoiceNotes = value;
break;
case 'isInviteOnlyStudents':
isInviteOnlyStudents = value;
break;
default:
throw Exception('Invalid key for setting permissions - $key');
}
}
setLanguageToolSetting(ToolSetting setting, int value) {
switch (setting) {
case ToolSetting.interactiveTranslator:

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ class UserProfileSearchResponse {
int count;
String? next;
String? previous;
List<Profile> results;
List<PangeaProfile> results;
UserProfileSearchResponse({
required this.count,
@ -19,9 +19,9 @@ class UserProfileSearchResponse {
next: json["next"],
previous: json["previous"],
results: json["results"]
.map((p) => Profile.fromJson(p))
.map((p) => PangeaProfile.fromJson(p))
.toList()
.cast<Profile>(),
.cast<PangeaProfile>(),
);
}
}

View file

@ -3,7 +3,9 @@ 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 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../../widgets/matrix.dart';
import '../../controllers/pangea_controller.dart';
@ -35,9 +37,10 @@ class FindPartnerController extends State<FindPartner> {
Timer? coolDown;
final List<Profile> _userProfilesCache = [];
final List<PangeaProfile> _userProfilesCache = [];
final scrollController = ScrollController();
String? error;
@override
void initState() {
@ -66,10 +69,21 @@ class FindPartnerController extends State<FindPartner> {
@override
Widget build(BuildContext context) {
if (error != null && error!.isNotEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(L10n.of(context)!.oopsSomethingWentWrong),
Text(L10n.of(context)!.errorPleaseRefresh),
],
),
);
}
return FindPartnerView(this);
}
List<Profile> get userProfiles => _userProfilesCache.where((p) {
List<PangeaProfile> get userProfiles => _userProfilesCache.where((p) {
return (p.targetLanguage != null &&
targetLanguageSearch.langCode == p.targetLanguage) &&
(p.sourceLanguage != null &&
@ -91,21 +105,29 @@ class FindPartnerController extends State<FindPartner> {
if (loading || nextUrl == null) return;
setState(() => loading = true);
final UserProfileSearchResponse response =
await PUserRepo.searchUserProfiles(
accessToken: await pangeaController.userController.accessToken,
targetLanguage: targetLanguageSearch.langCode,
sourceLanguage: sourceLanguageSearch.langCode,
country: countrySearch,
limit: 15,
pageNumber: nextPage.toString(),
);
UserProfileSearchResponse response;
try {
final String accessToken =
await pangeaController.userController.accessToken;
response = await PUserRepo.searchUserProfiles(
accessToken: accessToken,
targetLanguage: targetLanguageSearch.langCode,
sourceLanguage: sourceLanguageSearch.langCode,
country: countrySearch,
limit: 15,
pageNumber: nextPage.toString(),
);
} catch (err, s) {
error = err.toString();
setState(() => loading = false);
ErrorHandler.logError(e: err, s: s);
return;
}
nextUrl = response.next;
nextPage++;
final String? currentUserId =
pangeaController.userController.userModel?.profile?.pangeaUserId;
final String? currentUserId = pangeaController.matrixState.client.userID;
_userProfilesCache.addAll(
response.results.where(
(p) =>

View file

@ -2,6 +2,7 @@ import 'package:country_picker/country_picker.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/country_display.dart';
import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart';
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dropdown.dart';
@ -244,7 +245,7 @@ class LanguageSelectionRow extends StatelessWidget {
}
class UserProfileEntry extends StatelessWidget {
final Profile pangeaProfile;
final PangeaProfile pangeaProfile;
final FindPartnerController controller;
const UserProfileEntry({
@ -287,7 +288,7 @@ class UserProfileEntry extends StatelessWidget {
const SizedBox(width: 20),
RichText(
text: TextSpan(
text: pangeaProfile.flagEmoji,
text: CountryDisplayUtil.flagEmoji(pangeaProfile.country),
style: const TextStyle(fontSize: 15),
),
),

View file

@ -10,7 +10,6 @@ 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:intl/intl.dart';
import '../../utils/bot_name.dart';
import '../../utils/error_handler.dart';
@ -73,26 +72,24 @@ class PUserAgeController extends State<PUserAge> {
}
//Note: used linear progress bar (also used in fluffychat signup button) for consistency
createUserInPangea() async {
Future<void> createUserInPangea() async {
try {
setState(() {
error = dobValidator();
});
setState(() => error = dobValidator());
if (error?.isNotEmpty == true) return;
setState(() => loading = true);
setState(() {
loading = true;
});
final DateTime? dob =
pangeaController.userController.profile.userSettings.dateOfBirth;
final String date = DateFormat('yyyy-MM-dd').format(selectedDate!);
if (pangeaController.userController.userModel?.access == null) {
await pangeaController.userController.createPangeaUser(dob: date);
} else {
await pangeaController.userController.updateUserProfile(
dateOfBirth: date,
if (dob == null) {
await pangeaController.userController.createProfile(
dob: selectedDate!,
);
} else {
pangeaController.userController.updateProfile((profile) {
profile.userSettings.dateOfBirth = selectedDate!;
return profile;
});
}
FluffyChatApp.router.go('/rooms');
} catch (err, s) {

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'package:country_picker/country_picker.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.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_view.dart';
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -18,31 +20,55 @@ class SettingsLearningController extends State<SettingsLearning> {
late StreamSubscription _userSubscription;
PangeaController pangeaController = MatrixState.pangeaController;
setPublicProfile(bool b) async {
await pangeaController.userController.updateUserProfile(publicProfile: b);
setState(() {});
Future<void> changeLanguage() async {
await pLanguageDialog(context, () {});
}
@override
void initState() {
super.initState();
_userSubscription =
pangeaController.userController.stateStream.listen((event) {
setState(() {});
Future<void> setPublicProfile(bool isPublic) async {
pangeaController.userController.updateProfile((profile) {
profile.userSettings.publicProfile = isPublic;
return profile;
});
}
Future<void> changeLanguage() async {
await pLanguageDialog(context, () {});
setState(() {});
Future<void> changeCountry(Country country) async {
pangeaController.userController.updateProfile((profile) {
profile.userSettings.country = country.displayNameNoCountryCode;
return profile;
});
}
Future<void> changeCountry(Country country) async {
await pangeaController.userController.updateUserProfile(
country: country.displayNameNoCountryCode,
);
setState(() {});
void updateToolSetting(ToolSetting toolSetting, bool value) {
pangeaController.userController.updateProfile((Profile profile) {
switch (toolSetting) {
case ToolSetting.interactiveTranslator:
return profile..toolSettings.interactiveTranslator = value;
case ToolSetting.interactiveGrammar:
return profile..toolSettings.interactiveGrammar = value;
case ToolSetting.immersionMode:
return profile..toolSettings.immersionMode = value;
case ToolSetting.definitions:
return profile..toolSettings.definitions = value;
case ToolSetting.autoIGC:
return profile..toolSettings.autoIGC = value;
}
});
}
bool getToolSetting(ToolSetting toolSetting) {
final toolSettings = pangeaController.userController.profile.toolSettings;
switch (toolSetting) {
case ToolSetting.interactiveTranslator:
return toolSettings.interactiveTranslator;
case ToolSetting.interactiveGrammar:
return toolSettings.interactiveGrammar;
case ToolSetting.immersionMode:
return toolSettings.immersionMode;
case ToolSetting.definitions:
return toolSettings.definitions;
case ToolSetting.autoIGC:
return toolSettings.autoIGC;
}
}
@override

View file

@ -1,4 +1,3 @@
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
@ -18,73 +17,97 @@ class SettingsLearningView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
L10n.of(context)!.learningSettings,
),
// rebuild this page each time a sync comes through with new account data
// this prevents having to call setState each time an individual setting is changed
return StreamBuilder(
stream:
controller.pangeaController.matrixState.client.onSync.stream.where(
(update) => update.accountData != null,
),
body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
child: MaxWidthBody(
withScrolling: true,
child: Column(
children: [
LanguageTile(controller),
CountryPickerTile(controller),
const SizedBox(height: 8),
const Divider(height: 1),
const SizedBox(height: 8),
if (controller.pangeaController.permissionsController.isUser18())
SwitchListTile.adaptive(
activeColor: AppConfig.activeToggleColor,
title: Text(L10n.of(context)!.publicProfileTitle),
subtitle: Text(L10n.of(context)!.publicProfileDesc),
value: controller.pangeaController.userController.isPublic,
onChanged: (bool isPublicProfile) => showFutureLoadingDialog(
context: context,
future: () => controller.setPublicProfile(isPublicProfile),
onError: (err) =>
ErrorHandler.logError(e: err, s: StackTrace.current),
),
),
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,
subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc,
pStoreKey: PLocalKey.itAutoPlay,
local: false,
),
PSettingsSwitchListTile.adaptive(
defaultValue: controller.pangeaController.pStoreService.read(
PLocalKey.autoPlayMessages,
) ??
false,
title: L10n.of(context)!.autoPlayTitle,
subtitle: L10n.of(context)!.autoPlayDesc,
pStoreKey: PLocalKey.autoPlayMessages,
local: false,
),
],
builder: (context, snapshot) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
L10n.of(context)!.learningSettings,
),
),
),
),
body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
child: MaxWidthBody(
withScrolling: true,
child: Column(
children: [
LanguageTile(controller),
CountryPickerTile(controller),
const SizedBox(height: 8),
const Divider(height: 1),
const SizedBox(height: 8),
if (controller.pangeaController.permissionsController
.isUser18())
SwitchListTile.adaptive(
activeColor: AppConfig.activeToggleColor,
title: Text(L10n.of(context)!.publicProfileTitle),
subtitle: Text(L10n.of(context)!.publicProfileDesc),
value:
controller.pangeaController.userController.isPublic,
onChanged: (bool isPublicProfile) =>
showFutureLoadingDialog(
context: context,
future: () =>
controller.setPublicProfile(isPublicProfile),
onError: (err) => ErrorHandler.logError(
e: err,
s: StackTrace.current,
),
),
),
ListTile(
subtitle:
Text(L10n.of(context)!.toggleToolSettingsDescription),
),
for (final toolSetting in ToolSetting.values)
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller.getToolSetting(toolSetting),
title: toolSetting.toolName(context),
subtitle: toolSetting.toolDescription(context),
onChange: (bool value) => controller.updateToolSetting(
toolSetting,
value,
),
),
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller.pangeaController.userController
.profile.userSettings.itAutoPlay,
title: L10n.of(context)!
.interactiveTranslatorAutoPlaySliderHeader,
subtitle:
L10n.of(context)!.interactiveTranslatorAutoPlayDesc,
onChange: (bool value) => controller
.pangeaController.userController
.updateProfile((profile) {
profile.userSettings.itAutoPlay = value;
return profile;
}),
),
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller.pangeaController.userController
.profile.userSettings.autoPlayMessages,
title: L10n.of(context)!.autoPlayTitle,
subtitle: L10n.of(context)!.autoPlayDesc,
onChange: (bool value) => controller
.pangeaController.userController
.updateProfile((profile) {
profile.userSettings.autoPlayMessages = value;
return profile;
}),
),
],
),
),
),
);
},
);
}
}

View file

@ -150,4 +150,4 @@ SpanDetailsRepoReqAndRes get mockReponseWithChoices {
// res.span.choices![1].selected = true;
// res.span.message = "Conjugation error";
// return res;
// }
// }

View file

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import '../config/environment.dart';

View file

@ -4,37 +4,13 @@ import 'dart:developer';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:http/http.dart';
import '../../widgets/matrix.dart';
import '../models/user_model.dart';
import '../models/user_profile_search_model.dart';
import '../network/requests.dart';
import '../network/urls.dart';
class PUserRepo {
static Future<PUserModel> repoCreatePangeaUser({
required String userID,
required String dob,
required fullName,
required String matrixAccessToken,
}) async {
final Requests req = Requests(
baseUrl: PApiUrls.baseAPI,
matrixAccessToken: matrixAccessToken,
);
final Map<String, dynamic> body = {
ModelKey.userFullName: fullName,
ModelKey.userPangeaUserId: userID,
ModelKey.userDateOfBirth: dob,
};
final Response res = await req.post(
url: PApiUrls.createUser,
body: body,
);
return PUserModel.fromJson(jsonDecode(res.body));
}
static Future<PUserModel?> fetchPangeaUserInfo({
static Future<PangeaProfileResponse?> fetchPangeaUserInfo({
required String userID,
required String matrixAccessToken,
}) async {
@ -49,7 +25,7 @@ class PUserRepo {
objectId: userID,
);
return PUserModel.fromJson(jsonDecode(res.body));
return PangeaProfileResponse.fromJson(jsonDecode(res.body));
} catch (err) {
//status code should be 400 - PTODO - check ffor this.
log("Most likely a first signup and needs to make an account");
@ -57,32 +33,6 @@ class PUserRepo {
}
}
//notes for jordan - only replace non-null fields, return whole profile
//Jordan - should return pangeaUserId as well
static Future<Profile> updateUserProfile(
Profile userProfile,
String accessToken,
) async {
final Requests req = Requests(
baseUrl: PApiUrls.baseAPI,
accessToken: accessToken,
);
final Response res = await req.put(
url: PApiUrls.updateUserProfile,
body: userProfile.toJson(),
);
//temp fix
final content = jsonDecode(res.body);
//PTODO - try taking this out and see where bug occurs
if (content[ModelKey.userPangeaUserId] == null) {
content[ModelKey.userPangeaUserId] =
MatrixState.pangeaController.matrixState.client.userID;
}
return Profile.fromJson(content);
}
static Future<UserProfileSearchResponse> searchUserProfiles({
// List<String>? interests,
String? targetLanguage,

View file

@ -0,0 +1,515 @@
import 'package:country_picker/country_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class CountryDisplayUtil {
/// used in find a partner page for display partner's country
static String flagEmoji(String? countryName) {
countryName = countryName?.split(' (')[0];
final Country? country = CountryService().findByName(countryName);
return country?.flagEmoji ?? "";
}
static String? countryDisplayName(String? countryName, BuildContext context) {
countryName = countryName?.split(' (')[0];
final Country? country = CountryService().findByName(countryName);
if (country?.countryCode == null) return null;
switch (country!.countryCode) {
case 'WW':
return L10n.of(context)!.wwCountryDisplayName;
case 'AF':
return L10n.of(context)!.afCountryDisplayName;
case 'AX':
return L10n.of(context)!.axCountryDisplayName;
case 'AL':
return L10n.of(context)!.alCountryDisplayName;
case 'DZ':
return L10n.of(context)!.dzCountryDisplayName;
case 'AS':
return L10n.of(context)!.asCountryDisplayName;
case 'AD':
return L10n.of(context)!.adCountryDisplayName;
case 'AO':
return L10n.of(context)!.aoCountryDisplayName;
case 'AI':
return L10n.of(context)!.aiCountryDisplayName;
case 'AG':
return L10n.of(context)!.agCountryDisplayName;
case 'AR':
return L10n.of(context)!.arCountryDisplayName;
case 'AM':
return L10n.of(context)!.amCountryDisplayName;
case 'AW':
return L10n.of(context)!.awCountryDisplayName;
case 'AC':
return L10n.of(context)!.acCountryDisplayName;
case 'AU':
return L10n.of(context)!.auCountryDisplayName;
case 'AT':
return L10n.of(context)!.atCountryDisplayName;
case 'AZ':
return L10n.of(context)!.azCountryDisplayName;
case 'BS':
return L10n.of(context)!.bsCountryDisplayName;
case 'BH':
return L10n.of(context)!.bhCountryDisplayName;
case 'BD':
return L10n.of(context)!.bdCountryDisplayName;
case 'BB':
return L10n.of(context)!.bbCountryDisplayName;
case 'BY':
return L10n.of(context)!.byCountryDisplayName;
case 'BE':
return L10n.of(context)!.beCountryDisplayName;
case 'BZ':
return L10n.of(context)!.bzCountryDisplayName;
case 'BJ':
return L10n.of(context)!.bjCountryDisplayName;
case 'BM':
return L10n.of(context)!.bmCountryDisplayName;
case 'BT':
return L10n.of(context)!.btCountryDisplayName;
case 'BO':
return L10n.of(context)!.boCountryDisplayName;
case 'BA':
return L10n.of(context)!.baCountryDisplayName;
case 'BW':
return L10n.of(context)!.bwCountryDisplayName;
case 'BR':
return L10n.of(context)!.brCountryDisplayName;
case 'IO':
return L10n.of(context)!.ioCountryDisplayName;
case 'VG':
return L10n.of(context)!.vgCountryDisplayName;
case 'BN':
return L10n.of(context)!.bnCountryDisplayName;
case 'BG':
return L10n.of(context)!.bgCountryDisplayName;
case 'BF':
return L10n.of(context)!.bfCountryDisplayName;
case 'BI':
return L10n.of(context)!.biCountryDisplayName;
case 'KH':
return L10n.of(context)!.khCountryDisplayName;
case 'CM':
return L10n.of(context)!.cmCountryDisplayName;
case 'CA':
return L10n.of(context)!.caCountryDisplayName;
case 'CV':
return L10n.of(context)!.cvCountryDisplayName;
case 'BQ':
return L10n.of(context)!.bqCountryDisplayName;
case 'KY':
return L10n.of(context)!.kyCountryDisplayName;
case 'CF':
return L10n.of(context)!.cfCountryDisplayName;
case 'TD':
return L10n.of(context)!.tdCountryDisplayName;
case 'CL':
return L10n.of(context)!.clCountryDisplayName;
case 'CN':
return L10n.of(context)!.cnCountryDisplayName;
case 'CX':
return L10n.of(context)!.cxCountryDisplayName;
case 'CC':
return L10n.of(context)!.ccCountryDisplayName;
case 'CO':
return L10n.of(context)!.coCountryDisplayName;
case 'KM':
return L10n.of(context)!.kmCountryDisplayName;
case 'CD':
return L10n.of(context)!.cdCountryDisplayName;
case 'CG':
return L10n.of(context)!.cgCountryDisplayName;
case 'CK':
return L10n.of(context)!.ckCountryDisplayName;
case 'CR':
return L10n.of(context)!.crCountryDisplayName;
case 'CI':
return L10n.of(context)!.ciCountryDisplayName;
case 'HR':
return L10n.of(context)!.hrCountryDisplayName;
case 'CU':
return L10n.of(context)!.cuCountryDisplayName;
case 'CW':
return L10n.of(context)!.cwCountryDisplayName;
case 'CY':
return L10n.of(context)!.cyCountryDisplayName;
case 'CZ':
return L10n.of(context)!.czCountryDisplayName;
case 'DK':
return L10n.of(context)!.dkCountryDisplayName;
case 'DJ':
return L10n.of(context)!.djCountryDisplayName;
case 'DM':
return L10n.of(context)!.dmCountryDisplayName;
case 'DO':
return L10n.of(context)!.doCountryDisplayName;
case 'TL':
return L10n.of(context)!.tlCountryDisplayName;
case 'EC':
return L10n.of(context)!.ecCountryDisplayName;
case 'EG':
return L10n.of(context)!.egCountryDisplayName;
case 'SV':
return L10n.of(context)!.svCountryDisplayName;
case 'GQ':
return L10n.of(context)!.gqCountryDisplayName;
case 'ER':
return L10n.of(context)!.erCountryDisplayName;
case 'EE':
return L10n.of(context)!.eeCountryDisplayName;
case 'SZ':
return L10n.of(context)!.szCountryDisplayName;
case 'ET':
return L10n.of(context)!.etCountryDisplayName;
case 'FK':
return L10n.of(context)!.fkCountryDisplayName;
case 'FO':
return L10n.of(context)!.foCountryDisplayName;
case 'FJ':
return L10n.of(context)!.fjCountryDisplayName;
case 'FI':
return L10n.of(context)!.fiCountryDisplayName;
case 'FR':
return L10n.of(context)!.frCountryDisplayName;
case 'GF':
return L10n.of(context)!.gfCountryDisplayName;
case 'PF':
return L10n.of(context)!.pfCountryDisplayName;
case 'GA':
return L10n.of(context)!.gaCountryDisplayName;
case 'GM':
return L10n.of(context)!.gmCountryDisplayName;
case 'GE':
return L10n.of(context)!.geCountryDisplayName;
case 'DE':
return L10n.of(context)!.deCountryDisplayName;
case 'GH':
return L10n.of(context)!.ghCountryDisplayName;
case 'GI':
return L10n.of(context)!.giCountryDisplayName;
case 'GR':
return L10n.of(context)!.grCountryDisplayName;
case 'GL':
return L10n.of(context)!.glCountryDisplayName;
case 'GD':
return L10n.of(context)!.gdCountryDisplayName;
case 'GP':
return L10n.of(context)!.gpCountryDisplayName;
case 'GU':
return L10n.of(context)!.guCountryDisplayName;
case 'GT':
return L10n.of(context)!.gtCountryDisplayName;
case 'GG':
return L10n.of(context)!.ggCountryDisplayName;
case 'GN':
return L10n.of(context)!.gnCountryDisplayName;
case 'GW':
return L10n.of(context)!.gwCountryDisplayName;
case 'GY':
return L10n.of(context)!.gyCountryDisplayName;
case 'HT':
return L10n.of(context)!.htCountryDisplayName;
case 'HM':
return L10n.of(context)!.hmCountryDisplayName;
case 'HN':
return L10n.of(context)!.hnCountryDisplayName;
case 'HK':
return L10n.of(context)!.hkCountryDisplayName;
case 'HU':
return L10n.of(context)!.huCountryDisplayName;
case 'IS':
return L10n.of(context)!.isCountryDisplayName;
case 'IN':
return L10n.of(context)!.inCountryDisplayName;
case 'ID':
return L10n.of(context)!.idCountryDisplayName;
case 'IR':
return L10n.of(context)!.irCountryDisplayName;
case 'IQ':
return L10n.of(context)!.iqCountryDisplayName;
case 'IE':
return L10n.of(context)!.ieCountryDisplayName;
case 'IM':
return L10n.of(context)!.imCountryDisplayName;
case 'IL':
return L10n.of(context)!.ilCountryDisplayName;
case 'IT':
return L10n.of(context)!.itCountryDisplayName;
case 'JM':
return L10n.of(context)!.jmCountryDisplayName;
case 'JP':
return L10n.of(context)!.jpCountryDisplayName;
case 'JE':
return L10n.of(context)!.jeCountryDisplayName;
case 'JO':
return L10n.of(context)!.joCountryDisplayName;
case 'KZ':
return L10n.of(context)!.kzCountryDisplayName;
case 'KE':
return L10n.of(context)!.keCountryDisplayName;
case 'KI':
return L10n.of(context)!.kiCountryDisplayName;
case 'XK':
return L10n.of(context)!.xkCountryDisplayName;
case 'KW':
return L10n.of(context)!.kwCountryDisplayName;
case 'KG':
return L10n.of(context)!.kgCountryDisplayName;
case 'LA':
return L10n.of(context)!.laCountryDisplayName;
case 'LV':
return L10n.of(context)!.lvCountryDisplayName;
case 'LB':
return L10n.of(context)!.lbCountryDisplayName;
case 'LS':
return L10n.of(context)!.lsCountryDisplayName;
case 'LR':
return L10n.of(context)!.lrCountryDisplayName;
case 'LY':
return L10n.of(context)!.lyCountryDisplayName;
case 'LI':
return L10n.of(context)!.liCountryDisplayName;
case 'LT':
return L10n.of(context)!.ltCountryDisplayName;
case 'LU':
return L10n.of(context)!.luCountryDisplayName;
case 'MO':
return L10n.of(context)!.moCountryDisplayName;
case 'MK':
return L10n.of(context)!.mkCountryDisplayName;
case 'MG':
return L10n.of(context)!.mgCountryDisplayName;
case 'MW':
return L10n.of(context)!.mwCountryDisplayName;
case 'MY':
return L10n.of(context)!.myCountryDisplayName;
case 'MV':
return L10n.of(context)!.mvCountryDisplayName;
case 'ML':
return L10n.of(context)!.mlCountryDisplayName;
case 'MT':
return L10n.of(context)!.mtCountryDisplayName;
case 'MH':
return L10n.of(context)!.mhCountryDisplayName;
case 'MQ':
return L10n.of(context)!.mqCountryDisplayName;
case 'MR':
return L10n.of(context)!.mrCountryDisplayName;
case 'MU':
return L10n.of(context)!.muCountryDisplayName;
case 'YT':
return L10n.of(context)!.ytCountryDisplayName;
case 'MX':
return L10n.of(context)!.mxCountryDisplayName;
case 'FM':
return L10n.of(context)!.fmCountryDisplayName;
case 'MD':
return L10n.of(context)!.mdCountryDisplayName;
case 'MC':
return L10n.of(context)!.mcCountryDisplayName;
case 'MN':
return L10n.of(context)!.mnCountryDisplayName;
case 'ME':
return L10n.of(context)!.meCountryDisplayName;
case 'MS':
return L10n.of(context)!.msCountryDisplayName;
case 'MA':
return L10n.of(context)!.maCountryDisplayName;
case 'MZ':
return L10n.of(context)!.mzCountryDisplayName;
case 'MM':
return L10n.of(context)!.mmCountryDisplayName;
case 'NA':
return L10n.of(context)!.naCountryDisplayName;
case 'NR':
return L10n.of(context)!.nrCountryDisplayName;
case 'NP':
return L10n.of(context)!.npCountryDisplayName;
case 'NL':
return L10n.of(context)!.nlCountryDisplayName;
case 'NC':
return L10n.of(context)!.ncCountryDisplayName;
case 'NZ':
return L10n.of(context)!.nzCountryDisplayName;
case 'NI':
return L10n.of(context)!.niCountryDisplayName;
case 'NE':
return L10n.of(context)!.neCountryDisplayName;
case 'NG':
return L10n.of(context)!.ngCountryDisplayName;
case 'NU':
return L10n.of(context)!.nuCountryDisplayName;
case 'NF':
return L10n.of(context)!.nfCountryDisplayName;
case 'KP':
return L10n.of(context)!.kpCountryDisplayName;
case 'MP':
return L10n.of(context)!.mpCountryDisplayName;
case 'NO':
return L10n.of(context)!.noCountryDisplayName;
case 'OM':
return L10n.of(context)!.omCountryDisplayName;
case 'PK':
return L10n.of(context)!.pkCountryDisplayName;
case 'PW':
return L10n.of(context)!.pwCountryDisplayName;
case 'PS':
return L10n.of(context)!.psCountryDisplayName;
case 'PA':
return L10n.of(context)!.paCountryDisplayName;
case 'PG':
return L10n.of(context)!.pgCountryDisplayName;
case 'PY':
return L10n.of(context)!.pyCountryDisplayName;
case 'PE':
return L10n.of(context)!.peCountryDisplayName;
case 'PH':
return L10n.of(context)!.phCountryDisplayName;
case 'PL':
return L10n.of(context)!.plCountryDisplayName;
case 'PT':
return L10n.of(context)!.ptCountryDisplayName;
case 'PR':
return L10n.of(context)!.prCountryDisplayName;
case 'QA':
return L10n.of(context)!.qaCountryDisplayName;
case 'RE':
return L10n.of(context)!.reCountryDisplayName;
case 'RO':
return L10n.of(context)!.roCountryDisplayName;
case 'RU':
return L10n.of(context)!.ruCountryDisplayName;
case 'RW':
return L10n.of(context)!.rwCountryDisplayName;
case 'BL':
return L10n.of(context)!.blCountryDisplayName;
case 'SH':
return L10n.of(context)!.shCountryDisplayName;
case 'KN':
return L10n.of(context)!.knCountryDisplayName;
case 'LC':
return L10n.of(context)!.lcCountryDisplayName;
case 'MF':
return L10n.of(context)!.mfCountryDisplayName;
case 'PM':
return L10n.of(context)!.pmCountryDisplayName;
case 'VC':
return L10n.of(context)!.vcCountryDisplayName;
case 'WS':
return L10n.of(context)!.wsCountryDisplayName;
case 'SM':
return L10n.of(context)!.smCountryDisplayName;
case 'ST':
return L10n.of(context)!.stCountryDisplayName;
case 'SA':
return L10n.of(context)!.saCountryDisplayName;
case 'SN':
return L10n.of(context)!.snCountryDisplayName;
case 'RS':
return L10n.of(context)!.rsCountryDisplayName;
case 'SC':
return L10n.of(context)!.scCountryDisplayName;
case 'SL':
return L10n.of(context)!.slCountryDisplayName;
case 'SG':
return L10n.of(context)!.sgCountryDisplayName;
case 'SX':
return L10n.of(context)!.sxCountryDisplayName;
case 'SK':
return L10n.of(context)!.skCountryDisplayName;
case 'SI':
return L10n.of(context)!.siCountryDisplayName;
case 'SB':
return L10n.of(context)!.sbCountryDisplayName;
case 'SO':
return L10n.of(context)!.soCountryDisplayName;
case 'ZA':
return L10n.of(context)!.zaCountryDisplayName;
case 'GS':
return L10n.of(context)!.gsCountryDisplayName;
case 'KR':
return L10n.of(context)!.krCountryDisplayName;
case 'SS':
return L10n.of(context)!.ssCountryDisplayName;
case 'ES':
return L10n.of(context)!.esCountryDisplayName;
case 'LK':
return L10n.of(context)!.lkCountryDisplayName;
case 'SD':
return L10n.of(context)!.sdCountryDisplayName;
case 'SR':
return L10n.of(context)!.srCountryDisplayName;
case 'SJ':
return L10n.of(context)!.sjCountryDisplayName;
case 'SE':
return L10n.of(context)!.seCountryDisplayName;
case 'CH':
return L10n.of(context)!.chCountryDisplayName;
case 'SY':
return L10n.of(context)!.syCountryDisplayName;
case 'TW':
return L10n.of(context)!.twCountryDisplayName;
case 'TJ':
return L10n.of(context)!.tjCountryDisplayName;
case 'TZ':
return L10n.of(context)!.tzCountryDisplayName;
case 'TH':
return L10n.of(context)!.thCountryDisplayName;
case 'TG':
return L10n.of(context)!.tgCountryDisplayName;
case 'TK':
return L10n.of(context)!.tkCountryDisplayName;
case 'TO':
return L10n.of(context)!.toCountryDisplayName;
case 'TT':
return L10n.of(context)!.ttCountryDisplayName;
case 'TN':
return L10n.of(context)!.tnCountryDisplayName;
case 'TR':
return L10n.of(context)!.trCountryDisplayName;
case 'TM':
return L10n.of(context)!.tmCountryDisplayName;
case 'TC':
return L10n.of(context)!.tcCountryDisplayName;
case 'TV':
return L10n.of(context)!.tvCountryDisplayName;
case 'VI':
return L10n.of(context)!.viCountryDisplayName;
case 'UG':
return L10n.of(context)!.ugCountryDisplayName;
case 'UA':
return L10n.of(context)!.uaCountryDisplayName;
case 'AE':
return L10n.of(context)!.aeCountryDisplayName;
case 'GB':
return L10n.of(context)!.gbCountryDisplayName;
case 'US':
return L10n.of(context)!.usCountryDisplayName;
case 'UY':
return L10n.of(context)!.uyCountryDisplayName;
case 'UZ':
return L10n.of(context)!.uzCountryDisplayName;
case 'VU':
return L10n.of(context)!.vuCountryDisplayName;
case 'VA':
return L10n.of(context)!.vaCountryDisplayName;
case 'VE':
return L10n.of(context)!.veCountryDisplayName;
case 'VN':
return L10n.of(context)!.vnCountryDisplayName;
case 'WF':
return L10n.of(context)!.wfCountryDisplayName;
case 'EH':
return L10n.of(context)!.ehCountryDisplayName;
case 'YE':
return L10n.of(context)!.yeCountryDisplayName;
case 'ZM':
return L10n.of(context)!.zmCountryDisplayName;
case 'ZW':
return L10n.of(context)!.zwCountryDisplayName;
}
return null;
}
}

View file

@ -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<void> 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<void> delete(
String key, {
bool addClientIdToKey = true,
local = false,
}) async {
return local
? deleteLocal(
key,
addClientIdToKey: addClientIdToKey,
)
: deleteProfile(key);
}
/// save data in local
Future<void> saveLocal(
String key,
dynamic data, {
bool addClientIdToKey = true,
}) async {
await _box.write(_key(key, addClientIdToKey: addClientIdToKey), data);
}
Future<void> 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}) {
/// 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.read(_key(key, addClientIdToKey: addClientIdToKey))
? _box.read(_key(key, isAccountData: isAccountData))
: 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<void> deleteLocal(String key, {bool addClientIdToKey = true}) 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<void> delete(String key, {bool isAccountData = true}) async {
return pangeaController.matrixState.client.userID != null
? _box.remove(_key(key, addClientIdToKey: addClientIdToKey))
? _box.remove(_key(key, isAccountData: isAccountData))
: null;
}
Future<void> 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
/// 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();
}
}

View file

@ -3,7 +3,6 @@ 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/utils/any_state_holder.dart';
@ -329,17 +328,13 @@ class MessageToolbarState extends State<MessageToolbar> {
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final bool autoplay = MatrixState.pangeaController.pStoreService.read(
PLocalKey.autoPlayMessages,
) ??
false;
if (widget.pangeaMessageEvent.isAudioMessage) {
updateMode(MessageMode.speechToText);
return;
}
autoplay
MatrixState.pangeaController.userController.profile.userSettings
.autoPlayMessages
? updateMode(MessageMode.textToSpeech)
: updateMode(MessageMode.translation);
});

View file

@ -43,8 +43,7 @@ class _JoinClassWithLinkState extends State<JoinClassWithLink> {
await _pangeaController.pStoreService.save(
PLocalKey.cachedClassCodeToJoin,
classCode,
addClientIdToKey: false,
local: true,
isAccountData: false,
);
context.go("/home");
});

View file

@ -1,7 +1,6 @@
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/utils/bot_style.dart';
@ -154,21 +153,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) {
@ -344,6 +340,12 @@ class WordMatchContent extends StatelessWidget {
if (controller.widget.scm.pangeaMatch!.isITStart)
DontShowSwitchListTile(
controller: pangeaController,
onSwitch: (bool value) {
pangeaController.userController.updateProfile((profile) {
profile.userSettings.itAutoPlay = value;
return profile;
});
},
),
],
);
@ -486,10 +488,12 @@ class StartITButton extends StatelessWidget {
class DontShowSwitchListTile extends StatefulWidget {
final PangeaController controller;
final Function(bool) onSwitch;
const DontShowSwitchListTile({
super.key,
required this.controller,
required this.onSwitch,
});
@override
@ -510,12 +514,9 @@ class DontShowSwitchListTileState extends State<DontShowSwitchListTile> {
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) {
widget.onSwitch(value);
setState(() => switchValue = value);
},
);
}

View file

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:country_picker/country_picker.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/utils/country_display.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -19,10 +20,13 @@ class CountryPickerTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Profile? profile = pangeaController.userController.userModel?.profile;
final Profile profile = pangeaController.userController.profile;
return ListTile(
title: Text(
"${L10n.of(context)!.countryInformation}: ${profile?.countryDisplayName(context) ?? ''} ${profile?.flagEmoji}",
"${L10n.of(context)!.countryInformation}: ${CountryDisplayUtil.countryDisplayName(
profile.userSettings.country,
context,
) ?? ''} ${CountryDisplayUtil.flagEmoji(profile.userSettings.country)}",
),
trailing: const Icon(Icons.edit_outlined),
onTap: () => showCountryPicker(

View file

@ -88,13 +88,14 @@ pLanguageDialog(BuildContext parentContext, Function callback) async {
context: context,
future: () async {
try {
await pangeaController.userController
.updateUserProfile(
sourceLanguage:
selectedSourceLanguage.langCode,
targetLanguage:
selectedTargetLanguage.langCode,
);
pangeaController.userController
.updateProfile((profile) {
profile.userSettings.sourceLanguage =
selectedSourceLanguage.langCode;
profile.userSettings.targetLanguage =
selectedTargetLanguage.langCode;
return profile;
});
Navigator.pop(context);
} catch (err, s) {
debugger(when: kDebugMode);

View file

@ -1,45 +1,37 @@
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 {
class ProfileSettingsSwitchListTile extends StatefulWidget {
final bool defaultValue;
final String pStoreKey;
final String title;
final String? subtitle;
final bool local;
final Function(bool) onChange;
const PSettingsSwitchListTile.adaptive({
const ProfileSettingsSwitchListTile.adaptive({
super.key,
this.defaultValue = false,
required this.pStoreKey,
required this.defaultValue,
required this.title,
required this.onChange,
this.subtitle,
this.local = false,
});
@override
PSettingsSwitchListTileState createState() => PSettingsSwitchListTileState();
}
class PSettingsSwitchListTileState extends State<PSettingsSwitchListTile> {
class PSettingsSwitchListTileState
extends State<ProfileSettingsSwitchListTile> {
bool currentValue = true;
@override
void initState() {
currentValue = MatrixState.pangeaController.pStoreService.read(
widget.pStoreKey,
local: widget.local,
) ??
widget.defaultValue;
currentValue = widget.defaultValue;
super.initState();
}
@override
Widget build(BuildContext context) {
final PangeaController pangeaController = MatrixState.pangeaController;
return SwitchListTile.adaptive(
value: currentValue,
title: Text(widget.title),
@ -47,20 +39,15 @@ class PSettingsSwitchListTileState extends State<PSettingsSwitchListTile> {
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;
widget.onChange(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",
s: s,
);
}
setState(() {});
},
);
}

View file

@ -342,8 +342,11 @@ class MatrixState extends State<Matrix> 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) {