removed any functionality attached to pangea profile, only use it to migrate. Made matrix profile a class with instance members.

This commit is contained in:
ggurdin 2024-07-12 13:52:46 -04:00
parent ff468cb4bd
commit ca9dcba4e7
23 changed files with 1171 additions and 1245 deletions

View file

@ -513,8 +513,9 @@ class Choreographer {
chatController.room,
);
bool get itAutoPlayEnabled =>
pangeaController.userController.matrixProfile.itAutoPlay;
bool get itAutoPlayEnabled {
return pangeaController.userController.profile.userSettings.itAutoPlay;
}
bool get definitionsEnabled =>
pangeaController.permissionsController.isToolEnabled(

View file

@ -1,5 +1,5 @@
class PLocalKey {
static const String user = 'user';
static const String access = "access";
static const String cachedClassCodeToJoin = "cachedclasscodetojoin";
static const String beganWebPayment = "beganWebPayment";
static const String dismissedPaywall = 'dismissedPaywall';

View file

@ -16,6 +16,9 @@ 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

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

@ -204,24 +204,24 @@ class MyAnalyticsController {
Completer<void>? _updateCompleter;
Future<void> updateAnalytics() async {
if (!(_updateCompleter?.isCompleted ?? true)) {
await _updateCompleter!.future;
return;
}
_updateCompleter = Completer<void>();
try {
await _updateAnalytics();
clearMessagesSinceUpdate();
} catch (err, s) {
ErrorHandler.logError(
e: err,
m: "Failed to update analytics",
s: s,
);
} finally {
_updateCompleter?.complete();
_updateCompleter = null;
}
// if (!(_updateCompleter?.isCompleted ?? true)) {
// await _updateCompleter!.future;
// return;
// }
// _updateCompleter = Completer<void>();
// try {
// await _updateAnalytics();
// clearMessagesSinceUpdate();
// } catch (err, s) {
// ErrorHandler.logError(
// e: err,
// m: "Failed to update analytics",
// s: s,
// );
// } finally {
// _updateCompleter?.complete();
// _updateCompleter = null;
// }
}
String? get userL2 => _pangeaController.languageController.activeL2Code();

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';
@ -31,12 +30,11 @@ class PermissionsController extends BaseController {
}
/// Returns false if user is null
bool isUser18() =>
_pangeaController.userController.matrixProfile.dateOfBirth
?.isAtLeastYearsOld(
AgeLimits.toAccessFeatures,
) ??
false;
bool isUser18() {
final DateTime? dob =
_pangeaController.userController.profile.userSettings.dateOfBirth;
return dob?.isAtLeastYearsOld(AgeLimits.toAccessFeatures) ?? false;
}
/// A user can private chat if
/// 1) they are 18 and outside a class context or
@ -97,20 +95,22 @@ class PermissionsController extends BaseController {
return classPermission == 0;
}
bool userToolSetting(MatrixProfileEnum setting) {
switch (setting.asToolSetting) {
bool userToolSetting(ToolSetting setting) {
switch (setting) {
case ToolSetting.interactiveTranslator:
return _pangeaController
.userController.matrixProfile.interactiveTranslator;
.userController.profile.toolSettings.interactiveTranslator;
case ToolSetting.interactiveGrammar:
return _pangeaController
.userController.matrixProfile.interactiveGrammar;
.userController.profile.toolSettings.interactiveGrammar;
case ToolSetting.immersionMode:
return _pangeaController.userController.matrixProfile.immersionMode;
return _pangeaController
.userController.profile.toolSettings.immersionMode;
case ToolSetting.definitions:
return _pangeaController.userController.matrixProfile.definitions;
return _pangeaController
.userController.profile.toolSettings.definitions;
case ToolSetting.autoIGC:
return _pangeaController.userController.matrixProfile.autoIGC;
return _pangeaController.userController.profile.toolSettings.autoIGC;
default:
return false;
}
@ -118,13 +118,13 @@ class PermissionsController extends BaseController {
bool isToolEnabled(ToolSetting setting, Room? room) {
if (room?.isSpaceAdmin ?? false) {
return userToolSetting(setting.asMatrixProfileField);
return userToolSetting(setting);
}
final int? classPermission =
room != null ? classLanguageToolPermission(room, setting) : 1;
if (classPermission == 0) return false;
if (classPermission == 2) return true;
return userToolSetting(setting.asMatrixProfileField);
return userToolSetting(setting);
}
bool isWritingAssistanceEnabled(Room? room) {

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';
@ -179,31 +178,35 @@ class SubscriptionController extends BaseController {
}
}
bool get _activatedNewUserTrial =>
_pangeaController.userController.inTrialWindow &&
_pangeaController.userController.matrixProfile.activatedFreeTrial;
bool get _activatedNewUserTrial {
final bool activated = _pangeaController
.userController.profile.userSettings.activatedFreeTrial;
return _pangeaController.userController.inTrialWindow && activated;
}
void activateNewUserTrial() {
_pangeaController.userController.matrixProfile.saveProfileData({
MatrixProfileEnum.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);

View file

@ -2,7 +2,6 @@ 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:fluffychat/pangea/utils/error_handler.dart';
@ -27,61 +26,62 @@ class UserController extends BaseController {
String? get _matrixAccessToken =>
_pangeaController.matrixState.client.accessToken;
/// An instance of matrix profile. Used to update and access info from the user's matrix profile.
/// No information needs to be passing in the constructor as the matrix
/// profile get all of it's internal data the accountData stored in the client.
MatrixProfile matrixProfile = MatrixProfile();
Profile? _cachedProfile;
/// Returns the [PUserModel] object representing the current user.
///
/// This method retrieves the user data from the local storage using the [PLocalKey.user] key.
/// If the data exists, it is converted to a [PUserModel] object using the [PUserModel.fromJson] method.
/// If the data is null, indicating that the user is not logged in (or that
/// profile fetching has not yet completed, or had an error), null is returned.
PUserModel? get userModel {
final data = _pangeaController.pStoreService.read(PLocalKey.user);
return data != null ? PUserModel.fromJson(data) : null;
/// Listen for updates to account data in syncs and update the cached profile
void addProfileListener() {
_pangeaController.matrixState.client.onSync.stream
.where((sync) => sync.accountData != null)
.listen((sync) {
final Profile? fromAccountData = Profile.fromAccountData();
if (fromAccountData != null) {
_cachedProfile = fromAccountData;
}
});
}
/// Creates a user pangea chat profile, saves the user's profile information
/// locally, and set the user's DOB in their matrix profile.
///
/// The [dob] parameter is required and represents the date of birth of the user.
/// This method creates a new [PUserModel] using the [PUserRepo.repoCreatePangeaUser] method,
/// and saves the user model in local storage.
/// It also updates the user's matrix profile using the [updateMatrixProfile] method.
Future<void> createProfile({required String dob}) async {
if (userId == null || _matrixAccessToken == null) {
ErrorHandler.logError(
e: "calling createProfile with userId == null or 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!;
/// if account data is empty, return an empty profile
if (_pangeaController.matrixState.client.accountData.isEmpty) {
return Profile.emptyProfile;
}
final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser(
userID: userId!,
fullName: fullname,
dob: dob,
matrixAccessToken: _matrixAccessToken!,
);
newUserModel.save(_pangeaController);
await matrixProfile.saveProfileData(
{MatrixProfileEnum.dateOfBirth.title: dob},
waitForDataInSync: true,
/// 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;
}
void updateProfile(Profile Function(Profile) update) {
final Profile updatedProfile = update(profile);
updatedProfile.saveProfileData();
}
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<PUserModel?>? _profileCompleter;
Completer<void>? _profileCompleter;
/// Fetches the user model.
///
/// This method retrieves the user model asynchronously. If the profile completer is already completed,
/// it returns the future value of the completer. If the user model is currently being fetched,
/// it waits for the completion of the completer and returns the future value. Otherwise, it sets
/// the fetching flag, fetches the user model, completes the profile completer with the fetched user model,
/// and returns the future value of the completer.
///
/// Returns the future value of the user model completer.
Future<PUserModel?> fetchUserModel() async {
Future<void> initialize() async {
if (_profileCompleter?.isCompleted ?? false) {
return _profileCompleter!.future;
}
@ -91,213 +91,79 @@ class UserController extends BaseController {
return _profileCompleter!.future;
}
_profileCompleter = Completer<PUserModel?>();
PUserModel? fetchedUserModel;
_profileCompleter = Completer<void>();
try {
fetchedUserModel = await _fetchUserModel();
await _initialize();
addProfileListener();
} catch (err, s) {
ErrorHandler.logError(e: err, s: s);
} finally {
_profileCompleter!.complete(fetchedUserModel);
_profileCompleter!.complete();
}
return _profileCompleter!.future;
}
/// Fetches the user model asynchronously.
///
/// This method fetches the user model by calling the [fetchPangeaUserInfo] method
/// from the [PUserRepo] class. It requires the [_matrixAccessToken] and [userId]
/// to be non-null. If either of them is null, an error is logged.
///
/// The fetched [newUserModel] is then saved locally.
/// The [migrateMatrixProfile] method is called, to migrate any information that is
/// already saved in the user's pangea profile but is not yet saved in the
/// user's matrix profile. Finally, the [newUserModel] is returned.
Future<PUserModel?> _fetchUserModel() async {
if (_matrixAccessToken == null || userId == null) {
ErrorHandler.logError(
e: "calling fetchUserModel with userId == null or matrixAccessToken == null",
);
return null;
Future<void> _initialize() async {
await waitForAccountData();
if (profile.userSettings.dateOfBirth != null) {
return;
}
final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo(
final PangeaProfileResponse? resp = await PUserRepo.fetchPangeaUserInfo(
userID: userId!,
matrixAccessToken: _matrixAccessToken!,
);
newUserModel?.save(_pangeaController);
await migrateMatrixProfile();
return newUserModel;
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;
await fetchUserModel();
_cachedProfile = null;
await initialize();
}
/// Migrates the user's profile from Pangea to Matrix.
///
/// This method retrieves the user's profile / local settings information from Pangea and checks for corresponding information stored in Matrix.
/// If any of the profile fields in Pangea have information, but the corresponding fields in Matrix are null, the values are updated in Matrix.
/// The profile fields that are checked for migration include date of birth, creation date, target language, source language, country, and public profile.
/// Additionally, several profile settings related to auto play, trial activation, interactive features, and instructional messages are also checked for migration.
///
/// This method calls the [updateMatrixProfile] method to update the user's profile in Matrix with the migrated values.
///
/// Note: This method assumes that the [userModel] and [_pangeaController] instances are properly initialized before calling this method.
Future<void> migrateMatrixProfile() async {
// This function relies on the client's account data being loaded.
// The account data is loaded during
// the first sync, so wait for that to complete.
/// Account data comes through in the first sync, so wait for that
Future<void> waitForAccountData() async {
final client = _pangeaController.matrixState.client;
if (client.prevBatch == null) {
await client.onSync.stream.first;
}
final Map<String, dynamic> profileUpdates = {};
final Profile? pangeaProfile = userModel?.profile;
for (final field in MatrixProfile.pangeaProfileFields) {
final dynamic matrixValue = matrixProfile.getProfileData(field);
dynamic pangeaValue;
switch (field) {
case MatrixProfileEnum.dateOfBirth:
pangeaValue = pangeaProfile?.dateOfBirth;
break;
case MatrixProfileEnum.createdAt:
pangeaValue = pangeaProfile?.createdAt;
break;
case MatrixProfileEnum.targetLanguage:
pangeaValue = pangeaProfile?.targetLanguage;
break;
case MatrixProfileEnum.sourceLanguage:
pangeaValue = pangeaProfile?.sourceLanguage;
break;
case MatrixProfileEnum.country:
pangeaValue = pangeaProfile?.country;
break;
case MatrixProfileEnum.publicProfile:
pangeaValue = pangeaProfile?.publicProfile;
break;
default:
break;
}
if (pangeaValue != null && matrixValue == null) {
profileUpdates[field.title] = pangeaValue;
}
}
for (final value in MatrixProfileEnum.values) {
if (profileUpdates.containsKey(value.title)) continue;
final dynamic localValue =
_pangeaController.pStoreService.read(value.title);
final dynamic matrixValue = matrixProfile.getProfileData(value);
final dynamic unmigratedValue =
localValue != null && matrixValue == null ? localValue : null;
if (unmigratedValue != null) {
profileUpdates[value.title] = unmigratedValue;
}
}
await matrixProfile.saveProfileData(
profileUpdates,
waitForDataInSync: true,
);
}
/// Updates the user's profile with the provided information.
///
/// The [dateOfBirth] parameter is the new date of birth for the user.
/// The [targetLanguage] parameter is the new target language for the user.
/// The [sourceLanguage] parameter is the new source language for the user.
/// The [country] parameter is the new country for the user.
/// The [interests] parameter is a list of new interests for the user.
/// The [speaks] parameter is a list of new languages the user speaks.
/// The [publicProfile] parameter indicates whether the user's profile should be public or not.
Future<void> updateUserProfile({
String? dateOfBirth,
String? targetLanguage,
String? sourceLanguage,
String? country,
List<String>? interests,
List<String>? speaks,
bool? publicProfile,
}) async {
if (userModel == null) {
ErrorHandler.logError(
e: "calling updateUserProfile with userModel == null",
);
return;
}
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);
matrixProfile.saveProfileData({
MatrixProfileEnum.dateOfBirth.title: dateOfBirth,
MatrixProfileEnum.targetLanguage.title: targetLanguage,
MatrixProfileEnum.sourceLanguage.title: sourceLanguage,
MatrixProfileEnum.country.title: country,
MatrixProfileEnum.publicProfile.title: publicProfile,
});
}
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
/// It checks if the `userModel` has a non-null `access` token and if the token is expired using the `Jwt.isExpired()` method.
/// If the `userModel` is null or the `access` token is null, it returns true indicating that a new JWT is needed.
bool get needNewJWT =>
userModel?.access != null ? Jwt.isExpired(userModel!.access) : true;
bool needNewJWT(String token) => Jwt.isExpired(token);
/// Retrieves the access token for the user.
///
/// If the locally stored user model is null or the access token has
/// expired, it fetches the user model.
/// If the user model is still null after fetching, an error thrown.
///
/// Returns the access token as a string, or null if the user model is null.
/// 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 PUserModel? useThisOne =
needNewJWT ? await fetchUserModel() : userModel;
final localAccessToken =
_pangeaController.pStoreService.read(PLocalKey.access);
if (useThisOne == null) {
throw ("Trying to get accessToken with null userModel");
if (localAccessToken == null || needNewJWT(localAccessToken)) {
final PangeaProfileResponse? userModel =
await PUserRepo.fetchPangeaUserInfo(
userID: userId!,
matrixAccessToken: _matrixAccessToken!,
);
if (userModel?.access == null) {
throw ("Trying to get accessToken with null userModel");
}
_pangeaController.pStoreService.save(
PLocalKey.access,
userModel!.access,
);
return userModel.access;
}
return useThisOne.access;
return localAccessToken;
}
/// Returns the full name of the user.
@ -314,19 +180,6 @@ class UserController extends BaseController {
return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", "");
}
/// Checks if the user data is available.
/// Returns a [Future] that completes with a [bool] value
/// indicating whether the user data is available or not.
Future<bool> get isPUserDataAvailable async {
try {
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
return toCheck != null;
} catch (err, s) {
ErrorHandler.logError(e: err, s: s);
return false;
}
}
/// Checks if user data is available and the date of birth is set.
/// Returns a [Future] that completes with a [bool] value indicating
/// whether the user data is available and the date of birth is set.
@ -334,8 +187,8 @@ class UserController extends BaseController {
try {
// the function fetchUserModel() uses a completer, so it shouldn't
// re-call the endpoint if it has already been called
await fetchUserModel();
return matrixProfile.dateOfBirth != null;
await initialize();
return profile.userSettings.dateOfBirth != null;
} catch (err, s) {
ErrorHandler.logError(e: err, s: s);
return false;
@ -344,11 +197,11 @@ class UserController extends BaseController {
/// 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)),
);
}
@ -363,12 +216,8 @@ class UserController extends BaseController {
/// 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 &&
@ -382,7 +231,9 @@ class UserController extends BaseController {
}
/// Returns a boolean value indicating whether the user's profile is public.
bool get isPublic => userModel?.profile?.publicProfile ?? false;
bool get isPublic {
return profile.userSettings.publicProfile;
}
/// Retrieves the user's email address.
///

View file

@ -1,6 +1,5 @@
import 'dart:developer';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -270,19 +269,4 @@ extension SettingCopy on ToolSetting {
return L10n.of(context)!.autoIGCToolDescription;
}
}
MatrixProfileEnum get asMatrixProfileField {
switch (this) {
case ToolSetting.interactiveTranslator:
return MatrixProfileEnum.interactiveTranslator;
case ToolSetting.interactiveGrammar:
return MatrixProfileEnum.interactiveGrammar;
case ToolSetting.immersionMode:
return MatrixProfileEnum.immersionMode;
case ToolSetting.definitions:
return MatrixProfileEnum.definitions;
case ToolSetting.autoIGC:
return MatrixProfileEnum.autoIGC;
}
}
}

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

@ -37,7 +37,7 @@ class FindPartnerController extends State<FindPartner> {
Timer? coolDown;
final List<Profile> _userProfilesCache = [];
final List<PangeaProfile> _userProfilesCache = [];
final scrollController = ScrollController();
String? error;
@ -83,7 +83,7 @@ class FindPartnerController extends State<FindPartner> {
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 &&
@ -127,8 +127,7 @@ class FindPartnerController extends State<FindPartner> {
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.createProfile(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,5 +1,4 @@
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart';
@ -18,71 +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 MatrixProfile.toolSettings)
setting.asToolSetting != null
? ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller
.pangeaController.permissionsController
.userToolSetting(setting),
title: setting.asToolSetting!.toolName(context),
subtitle:
setting.asToolSetting!.toolDescription(context),
profileKey: setting.asToolSetting!.asMatrixProfileField,
)
: const SizedBox(),
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller
.pangeaController.userController.matrixProfile.itAutoPlay,
title:
L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader,
subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc,
profileKey: MatrixProfileEnum.itAutoPlay,
),
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller.pangeaController.userController
.matrixProfile.autoPlayMessages,
title: L10n.of(context)!.autoPlayTitle,
subtitle: L10n.of(context)!.autoPlayDesc,
profileKey: MatrixProfileEnum.autoPlayMessages,
),
],
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

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

@ -333,7 +333,8 @@ class MessageToolbarState extends State<MessageToolbar> {
return;
}
MatrixState.pangeaController.userController.matrixProfile.autoPlayMessages
MatrixState.pangeaController.userController.profile.userSettings
.autoPlayMessages
? updateMode(MessageMode.textToSpeech)
: updateMode(MessageMode.translation);
});

View file

@ -3,7 +3,6 @@ import 'dart:developer';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/span_data_type.dart';
import 'package:fluffychat/pangea/models/span_data.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/match_copy.dart';
@ -341,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;
});
},
),
],
);
@ -483,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
@ -508,10 +515,7 @@ class DontShowSwitchListTileState extends State<DontShowSwitchListTile> {
title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader),
value: switchValue,
onChanged: (value) {
MatrixState.pangeaController.userController.matrixProfile
.saveProfileData(
{MatrixProfileEnum.itAutoPlay.title: 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,20 +1,18 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
class ProfileSettingsSwitchListTile extends StatefulWidget {
final bool defaultValue;
final MatrixProfileEnum profileKey;
final String title;
final String? subtitle;
final Function(bool) onChange;
const ProfileSettingsSwitchListTile.adaptive({
super.key,
this.defaultValue = false,
required this.profileKey,
required this.defaultValue,
required this.title,
required this.onChange,
this.subtitle,
});
@ -28,11 +26,7 @@ class PSettingsSwitchListTileState
@override
void initState() {
currentValue = MatrixState.pangeaController.userController.matrixProfile
.getProfileData(
widget.profileKey,
) ??
widget.defaultValue;
currentValue = widget.defaultValue;
super.initState();
}
@ -45,15 +39,12 @@ class PSettingsSwitchListTileState
subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null,
onChanged: (bool newValue) async {
try {
MatrixState.pangeaController.userController.matrixProfile
.saveProfileData({
widget.profileKey.title: newValue,
});
widget.onChange(newValue);
setState(() => currentValue = newValue);
} catch (err, s) {
ErrorHandler.logError(
e: err,
m: "Failed to updates user setting ${widget.profileKey.title}",
m: "Failed to updates user setting",
s: s,
);
}