|
|
|
|
@ -5,7 +5,7 @@ import 'package:fluffychat/pangea/constants/language_constants.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/controllers/base_controller.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
|
|
|
|
import 'package:jwt_decode/jwt_decode.dart';
|
|
|
|
|
import 'package:matrix/matrix.dart' as matrix;
|
|
|
|
|
|
|
|
|
|
@ -13,14 +13,44 @@ import '../constants/local.key.dart';
|
|
|
|
|
import '../models/user_model.dart';
|
|
|
|
|
import '../repo/user_repo.dart';
|
|
|
|
|
|
|
|
|
|
/// Controller that manages saving and reading of user/profile information
|
|
|
|
|
class UserController extends BaseController {
|
|
|
|
|
late PangeaController _pangeaController;
|
|
|
|
|
final Completer _completer = Completer();
|
|
|
|
|
UserController(PangeaController pangeaController) : super() {
|
|
|
|
|
_pangeaController = pangeaController;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> createPangeaUser({required String dob}) async {
|
|
|
|
|
/// Convenience function that returns the user ID currently stored in the client.
|
|
|
|
|
String? get userId => _pangeaController.matrixState.client.userID;
|
|
|
|
|
|
|
|
|
|
/// Convenience function that returns the accessToken currently stored in the client.
|
|
|
|
|
String? get _matrixAccessToken =>
|
|
|
|
|
_pangeaController.matrixState.client.accessToken;
|
|
|
|
|
|
|
|
|
|
/// Returns the [PUserModel] object representing the current user.
|
|
|
|
|
///
|
|
|
|
|
/// This method retrieves the user data from the local storage using the [PLocalKey.user] key.
|
|
|
|
|
/// If the data exists, it is converted to a [PUserModel] object using the [PUserModel.fromJson] method.
|
|
|
|
|
/// If the data is null, indicating that the user is not logged in (or that
|
|
|
|
|
/// profile fetching has not yet completed, or had an error), null is returned.
|
|
|
|
|
PUserModel? get userModel {
|
|
|
|
|
final data = _pangeaController.pStoreService.read(PLocalKey.user);
|
|
|
|
|
return data != null ? PUserModel.fromJson(data) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a user pangea chat profile, saves the user's profile information
|
|
|
|
|
/// locally, and set the user's DOB in their matrix profile.
|
|
|
|
|
///
|
|
|
|
|
/// The [dob] parameter is required and represents the date of birth of the user.
|
|
|
|
|
/// This method creates a new [PUserModel] using the [PUserRepo.repoCreatePangeaUser] method,
|
|
|
|
|
/// and saves the user model in local storage.
|
|
|
|
|
/// It also updates the user's matrix profile using the [updateMatrixProfile] method.
|
|
|
|
|
Future<void> createProfile({required String dob}) async {
|
|
|
|
|
if (userId == null || _matrixAccessToken == null) {
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: "calling createProfile with userId == null or matrixAccessToken == null",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser(
|
|
|
|
|
userID: userId!,
|
|
|
|
|
fullName: fullname,
|
|
|
|
|
@ -28,139 +58,167 @@ class UserController extends BaseController {
|
|
|
|
|
matrixAccessToken: _matrixAccessToken!,
|
|
|
|
|
);
|
|
|
|
|
newUserModel.save(_pangeaController);
|
|
|
|
|
await updateMatrixProfile(dateOfBirth: dob);
|
|
|
|
|
await MatrixProfile.saveProfileData(
|
|
|
|
|
{MatrixProfileEnum.dateOfBirth.title: dob},
|
|
|
|
|
waitForDataInSync: true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A boolean flag indicating whether the profile data is currently being fetched.
|
|
|
|
|
bool _isFetching = false;
|
|
|
|
|
|
|
|
|
|
/// A completer for the profile model of a user.
|
|
|
|
|
Completer<PUserModel?> _profileCompleter = Completer<PUserModel?>();
|
|
|
|
|
|
|
|
|
|
/// 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 {
|
|
|
|
|
if (_profileCompleter.isCompleted) return _profileCompleter.future;
|
|
|
|
|
if (_isFetching) {
|
|
|
|
|
await _profileCompleter.future;
|
|
|
|
|
return _profileCompleter.future;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_isFetching = true;
|
|
|
|
|
PUserModel? fetchedUserModel;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (_matrixAccessToken == null) {
|
|
|
|
|
throw Exception(
|
|
|
|
|
"calling fetchUserModel with matrixAccesstoken == null",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
fetchedUserModel = await _fetchUserModel();
|
|
|
|
|
} catch (err, s) {
|
|
|
|
|
ErrorHandler.logError(e: err, s: s);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo(
|
|
|
|
|
userID: userId!,
|
|
|
|
|
matrixAccessToken: _matrixAccessToken!,
|
|
|
|
|
);
|
|
|
|
|
newUserModel?.save(_pangeaController);
|
|
|
|
|
await migrateMatrixProfile();
|
|
|
|
|
_completeCompleter();
|
|
|
|
|
|
|
|
|
|
return newUserModel;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
debugPrint(
|
|
|
|
|
"User model not found. Probably first signup and needs Pangea account",
|
|
|
|
|
_isFetching = false;
|
|
|
|
|
_profileCompleter.complete(fetchedUserModel);
|
|
|
|
|
return _profileCompleter.future;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches the user model asynchronously.
|
|
|
|
|
///
|
|
|
|
|
/// This method fetches the user model by calling the [fetchPangeaUserInfo] method
|
|
|
|
|
/// from the [PUserRepo] class. It requires the [_matrixAccessToken] and [userId]
|
|
|
|
|
/// to be non-null. If either of them is null, an error is logged.
|
|
|
|
|
///
|
|
|
|
|
/// The fetched [newUserModel] is then saved locally.
|
|
|
|
|
/// The [migrateMatrixProfile] method is called, to migrate any information that is
|
|
|
|
|
/// already saved in the user's pangea profile but is not yet saved in the
|
|
|
|
|
/// user's matrix profile. Finally, the [newUserModel] is returned.
|
|
|
|
|
Future<PUserModel?> _fetchUserModel() async {
|
|
|
|
|
if (_matrixAccessToken == null || userId == null) {
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: "calling fetchUserModel with userId == null or matrixAccessToken == null",
|
|
|
|
|
);
|
|
|
|
|
rethrow;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo(
|
|
|
|
|
userID: userId!,
|
|
|
|
|
matrixAccessToken: _matrixAccessToken!,
|
|
|
|
|
);
|
|
|
|
|
newUserModel?.save(_pangeaController);
|
|
|
|
|
await migrateMatrixProfile();
|
|
|
|
|
return newUserModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dynamic migratedProfileInfo(MatrixProfile key) {
|
|
|
|
|
final dynamic localValue = _pangeaController.pStoreService.read(
|
|
|
|
|
key.title,
|
|
|
|
|
local: true,
|
|
|
|
|
);
|
|
|
|
|
final dynamic matrixValue = _pangeaController.pStoreService.read(
|
|
|
|
|
key.title,
|
|
|
|
|
);
|
|
|
|
|
return localValue != null && matrixValue != localValue ? localValue : null;
|
|
|
|
|
/// Reinitializes the user's profile
|
|
|
|
|
///
|
|
|
|
|
/// This method sets up the necessary variables and fetches the user model.
|
|
|
|
|
/// It completes the [_profileCompleter] with the fetched user model.
|
|
|
|
|
/// This method should be called whenever the user's login status changes
|
|
|
|
|
Future<void> reinitialize() async {
|
|
|
|
|
_profileCompleter = Completer<PUserModel?>();
|
|
|
|
|
_isFetching = false;
|
|
|
|
|
await fetchUserModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Migrates the user's profile from Pangea to Matrix.
|
|
|
|
|
///
|
|
|
|
|
/// This method retrieves the user's profile / local settings information from Pangea and checks for corresponding information stored in Matrix.
|
|
|
|
|
/// If any of the profile fields in Pangea have information, but the corresponding fields in Matrix are null, the values are updated in Matrix.
|
|
|
|
|
/// The profile fields that are checked for migration include date of birth, creation date, target language, source language, country, and public profile.
|
|
|
|
|
/// Additionally, several profile settings related to auto play, trial activation, interactive features, and instructional messages are also checked for migration.
|
|
|
|
|
///
|
|
|
|
|
/// This method calls the [updateMatrixProfile] method to update the user's profile in Matrix with the migrated values.
|
|
|
|
|
///
|
|
|
|
|
/// Note: This method assumes that the [userModel] and [_pangeaController] instances are properly initialized before calling this method.
|
|
|
|
|
Future<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.
|
|
|
|
|
final client = _pangeaController.matrixState.client;
|
|
|
|
|
if (client.prevBatch == null) {
|
|
|
|
|
await client.onSync.stream.first;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Map<String, dynamic> profileUpdates = {};
|
|
|
|
|
final Profile? pangeaProfile = userModel?.profile;
|
|
|
|
|
|
|
|
|
|
final String? pangeaDob = pangeaProfile?.dateOfBirth;
|
|
|
|
|
final String? matrixDob = _pangeaController.pStoreService.read(
|
|
|
|
|
ModelKey.userDateOfBirth,
|
|
|
|
|
);
|
|
|
|
|
final String? dob =
|
|
|
|
|
pangeaDob != null && matrixDob != pangeaDob ? pangeaDob : null;
|
|
|
|
|
for (final field in MatrixProfile.pangeaProfileFields) {
|
|
|
|
|
final dynamic matrixValue = MatrixProfile.getProfileData(field);
|
|
|
|
|
dynamic pangeaValue;
|
|
|
|
|
switch (field) {
|
|
|
|
|
case MatrixProfileEnum.dateOfBirth:
|
|
|
|
|
pangeaValue = pangeaProfile?.dateOfBirth;
|
|
|
|
|
break;
|
|
|
|
|
case MatrixProfileEnum.createdAt:
|
|
|
|
|
pangeaValue = pangeaProfile?.createdAt;
|
|
|
|
|
break;
|
|
|
|
|
case MatrixProfileEnum.targetLanguage:
|
|
|
|
|
pangeaValue = pangeaProfile?.targetLanguage;
|
|
|
|
|
break;
|
|
|
|
|
case MatrixProfileEnum.sourceLanguage:
|
|
|
|
|
pangeaValue = pangeaProfile?.sourceLanguage;
|
|
|
|
|
break;
|
|
|
|
|
case MatrixProfileEnum.country:
|
|
|
|
|
pangeaValue = pangeaProfile?.country;
|
|
|
|
|
break;
|
|
|
|
|
case MatrixProfileEnum.publicProfile:
|
|
|
|
|
pangeaValue = pangeaProfile?.publicProfile;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (pangeaValue != null && matrixValue == null) {
|
|
|
|
|
profileUpdates[field.title] = pangeaValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final pangeaCreatedAt = pangeaProfile?.createdAt;
|
|
|
|
|
final matrixCreatedAt = _pangeaController.pStoreService.read(
|
|
|
|
|
MatrixProfile.createdAt.title,
|
|
|
|
|
);
|
|
|
|
|
final String? createdAt =
|
|
|
|
|
pangeaCreatedAt != null && matrixCreatedAt != pangeaCreatedAt
|
|
|
|
|
? pangeaCreatedAt
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
final String? pangeaTargetLanguage = pangeaProfile?.targetLanguage;
|
|
|
|
|
final String? matrixTargetLanguage = _pangeaController.pStoreService.read(
|
|
|
|
|
MatrixProfile.targetLanguage.title,
|
|
|
|
|
);
|
|
|
|
|
final String? targetLanguage = pangeaTargetLanguage != null &&
|
|
|
|
|
matrixTargetLanguage != pangeaTargetLanguage
|
|
|
|
|
? pangeaTargetLanguage
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
final String? pangeaSourceLanguage = pangeaProfile?.sourceLanguage;
|
|
|
|
|
final String? matrixSourceLanguage = _pangeaController.pStoreService.read(
|
|
|
|
|
MatrixProfile.sourceLanguage.title,
|
|
|
|
|
);
|
|
|
|
|
final String? sourceLanguage = pangeaSourceLanguage != null &&
|
|
|
|
|
matrixSourceLanguage != pangeaSourceLanguage
|
|
|
|
|
? pangeaSourceLanguage
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
final String? pangeaCountry = pangeaProfile?.country;
|
|
|
|
|
final String? matrixCountry = _pangeaController.pStoreService.read(
|
|
|
|
|
MatrixProfile.country.title,
|
|
|
|
|
);
|
|
|
|
|
final String? country =
|
|
|
|
|
pangeaCountry != null && matrixCountry != pangeaCountry
|
|
|
|
|
? pangeaCountry
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
final bool? pangeaPublicProfile = pangeaProfile?.publicProfile;
|
|
|
|
|
final bool? matrixPublicProfile = _pangeaController.pStoreService.read(
|
|
|
|
|
MatrixProfile.publicProfile.title,
|
|
|
|
|
);
|
|
|
|
|
final bool? publicProfile = pangeaPublicProfile != null &&
|
|
|
|
|
matrixPublicProfile != pangeaPublicProfile
|
|
|
|
|
? pangeaPublicProfile
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
final bool? autoPlay = migratedProfileInfo(MatrixProfile.autoPlayMessages);
|
|
|
|
|
final bool? itAutoPlay = migratedProfileInfo(MatrixProfile.itAutoPlay);
|
|
|
|
|
final bool? trial = migratedProfileInfo(MatrixProfile.activatedFreeTrial);
|
|
|
|
|
final bool? interactiveTranslator =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.interactiveTranslator);
|
|
|
|
|
final bool? interactiveGrammar =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.interactiveGrammar);
|
|
|
|
|
final bool? immersionMode =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.immersionMode);
|
|
|
|
|
final bool? definitions = migratedProfileInfo(MatrixProfile.definitions);
|
|
|
|
|
// final bool? translations = migratedProfileInfo(MatrixProfile.translations);
|
|
|
|
|
final bool? showItInstructions =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.showedItInstructions);
|
|
|
|
|
final bool? showClickMessage =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.showedClickMessage);
|
|
|
|
|
final bool? showBlurMeansTranslate =
|
|
|
|
|
migratedProfileInfo(MatrixProfile.showedBlurMeansTranslate);
|
|
|
|
|
|
|
|
|
|
await updateMatrixProfile(
|
|
|
|
|
dateOfBirth: dob,
|
|
|
|
|
autoPlayMessages: autoPlay,
|
|
|
|
|
itAutoPlay: itAutoPlay,
|
|
|
|
|
activatedFreeTrial: trial,
|
|
|
|
|
interactiveTranslator: interactiveTranslator,
|
|
|
|
|
interactiveGrammar: interactiveGrammar,
|
|
|
|
|
immersionMode: immersionMode,
|
|
|
|
|
definitions: definitions,
|
|
|
|
|
// translations: translations,
|
|
|
|
|
showedItInstructions: showItInstructions,
|
|
|
|
|
showedClickMessage: showClickMessage,
|
|
|
|
|
showedBlurMeansTranslate: showBlurMeansTranslate,
|
|
|
|
|
createdAt: createdAt,
|
|
|
|
|
targetLanguage: targetLanguage,
|
|
|
|
|
sourceLanguage: sourceLanguage,
|
|
|
|
|
country: country,
|
|
|
|
|
publicProfile: publicProfile,
|
|
|
|
|
for (final value in MatrixProfileEnum.values) {
|
|
|
|
|
if (profileUpdates.containsKey(value.title)) continue;
|
|
|
|
|
final dynamic localValue =
|
|
|
|
|
_pangeaController.pStoreService.read(value.title);
|
|
|
|
|
final dynamic matrixValue = MatrixProfile.getProfileData(value);
|
|
|
|
|
final dynamic unmigratedValue =
|
|
|
|
|
localValue != null && matrixValue == null ? localValue : null;
|
|
|
|
|
if (unmigratedValue != null) {
|
|
|
|
|
profileUpdates[value.title] = unmigratedValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await MatrixProfile.saveProfileData(
|
|
|
|
|
profileUpdates,
|
|
|
|
|
waitForDataInSync: true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Updates the user's profile with the provided information.
|
|
|
|
|
///
|
|
|
|
|
/// The [dateOfBirth] parameter is the new date of birth for the user.
|
|
|
|
|
/// The [targetLanguage] parameter is the new target language for the user.
|
|
|
|
|
/// The [sourceLanguage] parameter is the new source language for the user.
|
|
|
|
|
/// The [country] parameter is the new country for the user.
|
|
|
|
|
/// The [interests] parameter is a list of new interests for the user.
|
|
|
|
|
/// The [speaks] parameter is a list of new languages the user speaks.
|
|
|
|
|
/// The [publicProfile] parameter indicates whether the user's profile should be public or not.
|
|
|
|
|
///
|
|
|
|
|
/// Throws an error if [userModel] or [accessToken] is null.
|
|
|
|
|
Future<void> updateUserProfile({
|
|
|
|
|
String? dateOfBirth,
|
|
|
|
|
String? targetLanguage,
|
|
|
|
|
@ -170,7 +228,14 @@ class UserController extends BaseController {
|
|
|
|
|
List<String>? speaks,
|
|
|
|
|
bool? publicProfile,
|
|
|
|
|
}) async {
|
|
|
|
|
if (userModel == null) throw Exception("Local userModel not defined");
|
|
|
|
|
final String? accessToken = await this.accessToken;
|
|
|
|
|
if (userModel == null || accessToken == null) {
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: "calling updateUserProfile with userModel == null or accessToken == null",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final profileJson = userModel!.profile!.toJson();
|
|
|
|
|
|
|
|
|
|
if (dateOfBirth != null) {
|
|
|
|
|
@ -194,230 +259,95 @@ class UserController extends BaseController {
|
|
|
|
|
if (publicProfile != null) {
|
|
|
|
|
profileJson[ModelKey.publicProfile] = publicProfile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Profile updatedUserProfile = await PUserRepo.updateUserProfile(
|
|
|
|
|
Profile.fromJson(profileJson),
|
|
|
|
|
await accessToken,
|
|
|
|
|
accessToken,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
PUserModel(
|
|
|
|
|
access: await accessToken,
|
|
|
|
|
access: accessToken,
|
|
|
|
|
refresh: userModel!.refresh,
|
|
|
|
|
profile: updatedUserProfile,
|
|
|
|
|
).save(_pangeaController);
|
|
|
|
|
|
|
|
|
|
await updateMatrixProfile(
|
|
|
|
|
dateOfBirth: dateOfBirth,
|
|
|
|
|
targetLanguage: targetLanguage,
|
|
|
|
|
sourceLanguage: sourceLanguage,
|
|
|
|
|
country: country,
|
|
|
|
|
publicProfile: publicProfile,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PUserModel? get userModel {
|
|
|
|
|
final data = _pangeaController.pStoreService.read(
|
|
|
|
|
PLocalKey.user,
|
|
|
|
|
local: true,
|
|
|
|
|
);
|
|
|
|
|
return data != null ? PUserModel.fromJson(data) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<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;
|
|
|
|
|
MatrixProfile.saveProfileData({
|
|
|
|
|
MatrixProfileEnum.dateOfBirth.title: dateOfBirth,
|
|
|
|
|
MatrixProfileEnum.targetLanguage.title: targetLanguage,
|
|
|
|
|
MatrixProfileEnum.sourceLanguage.title: sourceLanguage,
|
|
|
|
|
MatrixProfileEnum.country.title: country,
|
|
|
|
|
MatrixProfileEnum.publicProfile.title: publicProfile,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
|
|
|
|
|
/// It checks if the `userModel` has a non-null `access` token and if the token is expired using the `Jwt.isExpired()` method.
|
|
|
|
|
/// If the `userModel` is null or the `access` token is null, it returns true indicating that a new JWT is needed.
|
|
|
|
|
bool get needNewJWT =>
|
|
|
|
|
userModel?.access != null ? Jwt.isExpired(userModel!.access) : true;
|
|
|
|
|
|
|
|
|
|
Future<String> get accessToken async {
|
|
|
|
|
await (await completer).future;
|
|
|
|
|
// if userModel null or access token expired then fetchUserModel
|
|
|
|
|
/// Retrieves the access token for the user.
|
|
|
|
|
///
|
|
|
|
|
/// If the locally stored user model is null or the access token has
|
|
|
|
|
/// expired, it fetches the user model.
|
|
|
|
|
/// If the user model is still null after fetching, an error is logged.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the access token as a string, or null if the user model is null.
|
|
|
|
|
Future<String?> get accessToken async {
|
|
|
|
|
final PUserModel? useThisOne =
|
|
|
|
|
needNewJWT ? await fetchUserModel() : userModel;
|
|
|
|
|
|
|
|
|
|
if (useThisOne == null) {
|
|
|
|
|
//debugger(when: kDebugMode);
|
|
|
|
|
throw Exception("trying to get accessToken with userModel = null");
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: "trying to get accessToken with userModel = null",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return useThisOne.access;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String? get userId {
|
|
|
|
|
return _pangeaController.matrixState.client.userID;
|
|
|
|
|
return useThisOne?.access;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String get fullname {
|
|
|
|
|
final String? userID = userId;
|
|
|
|
|
if (userID == null) {
|
|
|
|
|
throw Exception('User ID not found');
|
|
|
|
|
/// Returns the full name of the user.
|
|
|
|
|
/// If the [userId] is null, an error will be logged and null will be returned.
|
|
|
|
|
/// The full name is obtained by extracting the substring before the first occurrence of ":" in the [userId]
|
|
|
|
|
/// and then replacing all occurrences of "@" with an empty string.
|
|
|
|
|
String? get fullname {
|
|
|
|
|
if (userId == null) {
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: "calling fullname with userId == null",
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return userID.substring(0, userID.indexOf(":")).replaceAll("@", "");
|
|
|
|
|
return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks if the user data is available.
|
|
|
|
|
/// Returns a [Future] that completes with a [bool] value
|
|
|
|
|
/// indicating whether the user data is available or not.
|
|
|
|
|
Future<bool> get isPUserDataAvailable async {
|
|
|
|
|
try {
|
|
|
|
|
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
|
|
|
|
|
return toCheck != null ? true : false;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return toCheck != null;
|
|
|
|
|
} catch (err, s) {
|
|
|
|
|
ErrorHandler.logError(e: err, s: s);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks if user data is available and the date of birth is set.
|
|
|
|
|
/// Returns a [Future] that completes with a [bool] value indicating
|
|
|
|
|
/// whether the user data is available and the date of birth is set.
|
|
|
|
|
Future<bool> get isUserDataAvailableAndDateOfBirthSet async {
|
|
|
|
|
try {
|
|
|
|
|
final client = _pangeaController.matrixState.client;
|
|
|
|
|
if (client.prevBatch == null) {
|
|
|
|
|
await client.onSync.stream.first;
|
|
|
|
|
}
|
|
|
|
|
// the function fetchUserModel() uses a completer, so it shouldn't
|
|
|
|
|
// re-call the endpoint if it has already been called
|
|
|
|
|
await fetchUserModel();
|
|
|
|
|
final localAccountData = _pangeaController.pStoreService.read(
|
|
|
|
|
ModelKey.userDateOfBirth,
|
|
|
|
|
);
|
|
|
|
|
return localAccountData != null;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return MatrixProfile.dateOfBirth != null;
|
|
|
|
|
} catch (err, s) {
|
|
|
|
|
ErrorHandler.logError(e: err, s: s);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a boolean value indicating whether the user is currently in the trial window.
|
|
|
|
|
bool get inTrialWindow {
|
|
|
|
|
final String? createdAt = userModel?.profile?.createdAt;
|
|
|
|
|
if (createdAt == null) {
|
|
|
|
|
@ -428,6 +358,14 @@ class UserController extends BaseController {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks if the user's languages are set.
|
|
|
|
|
/// Returns a [Future] that completes with a [bool] value
|
|
|
|
|
/// indicating whether the user's languages are set.
|
|
|
|
|
///
|
|
|
|
|
/// A user's languages are considered set if the source and target languages
|
|
|
|
|
/// are not null, not empty, and not equal to the [LanguageKeys.unknownLanguage] constant.
|
|
|
|
|
///
|
|
|
|
|
/// If an error occurs during the process, it logs the error and returns `false`.
|
|
|
|
|
Future<bool> get areUserLanguagesSet async {
|
|
|
|
|
try {
|
|
|
|
|
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
|
|
|
|
|
@ -442,18 +380,29 @@ class UserController extends BaseController {
|
|
|
|
|
tgtLang.isNotEmpty &&
|
|
|
|
|
srcLang != LanguageKeys.unknownLanguage &&
|
|
|
|
|
tgtLang != LanguageKeys.unknownLanguage;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
} catch (err, s) {
|
|
|
|
|
ErrorHandler.logError(e: err, s: s);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String? get _matrixAccessToken =>
|
|
|
|
|
_pangeaController.matrixState.client.accessToken;
|
|
|
|
|
|
|
|
|
|
/// Returns a boolean value indicating whether the user's profile is public.
|
|
|
|
|
bool get isPublic =>
|
|
|
|
|
_pangeaController.userController.userModel?.profile?.publicProfile ??
|
|
|
|
|
false;
|
|
|
|
|
|
|
|
|
|
/// Retrieves the user's email address.
|
|
|
|
|
///
|
|
|
|
|
/// This method fetches the user's email address by making a request to the
|
|
|
|
|
/// Matrix server. It uses the `_pangeaController` instance to access the
|
|
|
|
|
/// Matrix client and retrieve the account's third-party identifiers. It then
|
|
|
|
|
/// filters the identifiers to find the first one with the medium set to
|
|
|
|
|
/// `ThirdPartyIdentifierMedium.email`. Finally, it returns the email address
|
|
|
|
|
/// associated with the identifier, or `null` if no email address is found.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// - The user's email address as a [String], or `null` if no email address
|
|
|
|
|
/// is found.
|
|
|
|
|
Future<String?> get userEmail async {
|
|
|
|
|
final List<matrix.ThirdPartyIdentifier>? identifiers =
|
|
|
|
|
await _pangeaController.matrixState.client.getAccount3PIDs();
|
|
|
|
|
|