You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/models/user_model.dart

462 lines
16 KiB
Dart

import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:matrix/matrix.dart';
import 'language_model.dart';
/// The user's settings learning settings.
class UserSettings {
DateTime? dateOfBirth;
DateTime? createdAt;
bool autoPlayMessages;
bool itAutoPlay;
bool activatedFreeTrial;
bool publicProfile;
String? targetLanguage;
String? sourceLanguage;
String? country;
UserSettings({
this.dateOfBirth,
this.createdAt,
this.autoPlayMessages = false,
this.itAutoPlay = false,
this.activatedFreeTrial = false,
this.publicProfile = false,
this.targetLanguage,
this.sourceLanguage,
this.country,
});
factory UserSettings.fromJson(Map<String, dynamic> json) => UserSettings(
dateOfBirth: DateTime.parse(json[ModelKey.userDateOfBirth]),
createdAt: json[ModelKey.userCreatedAt] != null
? DateTime.parse(json[ModelKey.userCreatedAt])
: null,
autoPlayMessages: json[ModelKey.autoPlayMessages] ?? false,
itAutoPlay: json[ModelKey.itAutoPlay] ?? false,
activatedFreeTrial: json[ModelKey.activatedTrialKey] ?? false,
publicProfile: json[ModelKey.publicProfile] ?? false,
targetLanguage: json[ModelKey.l2LanguageKey],
sourceLanguage: json[ModelKey.l1LanguageKey],
country: json[ModelKey.userCountry],
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data[ModelKey.userDateOfBirth] = dateOfBirth?.toIso8601String();
data[ModelKey.userCreatedAt] = createdAt?.toIso8601String();
data[ModelKey.autoPlayMessages] = autoPlayMessages;
data[ModelKey.itAutoPlay] = itAutoPlay;
data[ModelKey.activatedTrialKey] = activatedFreeTrial;
data[ModelKey.publicProfile] = publicProfile;
data[ModelKey.l2LanguageKey] = targetLanguage;
data[ModelKey.l1LanguageKey] = sourceLanguage;
data[ModelKey.userCountry] = country;
return data;
}
static UserSettings? migrateFromAccountData() {
final accountData =
MatrixState.pangeaController.matrixState.client.accountData;
if (!accountData.containsKey(ModelKey.userDateOfBirth)) return null;
final dobContent = accountData[ModelKey.userDateOfBirth]!
.content[ModelKey.userDateOfBirth];
String? dobString;
if (dobContent != null) {
dobString = dobContent as String;
}
DateTime dob;
try {
dob = DateTime.parse(dobString!);
} catch (_) {
return null;
}
final createdAtContent =
accountData[ModelKey.userCreatedAt]?.content[ModelKey.userCreatedAt];
DateTime? createdAt;
if (createdAtContent != null) {
try {
createdAt = DateTime.parse(createdAtContent as String);
} catch (_) {
createdAt = null;
}
}
return UserSettings(
dateOfBirth: dob,
createdAt: createdAt,
autoPlayMessages: (accountData[ModelKey.autoPlayMessages]
?.content[ModelKey.autoPlayMessages] as bool?) ??
false,
itAutoPlay: (accountData[ModelKey.itAutoPlay]
?.content[ModelKey.itAutoPlay] as bool?) ??
false,
activatedFreeTrial: (accountData[ModelKey.activatedTrialKey]
?.content[ModelKey.activatedTrialKey] as bool?) ??
false,
publicProfile: (accountData[ModelKey.publicProfile]
?.content[ModelKey.publicProfile] as bool?) ??
false,
targetLanguage: accountData[ModelKey.l2LanguageKey]
?.content[ModelKey.l2LanguageKey] as String?,
sourceLanguage: accountData[ModelKey.l1LanguageKey]
?.content[ModelKey.l1LanguageKey] as String?,
country: accountData[ModelKey.userCountry]?.content[ModelKey.userCountry]
as String?,
);
}
}
/// The user's language tool settings.
class UserToolSettings {
bool interactiveTranslator;
bool interactiveGrammar;
bool immersionMode;
bool definitions;
bool autoIGC;
UserToolSettings({
this.interactiveTranslator = true,
this.interactiveGrammar = true,
this.immersionMode = false,
this.definitions = true,
this.autoIGC = true,
});
factory UserToolSettings.fromJson(Map<String, dynamic> json) =>
UserToolSettings(
interactiveTranslator:
json[ToolSetting.interactiveTranslator.toString()] ?? true,
interactiveGrammar:
json[ToolSetting.interactiveGrammar.toString()] ?? true,
immersionMode: json[ToolSetting.immersionMode.toString()] ?? false,
definitions: json[ToolSetting.definitions.toString()] ?? true,
autoIGC: json[ToolSetting.autoIGC.toString()] ?? true,
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data[ToolSetting.interactiveTranslator.toString()] = interactiveTranslator;
data[ToolSetting.interactiveGrammar.toString()] = interactiveGrammar;
data[ToolSetting.immersionMode.toString()] = immersionMode;
data[ToolSetting.definitions.toString()] = definitions;
data[ToolSetting.autoIGC.toString()] = autoIGC;
return data;
}
factory UserToolSettings.migrateFromAccountData() {
final accountData =
MatrixState.pangeaController.matrixState.client.accountData;
return UserToolSettings(
interactiveTranslator:
(accountData[ToolSetting.interactiveTranslator.toString()]
?.content[ToolSetting.interactiveTranslator.toString()]
as bool?) ??
true,
interactiveGrammar:
(accountData[ToolSetting.interactiveGrammar.toString()]
?.content[ToolSetting.interactiveGrammar.toString()]
as bool?) ??
true,
immersionMode: (accountData[ToolSetting.immersionMode.toString()]
?.content[ToolSetting.immersionMode.toString()] as bool?) ??
false,
definitions: (accountData[ToolSetting.definitions.toString()]
?.content[ToolSetting.definitions.toString()] as bool?) ??
true,
autoIGC: (accountData[ToolSetting.autoIGC.toString()]
?.content[ToolSetting.autoIGC.toString()] as bool?) ??
true,
);
}
}
/// The user's settings for whether or not to show instuction messages.
class UserInstructions {
bool showedItInstructions;
bool showedClickMessage;
bool showedBlurMeansTranslate;
bool showedTooltipInstructions;
bool showedSpeechToTextTooltip;
bool showedL1TranslationTooltip;
bool showedTranslationChoicesTooltip;
UserInstructions({
this.showedItInstructions = false,
this.showedClickMessage = false,
this.showedBlurMeansTranslate = false,
this.showedTooltipInstructions = false,
this.showedSpeechToTextTooltip = false,
this.showedL1TranslationTooltip = false,
this.showedTranslationChoicesTooltip = false,
});
factory UserInstructions.fromJson(Map<String, dynamic> json) =>
UserInstructions(
showedItInstructions:
json[InstructionsEnum.itInstructions.toString()] ?? false,
showedClickMessage:
json[InstructionsEnum.clickMessage.toString()] ?? false,
showedBlurMeansTranslate:
json[InstructionsEnum.blurMeansTranslate.toString()] ?? false,
showedTooltipInstructions:
json[InstructionsEnum.tooltipInstructions.toString()] ?? false,
showedL1TranslationTooltip:
json[InlineInstructions.l1Translation.toString()] ?? false,
showedTranslationChoicesTooltip:
json[InlineInstructions.translationChoices.toString()] ?? false,
showedSpeechToTextTooltip:
json[InlineInstructions.speechToText.toString()] ?? false,
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data[InstructionsEnum.itInstructions.toString()] = showedItInstructions;
data[InstructionsEnum.clickMessage.toString()] = showedClickMessage;
data[InstructionsEnum.blurMeansTranslate.toString()] =
showedBlurMeansTranslate;
data[InstructionsEnum.tooltipInstructions.toString()] =
showedTooltipInstructions;
data[InlineInstructions.l1Translation.toString()] =
showedL1TranslationTooltip;
data[InlineInstructions.translationChoices.toString()] =
showedTranslationChoicesTooltip;
data[InlineInstructions.speechToText.toString()] =
showedSpeechToTextTooltip;
return data;
}
factory UserInstructions.migrateFromAccountData() {
final accountData =
MatrixState.pangeaController.matrixState.client.accountData;
return UserInstructions(
showedItInstructions:
(accountData[InstructionsEnum.itInstructions.toString()]
?.content[InstructionsEnum.itInstructions.toString()]
as bool?) ??
false,
showedClickMessage: (accountData[InstructionsEnum.clickMessage.toString()]
?.content[InstructionsEnum.clickMessage.toString()] as bool?) ??
false,
showedBlurMeansTranslate:
(accountData[InstructionsEnum.blurMeansTranslate.toString()]
?.content[InstructionsEnum.blurMeansTranslate.toString()]
as bool?) ??
false,
showedTooltipInstructions:
(accountData[InstructionsEnum.tooltipInstructions.toString()]
?.content[InstructionsEnum.tooltipInstructions.toString()]
as bool?) ??
false,
showedL1TranslationTooltip:
(accountData[InlineInstructions.l1Translation.toString()]
?.content[InlineInstructions.l1Translation.toString()]
as bool?) ??
false,
showedTranslationChoicesTooltip: (accountData[
InlineInstructions.translationChoices.toString()]
?.content[InlineInstructions.translationChoices.toString()]
as bool?) ??
false,
showedSpeechToTextTooltip:
(accountData[InlineInstructions.speechToText.toString()]
?.content[InlineInstructions.speechToText.toString()]
as bool?) ??
false,
);
}
}
/// A wrapper around the matrix account data for the user profile.
/// Enables easy access to the profile data and saving new data.
class Profile {
late UserSettings userSettings;
late UserToolSettings toolSettings;
late UserInstructions instructionSettings;
Profile({
required this.userSettings,
UserToolSettings? toolSettings,
UserInstructions? instructionSettings,
}) {
this.toolSettings = toolSettings ?? UserToolSettings();
this.instructionSettings = instructionSettings ?? UserInstructions();
}
/// Load an instance of profile from the client's account data.
static Profile? fromAccountData() {
final profileData = MatrixState.pangeaController.matrixState.client
.accountData[ModelKey.userProfile]?.content;
if (profileData == null) return null;
final userSettingsContent = profileData[ModelKey.userSettings];
if (userSettingsContent == null) return null;
final toolSettingsContent = profileData[ModelKey.toolSettings];
final instructionSettingsContent =
profileData[ModelKey.instructionsSettings];
return Profile(
userSettings:
UserSettings.fromJson(userSettingsContent as Map<String, dynamic>),
toolSettings: toolSettingsContent != null
? UserToolSettings.fromJson(
toolSettingsContent as Map<String, dynamic>,
)
: UserToolSettings(),
instructionSettings: instructionSettingsContent != null
? UserInstructions.fromJson(
instructionSettingsContent as Map<String, dynamic>,
)
: UserInstructions(),
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> json = {
ModelKey.userSettings: userSettings.toJson(),
ModelKey.toolSettings: toolSettings.toJson(),
ModelKey.instructionsSettings: instructionSettings.toJson(),
};
return json;
}
/// Migrate data from the old matrix account data
/// format to the new matrix account data format.
static Profile? migrateFromAccountData() {
final userSettings = UserSettings.migrateFromAccountData();
if (userSettings == null) return null;
final toolSettings = UserToolSettings.migrateFromAccountData();
final instructionSettings = UserInstructions.migrateFromAccountData();
return Profile(
userSettings: userSettings,
toolSettings: toolSettings,
instructionSettings: instructionSettings,
);
}
/// Saves the current configuration of the profile to the client's account data.
/// If [waitForDataInSync] is true, the function will wait for the updated account
/// data to come through in a sync, indicating that it has been set on the matrix server.
Future<void> saveProfileData({
waitForDataInSync = false,
}) async {
final PangeaController pangeaController = MatrixState.pangeaController;
final Client client = pangeaController.matrixState.client;
final List<String> profileKeys = [
ModelKey.userSettings,
ModelKey.toolSettings,
ModelKey.instructionsSettings,
];
Future<SyncUpdate>? waitForUpdate;
if (waitForDataInSync) {
waitForUpdate = client.onSync.stream.firstWhere(
(sync) =>
sync.accountData != null &&
sync.accountData!.any(
(event) => event.content.keys.any((k) => profileKeys.contains(k)),
),
);
}
await client.setAccountData(
client.userID!,
ModelKey.userProfile,
toJson(),
);
if (waitForDataInSync) {
await waitForUpdate;
}
}
static Profile get emptyProfile {
return Profile(
userSettings: UserSettings(),
toolSettings: UserToolSettings(),
instructionSettings: UserInstructions(),
);
}
}
/// Model of data from pangea chat server. Not used anymore, in favor of matrix account data.
/// This class if used to read in data from the server to be migrated to matrix account data.
class PangeaProfile {
final String createdAt;
final String pangeaUserId;
String? dateOfBirth;
String? targetLanguage;
String? sourceLanguage;
String? country;
bool publicProfile;
PangeaProfile({
required this.createdAt,
required this.pangeaUserId,
this.dateOfBirth,
this.targetLanguage,
this.sourceLanguage,
this.country,
this.publicProfile = false,
});
factory PangeaProfile.fromJson(Map<String, dynamic> json) {
final l2 = LanguageModel.codeFromNameOrCode(
json[ModelKey.l2LanguageKey],
);
final l1 = LanguageModel.codeFromNameOrCode(
json[ModelKey.l1LanguageKey],
);
return PangeaProfile(
createdAt: json[ModelKey.userCreatedAt],
pangeaUserId: json[ModelKey.userPangeaUserId],
dateOfBirth: json[ModelKey.userDateOfBirth],
targetLanguage: l2,
sourceLanguage: l1,
publicProfile: json[ModelKey.publicProfile] ?? false,
country: json[ModelKey.userCountry],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data[ModelKey.userCreatedAt] = createdAt;
data[ModelKey.userPangeaUserId] = pangeaUserId;
data[ModelKey.userDateOfBirth] = dateOfBirth;
data[ModelKey.l2LanguageKey] = targetLanguage;
data[ModelKey.l1LanguageKey] = sourceLanguage;
data[ModelKey.publicProfile] = publicProfile;
data[ModelKey.userCountry] = country;
return data;
}
}
class PangeaProfileResponse {
final PangeaProfile profile;
final String access;
PangeaProfileResponse({
required this.profile,
required this.access,
});
factory PangeaProfileResponse.fromJson(Map<String, dynamic> json) {
return PangeaProfileResponse(
profile: PangeaProfile.fromJson(json[ModelKey.userProfile]),
access: json[ModelKey.userAccess],
);
}
}