Merge pull request #4781 from pangeachat/reduce-size-of-pangea-controller
initial work to remove speech to text controller from pangea controller
This commit is contained in:
commit
10413397fc
168 changed files with 2378 additions and 3467 deletions
|
|
@ -43,7 +43,7 @@ import 'package:fluffychat/pangea/course_creation/selected_course_page.dart';
|
|||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/find_your_people/find_your_people_constants.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/add_course_page.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/course_code_page.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/create_pangea_account_page.dart';
|
||||
|
|
@ -53,8 +53,8 @@ import 'package:fluffychat/pangea/login/pages/new_course_page.dart';
|
|||
import 'package:fluffychat/pangea/login/pages/public_courses_page.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/signup.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/join_with_link.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/join_with_link_page.dart';
|
||||
import 'package:fluffychat/pangea/subscription/pages/settings_subscription.dart';
|
||||
import 'package:fluffychat/widgets/config_viewer.dart';
|
||||
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/locale_provider.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/languages/locale_provider.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'config/setting_keys.dart';
|
||||
|
|
@ -45,6 +45,7 @@ void main() async {
|
|||
final List<Future> initFutures = [
|
||||
GetStorage.init(),
|
||||
GetStorage.init("subscription_storage"),
|
||||
GetStorage.init('class_storage'),
|
||||
];
|
||||
await Future.wait(initFutures);
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -57,11 +57,12 @@ import 'package:fluffychat/pangea/events/models/representation_content_model.dar
|
|||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/repo/language_mismatch_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dialog.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_service.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_mismatch_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/p_language_dialog.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/tokens_util.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/load_participants_builder.dart';
|
||||
import 'package:fluffychat/pangea/subscription/widgets/paywall_card.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_notification.dart';
|
||||
|
|
@ -531,7 +532,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
if (!mounted) return;
|
||||
pangeaController.languageController.showDialogOnEmptyLanguage(
|
||||
LanguageService.showDialogOnEmptyLanguage(
|
||||
context,
|
||||
() => () => setState(() {}),
|
||||
);
|
||||
|
|
@ -1152,8 +1153,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
'waveform': result.waveform,
|
||||
},
|
||||
// #Pangea
|
||||
'speaker_l1': pangeaController.languageController.activeL1Code(),
|
||||
'speaker_l2': pangeaController.languageController.activeL2Code(),
|
||||
'speaker_l1': pangeaController.userController.userL1Code,
|
||||
'speaker_l2': pangeaController.userController.userL2Code,
|
||||
// Pangea#
|
||||
},
|
||||
// #Pangea
|
||||
|
|
@ -1989,7 +1990,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
// Check if the user has set their languages. If not, prompt them to do so.
|
||||
if (!MatrixState.pangeaController.languageController.languagesSet) {
|
||||
if (!MatrixState.pangeaController.userController.languagesSet) {
|
||||
pLanguageDialog(context, () {});
|
||||
return;
|
||||
}
|
||||
|
|
@ -2145,10 +2146,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
ownMessage: true,
|
||||
);
|
||||
|
||||
final stt = await messageEvent.getSpeechToText(
|
||||
MatrixState.pangeaController.languageController.userL1?.langCodeShort ??
|
||||
final stt = await messageEvent.requestSpeechToText(
|
||||
MatrixState.pangeaController.userController.userL1?.langCodeShort ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
MatrixState.pangeaController.languageController.userL2?.langCodeShort ??
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
);
|
||||
if (stt.transcript.sttTokens.isEmpty) return;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/video_player.dart';
|
|||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
||||
|
|
|
|||
|
|
@ -401,11 +401,10 @@ class InputBar extends StatelessWidget {
|
|||
MatrixState.pangeaController.subscriptionController.subscriptionStatus;
|
||||
|
||||
String _defaultHintText(BuildContext context) {
|
||||
final lang = MatrixState.pangeaController.languageController;
|
||||
return lang.languagesSet
|
||||
return MatrixState.pangeaController.userController.languagesSet
|
||||
? L10n.of(context).writeAMessageLangCodes(
|
||||
lang.userL1!.displayName,
|
||||
lang.userL2!.displayName,
|
||||
MatrixState.pangeaController.userController.userL1!.displayName,
|
||||
MatrixState.pangeaController.userController.userL2!.displayName,
|
||||
)
|
||||
: L10n.of(context).writeAMessage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart
|
|||
import 'package:fluffychat/pangea/chat_settings/widgets/chat_context_menu_action.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_code_controller.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_code_repo.dart';
|
||||
import 'package:fluffychat/pangea/subscription/widgets/subscription_snackbar.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
|
|
@ -563,8 +565,10 @@ class ChatListController extends State<ChatList>
|
|||
_checkTorBrowser();
|
||||
|
||||
//#Pangea
|
||||
_invitedSpaceSubscription = MatrixState
|
||||
.pangeaController.matrixState.client.onSync.stream
|
||||
_invitedSpaceSubscription = Matrix.of(context)
|
||||
.client
|
||||
.onSync
|
||||
.stream
|
||||
.where((event) => event.rooms?.invite != null)
|
||||
.listen((event) async {
|
||||
for (final inviteEntry in event.rooms!.invite!.entries) {
|
||||
|
|
@ -582,15 +586,12 @@ class ChatListController extends State<ChatList>
|
|||
|
||||
if (isSpace) {
|
||||
final spaceId = inviteEntry.key;
|
||||
final space =
|
||||
MatrixState.pangeaController.matrixState.client.getRoomById(
|
||||
spaceId,
|
||||
);
|
||||
final space = Matrix.of(context).client.getRoomById(
|
||||
spaceId,
|
||||
);
|
||||
|
||||
final String? justInputtedCode =
|
||||
MatrixState.pangeaController.spaceCodeController.justInputtedCode;
|
||||
final newSpaceCode = space?.classCode;
|
||||
if (newSpaceCode?.toLowerCase() == justInputtedCode?.toLowerCase()) {
|
||||
if (space?.classCode?.toLowerCase() ==
|
||||
SpaceCodeRepo.recentCode?.toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -603,8 +604,8 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
|
||||
if (isAnalytics) {
|
||||
final analyticsRoom = MatrixState.pangeaController.matrixState.client
|
||||
.getRoomById(inviteEntry.key);
|
||||
final analyticsRoom =
|
||||
Matrix.of(context).client.getRoomById(inviteEntry.key);
|
||||
try {
|
||||
await analyticsRoom?.join();
|
||||
} catch (err, s) {
|
||||
|
|
@ -626,7 +627,7 @@ class ChatListController extends State<ChatList>
|
|||
// listen for space child updates for any space that is not the active space
|
||||
// so that when the user navigates to the space that was updated, it will
|
||||
// reload any rooms that have been added / removed
|
||||
final client = MatrixState.pangeaController.matrixState.client;
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
// listen for room join events and leave room if over capacity
|
||||
_roomCapacitySubscription ??= client.onSync.stream
|
||||
|
|
@ -700,8 +701,7 @@ class ChatListController extends State<ChatList>
|
|||
_roomCapacitySubscription?.cancel();
|
||||
MatrixState.pangeaController.subscriptionController.subscriptionNotifier
|
||||
.removeListener(_onSubscribe);
|
||||
MatrixState.pangeaController.spaceCodeController.codeNotifier
|
||||
.removeListener(_onCacheSpaceCode);
|
||||
SpaceCodeController.codeNotifier.removeListener(_onCacheSpaceCode);
|
||||
//Pangea#
|
||||
scrollController.removeListener(_onScroll);
|
||||
super.dispose();
|
||||
|
|
@ -1112,18 +1112,14 @@ class ChatListController extends State<ChatList>
|
|||
void _initPangeaControllers(Client client) {
|
||||
MatrixState.pangeaController.initControllers();
|
||||
if (mounted) {
|
||||
MatrixState.pangeaController.spaceCodeController
|
||||
.joinCachedSpaceCode(context);
|
||||
MatrixState.pangeaController.spaceCodeController.codeNotifier
|
||||
.addListener(_onCacheSpaceCode);
|
||||
SpaceCodeController.joinCachedSpaceCode(context);
|
||||
SpaceCodeController.codeNotifier.addListener(_onCacheSpaceCode);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCacheSpaceCode() {
|
||||
if (!mounted) return;
|
||||
MatrixState.pangeaController.spaceCodeController.joinCachedSpaceCode(
|
||||
context,
|
||||
);
|
||||
SpaceCodeController.joinCachedSpaceCode(context);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/login_options_view.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/pangea_login_view.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/p_sso_button.dart';
|
||||
import 'package:fluffychat/pangea/user/utils/p_login.dart';
|
||||
import 'package:fluffychat/pangea/user/p_login.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
|
|
@ -46,7 +46,6 @@ class LoginController extends State<Login> {
|
|||
bool loadingAppleSSO = false;
|
||||
bool loadingGoogleSSO = false;
|
||||
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
Client? client;
|
||||
|
|
@ -61,7 +60,7 @@ class LoginController extends State<Login> {
|
|||
// TODO: implement initState
|
||||
super.initState();
|
||||
loadingSignIn = true;
|
||||
pangeaController.checkHomeServerAction().then((client) {
|
||||
checkHomeServerAction().then((client) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loadingSignIn = false;
|
||||
|
|
@ -234,6 +233,30 @@ class LoginController extends State<Login> {
|
|||
// if (mounted) setState(() {});
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<Client> checkHomeServerAction() async {
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
if (client.homeserver != null) {
|
||||
await Future.delayed(Duration.zero);
|
||||
return client;
|
||||
}
|
||||
|
||||
final String homeServer =
|
||||
AppConfig.defaultHomeserver.trim().toLowerCase().replaceAll(' ', '-');
|
||||
var homeserver = Uri.parse(homeServer);
|
||||
if (homeserver.scheme.isEmpty) {
|
||||
homeserver = Uri.https(homeServer, '');
|
||||
}
|
||||
|
||||
try {
|
||||
await client.register();
|
||||
Matrix.of(context).loginRegistrationSupported = true;
|
||||
} on MatrixException catch (e) {
|
||||
Matrix.of(context).loginRegistrationSupported =
|
||||
e.requireAdditionalAuthentication;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void passwordForgotten() async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:image_picker/image_picker.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/user/utils/p_logout.dart';
|
||||
import 'package:fluffychat/pangea/user/p_logout.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@
|
|||
// super.initState();
|
||||
|
||||
// selectedLanguageOfInstructions =
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
// MatrixState.pangeaController.userController.userL1?.langCode;
|
||||
// selectedTargetLanguage =
|
||||
// MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
// MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
// selectedCefrLevel = LanguageLevelTypeEnum.a1;
|
||||
// selectedNumberOfParticipants = 3;
|
||||
// _setMode();
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
|
||||
// ActivitySettingRequestSchema get req => ActivitySettingRequestSchema(
|
||||
// langCode:
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
// MatrixState.pangeaController.userController.userL1?.langCode ??
|
||||
// LanguageKeys.defaultLanguage,
|
||||
// );
|
||||
|
||||
|
|
@ -139,9 +139,9 @@
|
|||
// modeController.clear();
|
||||
// selectedMedia = MediaEnum.nan;
|
||||
// selectedLanguageOfInstructions =
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
// MatrixState.pangeaController.userController.userL1?.langCode;
|
||||
// selectedTargetLanguage =
|
||||
// MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
// MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
// selectedCefrLevel = LanguageLevelTypeEnum.a1;
|
||||
// selectedNumberOfParticipants = 3;
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
// controller.selectedLanguageOfInstructions!,
|
||||
// )
|
||||
// : MatrixState
|
||||
// .pangeaController.languageController.userL1,
|
||||
// .pangeaController.userController.userL1,
|
||||
// isL2List: false,
|
||||
// decorationText:
|
||||
// L10n.of(context).languageOfInstructionsLabel,
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
// controller.selectedTargetLanguage!,
|
||||
// )
|
||||
// : MatrixState
|
||||
// .pangeaController.languageController.userL2,
|
||||
// .pangeaController.userController.userL2,
|
||||
// decorationText: L10n.of(context).targetLanguageLabel,
|
||||
// isL2List: true,
|
||||
// ),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:fluffychat/pangea/activity_generator/media_enum.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
||||
class ActivityPlanRequest {
|
||||
final String topic;
|
||||
|
|
@ -49,7 +49,7 @@ class ActivityPlanRequest {
|
|||
objective: json[ModelKey.activityRequestObjective],
|
||||
media: MediaEnum.nan.fromString(json[ModelKey.activityRequestMedia]),
|
||||
cefrLevel: json[ModelKey.activityRequestCefrLevel] != null
|
||||
? LanguageLevelTypeEnumExtension.fromString(
|
||||
? LanguageLevelTypeEnum.fromString(
|
||||
json[ModelKey.activityRequestCefrLevel],
|
||||
)
|
||||
: LanguageLevelTypeEnum.a1,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_indicator.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/load_participants_builder.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/repo/language_mismatch_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_mismatch_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
extension ActivityMenuLogic on ChatController {
|
||||
|
|
@ -43,9 +43,9 @@ extension ActivityMenuLogic on ChatController {
|
|||
}
|
||||
|
||||
final l1 =
|
||||
MatrixState.pangeaController.languageController.userL1?.langCodeShort;
|
||||
MatrixState.pangeaController.userController.userL1?.langCodeShort;
|
||||
final l2 =
|
||||
MatrixState.pangeaController.languageController.userL2?.langCodeShort;
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort;
|
||||
final activityLang = room.activityPlan?.req.targetLanguage.split('-').first;
|
||||
|
||||
return activityLang != null &&
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
|
|||
final activitiesResponse = await CourseActivityRepo.get(
|
||||
TranslateActivityRequest(
|
||||
activityIds: [widget.activityId],
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
widget.activityId,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -106,10 +106,10 @@ class ActivitySessionStartView extends StatelessWidget {
|
|||
activityId: controller.widget.activityId,
|
||||
feedbackText: feedback,
|
||||
userId: Matrix.of(context).client.userID!,
|
||||
userL1: MatrixState.pangeaController.languageController
|
||||
.activeL1Code()!,
|
||||
userL2: MatrixState.pangeaController.languageController
|
||||
.activeL2Code()!,
|
||||
userL1: MatrixState
|
||||
.pangeaController.userController.userL1Code!,
|
||||
userL2: MatrixState
|
||||
.pangeaController.userController.userL2Code!,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -120,8 +120,7 @@ class ActivitySessionStartView extends StatelessWidget {
|
|||
|
||||
CourseActivityRepo.setSentFeedback(
|
||||
controller.widget.activityId,
|
||||
MatrixState.pangeaController.languageController
|
||||
.activeL1Code()!,
|
||||
MatrixState.pangeaController.userController.userL1Code!,
|
||||
);
|
||||
|
||||
await showDialog(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
|||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
|
||||
class ActivitySummary extends StatelessWidget {
|
||||
final ActivityPlanModel activity;
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@
|
|||
// double get cardWidth => _isColumnMode ? 225.0 : 150.0;
|
||||
|
||||
// String get instructionLanguage =>
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
// MatrixState.pangeaController.userController.userL1?.langCode ??
|
||||
// LanguageKeys.defaultLanguage;
|
||||
// String get targetLanguage =>
|
||||
// MatrixState.pangeaController.languageController.userL2?.langCode ??
|
||||
// MatrixState.pangeaController.userController.userL2?.langCode ??
|
||||
// LanguageKeys.defaultLanguage;
|
||||
|
||||
// ActivityPlanRequest get _request {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class ActivitySummaryAnalyticsModel {
|
|||
}
|
||||
|
||||
void addMessageConstructs(PangeaMessageEvent event) {
|
||||
final uses = event.originalSent?.vocabAndMorphUses();
|
||||
final uses = event.originalSent?.vocabAndMorphUses;
|
||||
if (uses == null || uses.isEmpty) return;
|
||||
addConstructs(event.senderId, uses);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {"l2": MatrixState.pangeaController.languageController.userL2},
|
||||
data: {"l2": MatrixState.pangeaController.userController.userL2},
|
||||
);
|
||||
} finally {
|
||||
features.sort(
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class VocabDetailsView extends StatelessWidget {
|
|||
|
||||
/// Get the language code for the current lemma
|
||||
String? get _userL2 =>
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
|
||||
List<String> get forms =>
|
||||
MatrixState.pangeaController.getAnalytics.constructListModel
|
||||
|
|
@ -69,14 +69,13 @@ class VocabDetailsView extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (MatrixState
|
||||
.pangeaController.languageController.showTranscription)
|
||||
if (MatrixState.pangeaController.userController.showTranscription)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: PhoneticTranscriptionWidget(
|
||||
text: _construct.lemma,
|
||||
textLanguage:
|
||||
MatrixState.pangeaController.languageController.userL2!,
|
||||
MatrixState.pangeaController.userController.userL2!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: textColor.withAlpha((0.7 * 255).toInt()),
|
||||
fontSize: 18,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
return;
|
||||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
try {
|
||||
if (_downloadType == DownloadType.csv) {
|
||||
final vocabContent = _getCSVFileContent(
|
||||
|
|
@ -96,10 +97,10 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
);
|
||||
|
||||
final vocabFileName =
|
||||
"analytics_vocab_${MatrixState.pangeaController.matrixState.client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.csv";
|
||||
"analytics_vocab_${client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.csv";
|
||||
|
||||
final morphFileName =
|
||||
"analytics_morph_${MatrixState.pangeaController.matrixState.client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.csv";
|
||||
"analytics_morph_${client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.csv";
|
||||
|
||||
final futures = [
|
||||
DownloadUtil.downloadFile(
|
||||
|
|
@ -122,7 +123,7 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
});
|
||||
|
||||
final fileName =
|
||||
"analytics_${MatrixState.pangeaController.matrixState.client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.xlsx'}";
|
||||
"analytics_${client.userID?.localpart}_${DateFormat('yyyy-MM-dd-hh:mm:ss').format(DateTime.now())}.xlsx'}";
|
||||
|
||||
await DownloadUtil.downloadFile(
|
||||
content,
|
||||
|
|
@ -263,8 +264,8 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
final List<PangeaMessageEvent> examples = [];
|
||||
for (final OneConstructUse use in allUses) {
|
||||
if (use.metadata.roomId == null) continue;
|
||||
final Room? room = MatrixState.pangeaController.matrixState.client
|
||||
.getRoomById(use.metadata.roomId!);
|
||||
final client = Matrix.of(context).client;
|
||||
final Room? room = client.getRoomById(use.metadata.roomId!);
|
||||
if (room == null) continue;
|
||||
|
||||
if (use.useType.skillsEnumType != LearningSkillsEnum.writing ||
|
||||
|
|
@ -287,12 +288,11 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
|
||||
final Event? event = await room.getEventById(use.metadata.eventId!);
|
||||
|
||||
if (event == null || event.senderId != room.client.userID) continue;
|
||||
if (event == null || event.senderId != client.userID) continue;
|
||||
final PangeaMessageEvent pangeaMessageEvent = PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timeline!,
|
||||
ownMessage: event.senderId ==
|
||||
MatrixState.pangeaController.matrixState.client.userID,
|
||||
ownMessage: event.senderId == client.userID,
|
||||
);
|
||||
examples.add(pangeaMessageEvent);
|
||||
if (examples.length >= 5) break;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
extension AnalyticsClientExtension on Client {
|
||||
|
|
@ -27,7 +27,7 @@ extension AnalyticsClientExtension on Client {
|
|||
/// optional userId (if not specified, uses current user).
|
||||
/// If user is invited to the room, joins the room.
|
||||
Room? analyticsRoomLocal([LanguageModel? lang, String? userIdParam]) {
|
||||
lang ??= MatrixState.pangeaController.languageController.userL2;
|
||||
lang ??= MatrixState.pangeaController.userController.userL2;
|
||||
|
||||
if (lang == null) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import 'package:fluffychat/pangea/constructs/construct_repo.dart';
|
|||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ class GetAnalyticsController extends BaseController {
|
|||
_pangeaController = pangeaController;
|
||||
}
|
||||
|
||||
LanguageModel? get _l1 => _pangeaController.languageController.userL1;
|
||||
LanguageModel? get _l2 => _pangeaController.languageController.userL2;
|
||||
LanguageModel? get _l1 => _pangeaController.userController.userL1;
|
||||
LanguageModel? get _l2 => _pangeaController.userController.userL2;
|
||||
|
||||
Client get _client => _pangeaController.matrixState.client;
|
||||
|
||||
|
|
@ -383,8 +383,8 @@ class GetAnalyticsController extends BaseController {
|
|||
// this function gets called soon after login, so first
|
||||
// make sure that the user's l2 is loaded, if the user has set their l2
|
||||
if (_client.userID != null && _l2 == null) {
|
||||
if (_pangeaController.matrixState.client.prevBatch == null) {
|
||||
await _pangeaController.matrixState.client.onSync.stream.first;
|
||||
if (_client.prevBatch == null) {
|
||||
await _client.onSync.stream.first;
|
||||
}
|
||||
if (_l2 == null) return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ class LevelUpBannerState extends State<LevelUpBanner>
|
|||
_loadConstructSummary();
|
||||
|
||||
LevelUpManager.instance.preloadAnalytics(
|
||||
context,
|
||||
widget.level,
|
||||
widget.prevLevel,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LevelUpManager {
|
||||
|
|
@ -27,7 +25,6 @@ class LevelUpManager {
|
|||
bool shouldAutoPopup = false;
|
||||
|
||||
Future<void> preloadAnalytics(
|
||||
BuildContext context,
|
||||
int level,
|
||||
int prevLevel,
|
||||
) async {
|
||||
|
|
@ -43,7 +40,7 @@ class LevelUpManager {
|
|||
.numConstructs(ConstructTypeEnum.vocab);
|
||||
|
||||
final LanguageModel? l2 =
|
||||
MatrixState.pangeaController.languageController.userL2;
|
||||
MatrixState.pangeaController.userController.userL2;
|
||||
final Room? analyticsRoom =
|
||||
MatrixState.pangeaController.matrixState.client.analyticsRoomLocal(l2!);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import 'package:fluffychat/pangea/analytics_summary/progress_bar/progress_bar_de
|
|||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
|
@ -107,10 +107,9 @@ class _LevelUpPopupContentState extends State<LevelUpPopupContent>
|
|||
Uri? avatarUrl;
|
||||
final bool _hasBlastedConfetti = false;
|
||||
|
||||
String language = MatrixState.pangeaController.languageController
|
||||
.activeL2Code()
|
||||
?.toUpperCase() ??
|
||||
LanguageKeys.unknownLanguage;
|
||||
String language =
|
||||
MatrixState.pangeaController.userController.userL2Code?.toUpperCase() ??
|
||||
LanguageKeys.unknownLanguage;
|
||||
|
||||
ConstructSummary? _constructSummary;
|
||||
Object? _error;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
|||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/user/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum AnalyticsUpdateType { server, local }
|
||||
|
|
@ -75,7 +75,7 @@ class PutAnalyticsController {
|
|||
/// If analytics haven't been updated in the last day, update them
|
||||
Future<void> _refreshAnalyticsIfOutdated() async {
|
||||
// don't set anything is the user is not logged in
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (_client.userID == null) return;
|
||||
try {
|
||||
// if lastUpdated hasn't been set yet, set it
|
||||
lastUpdated ??=
|
||||
|
|
@ -221,7 +221,7 @@ class PutAnalyticsController {
|
|||
onLogout = false,
|
||||
LanguageModel? l2Override,
|
||||
}) async {
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (_client.userID == null) return;
|
||||
if (_pangeaController.getAnalytics.messagesSinceUpdate.isEmpty) return;
|
||||
|
||||
if (!(_updateCompleter?.isCompleted ?? true)) {
|
||||
|
|
@ -266,7 +266,7 @@ class PutAnalyticsController {
|
|||
if (cachedConstructs.isEmpty || onlyDraft) return;
|
||||
|
||||
// if missing important info, don't send analytics. Could happen if user just signed up.
|
||||
final l2 = l2Override ?? _pangeaController.languageController.userL2;
|
||||
final l2 = l2Override ?? _pangeaController.userController.userL2;
|
||||
if (l2 == null || _client.userID == null) return;
|
||||
|
||||
// analytics room for the user and current target language
|
||||
|
|
@ -279,11 +279,11 @@ class PutAnalyticsController {
|
|||
}
|
||||
|
||||
Future<void> sendActivityAnalytics(String roomId) async {
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (_pangeaController.languageController.userL2 == null) return;
|
||||
if (_client.userID == null) return;
|
||||
if (_pangeaController.userController.userL2 == null) return;
|
||||
|
||||
final Room? analyticsRoom = await _client.getMyAnalyticsRoom(
|
||||
_pangeaController.languageController.userL2!,
|
||||
_pangeaController.userController.userL2!,
|
||||
);
|
||||
if (analyticsRoom == null) return;
|
||||
await analyticsRoom.addActivityRoomId(roomId);
|
||||
|
|
@ -292,10 +292,10 @@ class PutAnalyticsController {
|
|||
|
||||
Future<void> blockConstruct(ConstructIdentifier constructId) async {
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (_pangeaController.languageController.userL2 == null) return;
|
||||
if (_pangeaController.userController.userL2 == null) return;
|
||||
|
||||
final Room? analyticsRoom = await _client.getMyAnalyticsRoom(
|
||||
_pangeaController.languageController.userL2!,
|
||||
_pangeaController.userController.userL2!,
|
||||
);
|
||||
if (analyticsRoom == null) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import 'package:fluffychat/pangea/analytics_summary/learning_progress_bar.dart';
|
|||
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicator_button.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/progress_indicator.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -98,8 +98,8 @@ class LearningProgressIndicatorsState
|
|||
return const SizedBox();
|
||||
}
|
||||
|
||||
final userL1 = MatrixState.pangeaController.languageController.userL1;
|
||||
final userL2 = MatrixState.pangeaController.languageController.userL2;
|
||||
final userL1 = MatrixState.pangeaController.userController.userL1;
|
||||
final userL2 = MatrixState.pangeaController.userController.userL2;
|
||||
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/choreographer_send_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer_state_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -24,9 +24,9 @@ class PangeaChatInputRow extends StatelessWidget {
|
|||
});
|
||||
|
||||
LanguageModel? get activel1 =>
|
||||
controller.pangeaController.languageController.activeL1Model();
|
||||
controller.pangeaController.userController.userL1;
|
||||
LanguageModel? get activel2 =>
|
||||
controller.pangeaController.languageController.activeL2Model();
|
||||
controller.pangeaController.userController.userL2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_code_repo.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -116,8 +117,7 @@ void chatListHandleSpaceTap(
|
|||
(element) =>
|
||||
element.isSpace && element.membership == Membership.join,
|
||||
);
|
||||
final justInputtedCode =
|
||||
MatrixState.pangeaController.spaceCodeController.justInputtedCode;
|
||||
final justInputtedCode = SpaceCodeRepo.recentCode;
|
||||
if (rooms.any((s) => s.spaceChildren.any((c) => c.roomId == space.id))) {
|
||||
autoJoin(space);
|
||||
} else if (justInputtedCode != null &&
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ChatListItemSubtitle extends StatelessWidget {
|
|||
});
|
||||
|
||||
bool _showPangeaContent(Event event) {
|
||||
return MatrixState.pangeaController.languageController.languagesSet &&
|
||||
return MatrixState.pangeaController.userController.languagesSet &&
|
||||
!event.redacted &&
|
||||
event.type == EventTypes.Message &&
|
||||
event.messageType == MessageTypes.Text &&
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
||||
class BotOptionsModel {
|
||||
LanguageLevelTypeEnum languageLevel;
|
||||
|
|
@ -63,9 +63,9 @@ class BotOptionsModel {
|
|||
// General Bot Options
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
languageLevel: json[ModelKey.languageLevel] is int
|
||||
? LanguageLevelTypeEnumExtension.fromInt(json[ModelKey.languageLevel])
|
||||
? LanguageLevelTypeEnum.fromInt(json[ModelKey.languageLevel])
|
||||
: json[ModelKey.languageLevel] is String
|
||||
? LanguageLevelTypeEnumExtension.fromString(
|
||||
? LanguageLevelTypeEnum.fromString(
|
||||
json[ModelKey.languageLevel],
|
||||
)
|
||||
: LanguageLevelTypeEnum.a1,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selectio
|
|||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/map_clipper.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_display_name.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/load_participants_builder.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ extension BotClientExtension on Client {
|
|||
StateEvent(
|
||||
content: BotOptionsModel(
|
||||
mode: BotMode.directChat,
|
||||
targetLanguage: MatrixState
|
||||
.pangeaController.languageController.userL2?.langCode,
|
||||
targetLanguage:
|
||||
MatrixState.pangeaController.userController.userL2?.langCode,
|
||||
languageLevel: MatrixState.pangeaController.userController.profile
|
||||
.userSettings.cefrLevel,
|
||||
).toJson(),
|
||||
|
|
@ -53,4 +53,23 @@ extension BotClientExtension on Client {
|
|||
),
|
||||
],
|
||||
);
|
||||
|
||||
Future<void> updateBotOptions() async {
|
||||
if (!isLogged() || botDM == null) return;
|
||||
|
||||
final targetLanguage =
|
||||
MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
final cefrLevel = MatrixState
|
||||
.pangeaController.userController.profile.userSettings.cefrLevel;
|
||||
final updateBotOptions = botDM!.botOptions ?? BotOptionsModel();
|
||||
|
||||
if (updateBotOptions.targetLanguage == targetLanguage &&
|
||||
updateBotOptions.languageLevel == cefrLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateBotOptions.targetLanguage = targetLanguage;
|
||||
updateBotOptions.languageLevel = cefrLevel;
|
||||
await botDM!.setBotOptions(updateBotOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:dropdown_button2/dropdown_button2.dart';
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
||||
class LanguageLevelDropdown extends StatelessWidget {
|
||||
final LanguageLevelTypeEnum? initialLevel;
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart'
|
|||
import 'package:fluffychat/pangea/choreographer/pangea_message_content_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/text_editing/edit_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'choreographer_error_controller.dart';
|
||||
|
|
@ -223,8 +223,8 @@ class Choreographer extends ChangeNotifier {
|
|||
MatrixState.pangeaController.subscriptionController.subscriptionStatus;
|
||||
|
||||
if (canSendStatus != SubscriptionStatus.subscribed ||
|
||||
MatrixState.pangeaController.languageController.userL2 == null ||
|
||||
MatrixState.pangeaController.languageController.userL1 == null ||
|
||||
MatrixState.pangeaController.userController.userL2 == null ||
|
||||
MatrixState.pangeaController.userController.userL1 == null ||
|
||||
(!ToolSetting.interactiveGrammar.enabled &&
|
||||
!ToolSetting.interactiveTranslator.enabled) ||
|
||||
(!ToolSetting.autoIGC.enabled &&
|
||||
|
|
@ -261,19 +261,18 @@ class Choreographer extends ChangeNotifier {
|
|||
Future<PangeaMessageContentModel> getMessageContent(String message) async {
|
||||
TokensResponseModel? tokensResp;
|
||||
final l2LangCode =
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
final l1LangCode =
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
MatrixState.pangeaController.userController.userL1?.langCode;
|
||||
if (l1LangCode != null && l2LangCode != null) {
|
||||
final res = await MessageDataController.getTokens(
|
||||
repEventId: null,
|
||||
room: null,
|
||||
req: TokensRequestModel(
|
||||
final res = await TokensRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
TokensRequestModel(
|
||||
fullText: message,
|
||||
senderL1: l1LangCode,
|
||||
senderL2: l2LangCode,
|
||||
),
|
||||
).timeout(const Duration(seconds: 10));
|
||||
);
|
||||
tokensResp = res.isValue ? res.result : null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,17 +68,17 @@ class IgcController {
|
|||
) =>
|
||||
IGCRequestModel(
|
||||
fullText: text,
|
||||
userId: MatrixState.pangeaController.userController.userId!,
|
||||
userL1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
userL2: MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
userId: MatrixState.pangeaController.userController.client.userID!,
|
||||
userL1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
userL2: MatrixState.pangeaController.userController.userL2Code!,
|
||||
enableIGC: true,
|
||||
enableIT: true,
|
||||
prevMessages: prevMessages,
|
||||
);
|
||||
|
||||
SpanDetailsRequest _spanDetailsRequest(SpanData span) => SpanDetailsRequest(
|
||||
userL1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
userL2: MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
userL1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
userL2: MatrixState.pangeaController.userController.userL2Code!,
|
||||
enableIGC: true,
|
||||
enableIT: true,
|
||||
span: span,
|
||||
|
|
|
|||
|
|
@ -177,8 +177,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
widget.match.updatedMatch.match.selectedChoiceIndex,
|
||||
id: widget.match.hashCode.toString(),
|
||||
langCode: MatrixState
|
||||
.pangeaController.languageController
|
||||
.activeL2Code(),
|
||||
.pangeaController.userController.userL2Code!,
|
||||
),
|
||||
_SpanCardFeedback(
|
||||
_selectedChoice != null,
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class SpanData {
|
|||
final errorSpan = fullText.characters.skip(offset).take(length).toString();
|
||||
|
||||
final l2Code =
|
||||
MatrixState.pangeaController.languageController.userL2?.langCodeShort;
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort;
|
||||
|
||||
return correctChoice != null &&
|
||||
l2Code != null &&
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ extension SpanDataTypeEnumExt on SpanDataTypeEnum {
|
|||
return L10n.of(context).correctionDefaultPrompt;
|
||||
case SpanDataTypeEnum.itStart:
|
||||
return L10n.of(context).needsItMessage(
|
||||
MatrixState.pangeaController.languageController.userL2
|
||||
MatrixState.pangeaController.userController.userL2
|
||||
?.getDisplayName(context) ??
|
||||
L10n.of(context).targetLanguage,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer_state_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
|
||||
class StartIGCButton extends StatefulWidget {
|
||||
final VoidCallback onPressed;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import 'package:fluffychat/pangea/choreographer/it/it_feedback_card.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/it/word_data_card.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../common/utils/overlay.dart';
|
||||
|
|
@ -62,12 +62,9 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
FullTextTranslationRequestModel _translationRequest(String text) =>
|
||||
FullTextTranslationRequestModel(
|
||||
text: text,
|
||||
tgtLang:
|
||||
MatrixState.pangeaController.languageController.userL1!.langCode,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.userL1!.langCode,
|
||||
userL2:
|
||||
MatrixState.pangeaController.languageController.userL2!.langCode,
|
||||
tgtLang: MatrixState.pangeaController.userController.userL1!.langCode,
|
||||
userL1: MatrixState.pangeaController.userController.userL1!.langCode,
|
||||
userL2: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
);
|
||||
|
||||
void _openListener() {
|
||||
|
|
@ -105,8 +102,8 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
cardToShow: selected
|
||||
? WordDataCard(
|
||||
word: text,
|
||||
langCode: MatrixState
|
||||
.pangeaController.languageController.userL2!.langCode,
|
||||
langCode:
|
||||
MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
fullText: _sourceText.value ?? widget.choreographer.currentText,
|
||||
)
|
||||
: ITFeedbackCard(_translationRequest(text)),
|
||||
|
|
@ -394,7 +391,7 @@ class _ITChoices extends StatelessWidget {
|
|||
onPressed: (value, index) => onPressed(index),
|
||||
onLongPress: (value, index) => onLongPressed(continuances[index]),
|
||||
selectedChoiceIndex: null,
|
||||
langCode: MatrixState.pangeaController.languageController.activeL2Code(),
|
||||
langCode: MatrixState.pangeaController.userController.userL2Code!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,8 @@ class ITController {
|
|||
return ITRequestModel(
|
||||
text: _sourceText.value!,
|
||||
customInput: textInput,
|
||||
sourceLangCode:
|
||||
MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
targetLangCode:
|
||||
MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
sourceLangCode: MatrixState.pangeaController.userController.userL1Code!,
|
||||
targetLangCode: MatrixState.pangeaController.userController.userL2Code!,
|
||||
goldTranslation: _goldRouteTracker?.fullTranslation,
|
||||
goldContinuances: _goldRouteTracker?.continuances,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_request_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/toolbar_content_loading_indicator.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -29,9 +29,8 @@ class WordDataCard extends StatelessWidget {
|
|||
word: word,
|
||||
fullTextLang: langCode,
|
||||
wordLang: langCode,
|
||||
feedbackLang:
|
||||
MatrixState.pangeaController.languageController.activeL1Code() ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
feedbackLang: MatrixState.pangeaController.userController.userL1Code ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
Future<Result<String>> _fetchDefinition() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -13,36 +12,23 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/bot_client_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/controllers/language_controller.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/locale_provider.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/spaces/controllers/space_code_controller.dart';
|
||||
import 'package:fluffychat/pangea/languages/locale_provider.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/speech_to_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/pangea_push_rules_extension.dart';
|
||||
import 'package:fluffychat/pangea/user/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../utils/firebase_analytics.dart';
|
||||
|
||||
class PangeaController {
|
||||
///pangeaControllers
|
||||
late UserController userController;
|
||||
late LanguageController languageController;
|
||||
late SpaceCodeController spaceCodeController;
|
||||
late PermissionsController permissionsController;
|
||||
late GetAnalyticsController getAnalytics;
|
||||
late PutAnalyticsController putAnalytics;
|
||||
late SubscriptionController subscriptionController;
|
||||
late TextToSpeechController textToSpeech;
|
||||
late SpeechToTextController speechToText;
|
||||
|
||||
///store Services
|
||||
final pLanguageStore = PLanguageStore();
|
||||
|
|
@ -51,19 +37,15 @@ class PangeaController {
|
|||
StreamSubscription? _settingsSubscription;
|
||||
|
||||
///Matrix Variables
|
||||
MatrixState matrixState;
|
||||
Matrix matrix;
|
||||
final MatrixState matrixState;
|
||||
|
||||
int? randomint;
|
||||
PangeaController({required this.matrix, required this.matrixState}) {
|
||||
_setup();
|
||||
_setSettingsSubscriptions();
|
||||
randomint = Random().nextInt(2000);
|
||||
}
|
||||
|
||||
/// Pangea Initialization
|
||||
void _setup() {
|
||||
_addRefInObjects();
|
||||
PangeaController({required this.matrixState}) {
|
||||
userController = UserController();
|
||||
getAnalytics = GetAnalyticsController(this);
|
||||
putAnalytics = PutAnalyticsController(this);
|
||||
subscriptionController = SubscriptionController(this);
|
||||
PAuthGaurd.pController = this;
|
||||
_registerSubscriptions();
|
||||
}
|
||||
|
||||
/// Initializes various controllers and settings.
|
||||
|
|
@ -73,31 +55,128 @@ class PangeaController {
|
|||
void initControllers() {
|
||||
_initAnalyticsControllers();
|
||||
subscriptionController.initialize();
|
||||
setPangeaPushRules();
|
||||
matrixState.client.setPangeaPushRules();
|
||||
TtsController.setAvailableLanguages();
|
||||
}
|
||||
|
||||
/// Initialize controllers
|
||||
_addRefInObjects() {
|
||||
userController = UserController(this);
|
||||
languageController = LanguageController(this);
|
||||
spaceCodeController = SpaceCodeController(this);
|
||||
permissionsController = PermissionsController(this);
|
||||
getAnalytics = GetAnalyticsController(this);
|
||||
putAnalytics = PutAnalyticsController(this);
|
||||
subscriptionController = SubscriptionController(this);
|
||||
textToSpeech = TextToSpeechController(this);
|
||||
speechToText = SpeechToTextController(this);
|
||||
PAuthGaurd.pController = this;
|
||||
void _onLogin(BuildContext context) {
|
||||
initControllers();
|
||||
_registerSubscriptions();
|
||||
|
||||
userController.reinitialize().then((_) {
|
||||
final l1 = userController.profile.userSettings.sourceLanguage;
|
||||
Provider.of<LocaleProvider>(context, listen: false).setLocale(l1);
|
||||
});
|
||||
subscriptionController.reinitialize();
|
||||
}
|
||||
|
||||
_logOutfromPangea(BuildContext context) {
|
||||
debugPrint("Pangea logout");
|
||||
void _onLogout(BuildContext context) {
|
||||
_disposeAnalyticsControllers();
|
||||
userController.clear();
|
||||
_languageSubscription?.cancel();
|
||||
_settingsSubscription?.cancel();
|
||||
_languageSubscription = null;
|
||||
_settingsSubscription = null;
|
||||
|
||||
GoogleAnalytics.logout();
|
||||
clearCache();
|
||||
_clearCache();
|
||||
Provider.of<LocaleProvider>(context, listen: false).setLocale(null);
|
||||
}
|
||||
|
||||
void handleLoginStateChange(
|
||||
LoginState state,
|
||||
String? userID,
|
||||
BuildContext context,
|
||||
) {
|
||||
switch (state) {
|
||||
case LoginState.loggedOut:
|
||||
case LoginState.softLoggedOut:
|
||||
_onLogout(context);
|
||||
break;
|
||||
case LoginState.loggedIn:
|
||||
_onLogin(context);
|
||||
break;
|
||||
}
|
||||
|
||||
Sentry.configureScope(
|
||||
(scope) => scope.setUser(
|
||||
SentryUser(
|
||||
id: userID,
|
||||
name: userID,
|
||||
),
|
||||
),
|
||||
);
|
||||
GoogleAnalytics.analyticsUserUpdate(userID);
|
||||
}
|
||||
|
||||
void _disposeAnalyticsControllers() {
|
||||
putAnalytics.dispose();
|
||||
getAnalytics.dispose();
|
||||
}
|
||||
|
||||
void _registerSubscriptions() {
|
||||
_languageSubscription?.cancel();
|
||||
_languageSubscription =
|
||||
userController.languageStream.stream.listen(_onLanguageUpdate);
|
||||
|
||||
_settingsSubscription?.cancel();
|
||||
_settingsSubscription = userController.settingsUpdateStream.stream
|
||||
.listen((_) => matrixState.client.updateBotOptions());
|
||||
}
|
||||
|
||||
Future<void> _clearCache({List<String> exclude = const []}) async {
|
||||
final List<Future<void>> futures = [];
|
||||
for (final key in _storageKeys) {
|
||||
if (exclude.contains(key)) continue;
|
||||
futures.add(GetStorage(key).erase());
|
||||
}
|
||||
|
||||
if (AppConfig.showedActivityMenu) {
|
||||
futures.add(
|
||||
SharedPreferences.getInstance().then((prefs) async {
|
||||
AppConfig.showedActivityMenu = false;
|
||||
prefs.setBool(
|
||||
SettingKeys.showedActivityMenu,
|
||||
AppConfig.showedActivityMenu,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Future.wait(futures);
|
||||
}
|
||||
|
||||
Future<void> _initAnalyticsControllers() async {
|
||||
putAnalytics.initialize();
|
||||
await getAnalytics.initialize();
|
||||
}
|
||||
|
||||
Future<void> resetAnalytics() async {
|
||||
_disposeAnalyticsControllers();
|
||||
await _initAnalyticsControllers();
|
||||
}
|
||||
|
||||
Future<void> _onLanguageUpdate(LanguageUpdate update) async {
|
||||
final exclude = [
|
||||
'analytics_storage',
|
||||
'course_location_media_storage',
|
||||
'course_location_storage',
|
||||
'course_media_storage',
|
||||
];
|
||||
|
||||
// only clear course data if the base language has changed
|
||||
if (update.prevBaseLang == update.baseLang) {
|
||||
exclude.addAll([
|
||||
'course_storage',
|
||||
'course_topic_storage',
|
||||
'course_activity_storage',
|
||||
]);
|
||||
}
|
||||
|
||||
_clearCache(exclude: exclude);
|
||||
matrixState.client.updateBotOptions();
|
||||
}
|
||||
|
||||
static final List<String> _storageKeys = [
|
||||
'mode_list_storage',
|
||||
'activity_plan_storage',
|
||||
|
|
@ -126,234 +205,4 @@ class PangeaController {
|
|||
'course_location_media_storage',
|
||||
'language_mismatch',
|
||||
];
|
||||
|
||||
Future<void> clearCache({List<String> exclude = const []}) async {
|
||||
final List<Future<void>> futures = [];
|
||||
for (final key in _storageKeys) {
|
||||
if (exclude.contains(key)) continue;
|
||||
futures.add(GetStorage(key).erase());
|
||||
}
|
||||
|
||||
if (AppConfig.showedActivityMenu) {
|
||||
futures.add(
|
||||
SharedPreferences.getInstance().then((prefs) async {
|
||||
AppConfig.showedActivityMenu = false;
|
||||
prefs.setBool(
|
||||
SettingKeys.showedActivityMenu,
|
||||
AppConfig.showedActivityMenu,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Future.wait(futures);
|
||||
}
|
||||
|
||||
Future<Client> checkHomeServerAction() async {
|
||||
final client = await matrixState.getLoginClient();
|
||||
if (client.homeserver != null) {
|
||||
await Future.delayed(Duration.zero);
|
||||
return client;
|
||||
}
|
||||
|
||||
final String homeServer =
|
||||
AppConfig.defaultHomeserver.trim().toLowerCase().replaceAll(' ', '-');
|
||||
var homeserver = Uri.parse(homeServer);
|
||||
if (homeserver.scheme.isEmpty) {
|
||||
homeserver = Uri.https(homeServer, '');
|
||||
}
|
||||
|
||||
try {
|
||||
await client.register();
|
||||
matrixState.loginRegistrationSupported = true;
|
||||
} on MatrixException catch (e) {
|
||||
matrixState.loginRegistrationSupported =
|
||||
e.requireAdditionalAuthentication;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/// check user information if not found then redirect to Date of birth page
|
||||
void handleLoginStateChange(
|
||||
LoginState state,
|
||||
String? userID,
|
||||
BuildContext context,
|
||||
) {
|
||||
switch (state) {
|
||||
case LoginState.loggedOut:
|
||||
case LoginState.softLoggedOut:
|
||||
// Reset cached analytics data
|
||||
_disposeAnalyticsControllers();
|
||||
userController.clear();
|
||||
_languageSubscription?.cancel();
|
||||
_settingsSubscription?.cancel();
|
||||
_languageSubscription = null;
|
||||
_settingsSubscription = null;
|
||||
_logOutfromPangea(context);
|
||||
break;
|
||||
case LoginState.loggedIn:
|
||||
// Initialize analytics data
|
||||
initControllers();
|
||||
_setSettingsSubscriptions();
|
||||
|
||||
userController.reinitialize().then((_) {
|
||||
final l1 = userController.profile.userSettings.sourceLanguage;
|
||||
Provider.of<LocaleProvider>(context, listen: false).setLocale(l1);
|
||||
});
|
||||
subscriptionController.reinitialize();
|
||||
break;
|
||||
}
|
||||
|
||||
Sentry.configureScope(
|
||||
(scope) => scope.setUser(
|
||||
SentryUser(
|
||||
id: userID,
|
||||
name: userID,
|
||||
),
|
||||
),
|
||||
);
|
||||
GoogleAnalytics.analyticsUserUpdate(userID);
|
||||
}
|
||||
|
||||
Future<void> _initAnalyticsControllers() async {
|
||||
putAnalytics.initialize();
|
||||
await getAnalytics.initialize();
|
||||
}
|
||||
|
||||
void _disposeAnalyticsControllers() {
|
||||
putAnalytics.dispose();
|
||||
getAnalytics.dispose();
|
||||
}
|
||||
|
||||
Future<void> resetAnalytics() async {
|
||||
_disposeAnalyticsControllers();
|
||||
await _initAnalyticsControllers();
|
||||
}
|
||||
|
||||
void _setSettingsSubscriptions() {
|
||||
_languageSubscription?.cancel();
|
||||
_languageSubscription =
|
||||
userController.languageStream.stream.listen(_onLanguageUpdate);
|
||||
_settingsSubscription?.cancel();
|
||||
_settingsSubscription = userController.settingsUpdateStream.stream
|
||||
.listen((_) => _updateBotOptions());
|
||||
}
|
||||
|
||||
Future<void> _onLanguageUpdate(LanguageUpdate update) async {
|
||||
final exclude = [
|
||||
'analytics_storage',
|
||||
'course_location_media_storage',
|
||||
'course_location_storage',
|
||||
'course_media_storage',
|
||||
];
|
||||
|
||||
// only clear course data if the base language has changed
|
||||
if (update.prevBaseLang == update.baseLang) {
|
||||
exclude.addAll([
|
||||
'course_storage',
|
||||
'course_topic_storage',
|
||||
'course_activity_storage',
|
||||
]);
|
||||
}
|
||||
|
||||
clearCache(exclude: exclude);
|
||||
_updateBotOptions();
|
||||
}
|
||||
|
||||
Future<void> _updateBotOptions() async {
|
||||
if (!matrixState.client.isLogged()) return;
|
||||
final botDM = matrixState.client.botDM;
|
||||
if (botDM == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final targetLanguage = languageController.userL2?.langCode;
|
||||
final cefrLevel = userController.profile.userSettings.cefrLevel;
|
||||
final updateBotOptions = botDM.botOptions ?? BotOptionsModel();
|
||||
|
||||
if (updateBotOptions.targetLanguage == targetLanguage &&
|
||||
updateBotOptions.languageLevel == cefrLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateBotOptions.targetLanguage = targetLanguage;
|
||||
updateBotOptions.languageLevel = cefrLevel;
|
||||
await botDM.setBotOptions(updateBotOptions);
|
||||
}
|
||||
|
||||
Future<void> setPangeaPushRules() async {
|
||||
if (!matrixState.client.isLogged()) return;
|
||||
final List<Room> analyticsRooms =
|
||||
matrixState.client.rooms.where((room) => room.isAnalyticsRoom).toList();
|
||||
|
||||
for (final Room room in analyticsRooms) {
|
||||
final pushRule = room.pushRuleState;
|
||||
if (pushRule != PushRuleState.dontNotify) {
|
||||
await room.setPushRuleState(PushRuleState.dontNotify);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(matrixState.client.globalPushRules?.override?.any(
|
||||
(element) => element.ruleId == PangeaEventTypes.textToSpeechRule,
|
||||
) ??
|
||||
false)) {
|
||||
await matrixState.client.setPushRule(
|
||||
PushRuleKind.override,
|
||||
PangeaEventTypes.textToSpeechRule,
|
||||
[PushRuleAction.dontNotify],
|
||||
conditions: [
|
||||
PushCondition(
|
||||
kind: 'event_match',
|
||||
key: 'content.msgtype',
|
||||
pattern: MessageTypes.Audio,
|
||||
),
|
||||
PushCondition(
|
||||
kind: 'event_match',
|
||||
key: 'content.transcription.lang_code',
|
||||
pattern: '*',
|
||||
),
|
||||
PushCondition(
|
||||
kind: 'event_match',
|
||||
key: 'content.transcription.text',
|
||||
pattern: '*',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// /// Joins the user to the support space if they are
|
||||
// /// not already a member and have not previously left.
|
||||
// Future<void> joinSupportSpace() async {
|
||||
// // if the user is already in the space, return
|
||||
// await matrixState.client.roomsLoading;
|
||||
// final isInSupportSpace = matrixState.client.rooms.any(
|
||||
// (room) => room.id == Environment.supportSpaceId,
|
||||
// );
|
||||
// if (isInSupportSpace) return;
|
||||
|
||||
// // if the user has previously joined the space, return
|
||||
// final bool previouslyJoined =
|
||||
// userController.profile.userSettings.hasJoinedHelpSpace ?? false;
|
||||
// if (previouslyJoined) return;
|
||||
|
||||
// // join the space
|
||||
// try {
|
||||
// await matrixState.client.joinRoomById(Environment.supportSpaceId);
|
||||
// final room = matrixState.client.getRoomById(Environment.supportSpaceId);
|
||||
// if (room == null) {
|
||||
// await matrixState.client.waitForRoomInSync(
|
||||
// Environment.supportSpaceId,
|
||||
// join: true,
|
||||
// );
|
||||
// }
|
||||
// userController.updateProfile((profile) {
|
||||
// profile.userSettings.hasJoinedHelpSpace = true;
|
||||
// return profile;
|
||||
// });
|
||||
// } catch (err, s) {
|
||||
// ErrorHandler.logError(e: err, s: s);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class Requests {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
|
|||
import 'package:fluffychat/pangea/common/widgets/anchored_overlay_widget.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/overlay_container.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/transparent_backdrop.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/language_mismatch_popup.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_mismatch_popup.dart';
|
||||
import '../../../config/themes.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import 'error_handler.dart';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/choice_animation.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../choreographer/it/it_shimmer.dart';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import 'package:universal_html/html.dart' as html;
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
|
||||
class ShareRoomButton extends StatelessWidget {
|
||||
final Room room;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
|
|
@ -152,12 +152,12 @@ class ConstructIdentifier {
|
|||
|
||||
LemmaInfoRequest get _lemmaInfoRequest => LemmaInfoRequest(
|
||||
partOfSpeech: category,
|
||||
lemmaLang: MatrixState
|
||||
.pangeaController.languageController.userL2?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL1: MatrixState
|
||||
.pangeaController.languageController.userL1?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
lemmaLang:
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL1:
|
||||
MatrixState.pangeaController.userController.userL1?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
lemma: lemma,
|
||||
);
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ class ConstructIdentifier {
|
|||
|
||||
Future<void> setUserLemmaInfo(UserSetLemmaInfo newLemmaInfo) async {
|
||||
final client = MatrixState.pangeaController.matrixState.client;
|
||||
final l2 = MatrixState.pangeaController.languageController.userL2;
|
||||
final l2 = MatrixState.pangeaController.userController.userL2;
|
||||
if (l2 == null) return;
|
||||
|
||||
final analyticsRoom = await client.getMyAnalyticsRoom(l2);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import 'package:fluffychat/pangea/course_plans/courses/course_plan_builder.dart'
|
|||
import 'package:fluffychat/pangea/course_plans/courses/course_plan_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
|
||||
enum CourseDefaultChatsEnum {
|
||||
introductions,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/courses/course_plan_builder.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
|
||||
class CourseInfoChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/course_plan_filter_widget.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class CourseLanguageFilter extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class CourseTopicModel {
|
|||
CourseActivityRepo.getCached(
|
||||
TranslateActivityRequest(
|
||||
activityIds: activityIds,
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
).plans;
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ class CourseTopicModel {
|
|||
final resp = await CourseActivityRepo.get(
|
||||
TranslateActivityRequest(
|
||||
activityIds: activityIds,
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
uuid,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
||||
class CourseFilter {
|
||||
final LanguageModel? targetLanguage;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ mixin CoursePlanProvider<T extends StatefulWidget> on State<T> {
|
|||
course = await CoursePlansRepo.get(
|
||||
GetLocalizedCoursesRequest(
|
||||
coursePlanIds: [courseId],
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import 'package:fluffychat/pangea/course_plans/course_media/course_media_respons
|
|||
import 'package:fluffychat/pangea/course_plans/course_topics/course_topic_model.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_topics/course_topic_repo.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_topics/course_topic_translation_request.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
/// Represents a course plan in the course planner response.
|
||||
|
|
@ -57,7 +57,7 @@ class CoursePlanModel {
|
|||
return CoursePlanModel(
|
||||
targetLanguage: json['target_language'] as String,
|
||||
languageOfInstructions: json['language_of_instructions'] as String,
|
||||
cefrLevel: LanguageLevelTypeEnumExtension.fromString(json['cefr_level']),
|
||||
cefrLevel: LanguageLevelTypeEnum.fromString(json['cefr_level']),
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String,
|
||||
uuid: json['uuid'] as String,
|
||||
|
|
@ -95,7 +95,7 @@ class CoursePlanModel {
|
|||
Map<String, CourseTopicModel> get loadedTopics => CourseTopicRepo.getCached(
|
||||
TranslateTopicRequest(
|
||||
topicIds: topicIds,
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
).topics;
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ class CoursePlanModel {
|
|||
final resp = await CourseTopicRepo.get(
|
||||
TranslateTopicRequest(
|
||||
topicIds: topicIds,
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
uuid,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import 'package:fluffychat/pangea/course_plans/courses/course_plan_event.dart';
|
|||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
|
||||
extension CoursePlanRoomExtension on Room {
|
||||
CoursePlanEvent? get coursePlan {
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class CoursePlansRepo {
|
|||
return search(
|
||||
GetLocalizedCoursesRequest(
|
||||
coursePlanIds: result.docs,
|
||||
l1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
l1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/load_participants_builder.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,198 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:matrix/matrix.dart' hide Result;
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/stt_translation_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
// TODO - make this static and take it out of the _pangeaController
|
||||
// will need to pass accessToken to the requests
|
||||
class MessageDataController {
|
||||
/// get tokens from the server
|
||||
/// if repEventId is not null, send the tokens to the room
|
||||
static Future<Result<TokensResponseModel>> getTokens({
|
||||
required String? repEventId,
|
||||
required TokensRequestModel req,
|
||||
required Room? room,
|
||||
}) async {
|
||||
final res = await TokensRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
req,
|
||||
);
|
||||
if (res.isValue && repEventId != null && room != null) {
|
||||
room
|
||||
.sendPangeaEvent(
|
||||
content: PangeaMessageTokens(
|
||||
tokens: res.result!.tokens,
|
||||
detections: res.result!.detections,
|
||||
).toJson(),
|
||||
parentEventId: repEventId,
|
||||
type: PangeaEventTypes.tokens,
|
||||
)
|
||||
.catchError(
|
||||
(e) => ErrorHandler.logError(
|
||||
m: "error in _getTokens.sendPangeaEvent",
|
||||
e: e,
|
||||
s: StackTrace.current,
|
||||
data: req.toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/////// translation ////////
|
||||
|
||||
/// get translation from the server
|
||||
/// if in cache, return from cache
|
||||
/// if not in cache, get from server
|
||||
/// send the translation to the room as a representation event
|
||||
static Future<PangeaRepresentation> getPangeaRepresentation({
|
||||
required FullTextTranslationRequestModel req,
|
||||
required Event messageEvent,
|
||||
}) =>
|
||||
_getPangeaRepresentation(req: req, messageEvent: messageEvent);
|
||||
|
||||
static Future<PangeaRepresentation> _getPangeaRepresentation({
|
||||
required FullTextTranslationRequestModel req,
|
||||
required Event messageEvent,
|
||||
}) async {
|
||||
final res = await FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
req,
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
throw res.error!;
|
||||
}
|
||||
|
||||
final rep = PangeaRepresentation(
|
||||
langCode: req.tgtLang,
|
||||
text: res.result!,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
);
|
||||
|
||||
messageEvent.room
|
||||
.sendPangeaEvent(
|
||||
content: rep.toJson(),
|
||||
parentEventId: messageEvent.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
)
|
||||
.catchError(
|
||||
(e) => ErrorHandler.logError(
|
||||
m: "error in _getPangeaRepresentation.sendPangeaEvent",
|
||||
e: e,
|
||||
s: StackTrace.current,
|
||||
data: req.toJson(),
|
||||
),
|
||||
);
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
static Future<String?> getPangeaRepresentationEvent({
|
||||
required FullTextTranslationRequestModel req,
|
||||
required PangeaMessageEvent messageEvent,
|
||||
bool originalSent = false,
|
||||
}) async {
|
||||
final res = await FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
req,
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (originalSent && messageEvent.originalSent != null) {
|
||||
originalSent = false;
|
||||
}
|
||||
|
||||
final rep = PangeaRepresentation(
|
||||
langCode: req.tgtLang,
|
||||
text: res.result!,
|
||||
originalSent: originalSent,
|
||||
originalWritten: false,
|
||||
);
|
||||
|
||||
try {
|
||||
final repEvent = await messageEvent.room.sendPangeaEvent(
|
||||
content: rep.toJson(),
|
||||
parentEventId: messageEvent.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
);
|
||||
return repEvent?.eventId;
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
m: "error in _getPangeaRepresentation.sendPangeaEvent",
|
||||
e: e,
|
||||
s: s,
|
||||
data: req.toJson(),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<SttTranslationModel> getSttTranslation({
|
||||
required String? repEventId,
|
||||
required FullTextTranslationRequestModel req,
|
||||
required Room? room,
|
||||
}) =>
|
||||
_getSttTranslation(
|
||||
repEventId: repEventId,
|
||||
req: req,
|
||||
room: room,
|
||||
);
|
||||
|
||||
static Future<SttTranslationModel> _getSttTranslation({
|
||||
required String? repEventId,
|
||||
required FullTextTranslationRequestModel req,
|
||||
required Room? room,
|
||||
}) async {
|
||||
final res = await FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
req,
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
throw res.error!;
|
||||
}
|
||||
|
||||
final translation = SttTranslationModel(
|
||||
translation: res.result!,
|
||||
langCode: req.tgtLang,
|
||||
);
|
||||
|
||||
if (repEventId != null && room != null) {
|
||||
room
|
||||
.sendPangeaEvent(
|
||||
content: translation.toJson(),
|
||||
parentEventId: repEventId,
|
||||
type: PangeaEventTypes.sttTranslation,
|
||||
)
|
||||
.catchError(
|
||||
(e) => ErrorHandler.logError(
|
||||
m: "error in _getSttTranslation.sendPangeaEvent",
|
||||
e: e,
|
||||
s: StackTrace.current,
|
||||
data: req.toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return translation;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,30 +4,35 @@ import 'dart:ui';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:matrix/matrix.dart' hide Result;
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/language_detection_repo.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/language_detection_request.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/language_detection_response.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/audio_encoding_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/audio_encoding_enum.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_repo.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_request_model.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../common/utils/error_handler.dart';
|
||||
import '../../learning_settings/constants/language_constants.dart';
|
||||
import '../../languages/language_constants.dart';
|
||||
import '../constants/pangea_event_types.dart';
|
||||
|
||||
class PangeaMessageEvent {
|
||||
|
|
@ -69,12 +74,10 @@ class PangeaMessageEvent {
|
|||
|
||||
bool get isAudioMessage => _event.messageType == MessageTypes.Audio;
|
||||
|
||||
String? get mimetype {
|
||||
if (!isAudioMessage) return null;
|
||||
final Map<String, dynamic>? info = _event.content.tryGetMap("info");
|
||||
if (info == null) return null;
|
||||
return info["mime_type"] ?? info["mimetype"];
|
||||
}
|
||||
String? get _l2Code => MatrixState.pangeaController.userController.userL2Code;
|
||||
|
||||
String? get _l1Code =>
|
||||
MatrixState.pangeaController.userController.userL1?.langCode;
|
||||
|
||||
Event? _latestEditCache;
|
||||
Event get _latestEdit => _latestEditCache ??= _event
|
||||
|
|
@ -89,131 +92,6 @@ class PangeaMessageEvent {
|
|||
.firstOrNull ??
|
||||
_event;
|
||||
|
||||
void updateLatestEdit() {
|
||||
_latestEditCache = null;
|
||||
_representations = null;
|
||||
}
|
||||
|
||||
Future<PangeaAudioFile> getMatrixAudioFile(
|
||||
String langCode,
|
||||
) async {
|
||||
final RepresentationEvent? rep = representationByLanguage(langCode);
|
||||
final tokensResp = await rep?.tokensGlobal(
|
||||
senderId,
|
||||
originServerTs,
|
||||
);
|
||||
|
||||
final TextToSpeechRequest params = TextToSpeechRequest(
|
||||
text: rep?.content.text ?? body,
|
||||
tokens: tokensResp?.result?.map((t) => t.text).toList() ?? [],
|
||||
langCode: langCode,
|
||||
userL1: l1Code ?? LanguageKeys.unknownLanguage,
|
||||
userL2: l2Code ?? LanguageKeys.unknownLanguage,
|
||||
);
|
||||
|
||||
final TextToSpeechResponse response =
|
||||
await MatrixState.pangeaController.textToSpeech.get(
|
||||
params,
|
||||
);
|
||||
|
||||
final audioBytes = base64.decode(response.audioContent);
|
||||
final eventIdParam = _event.eventId;
|
||||
final fileName =
|
||||
"audio_for_${eventIdParam}_$langCode.${response.fileExtension}";
|
||||
|
||||
final file = PangeaAudioFile(
|
||||
bytes: audioBytes,
|
||||
name: fileName,
|
||||
mimeType: response.mimeType,
|
||||
duration: response.durationMillis,
|
||||
waveform: response.waveform,
|
||||
tokens: response.ttsTokens,
|
||||
);
|
||||
|
||||
sendAudioEvent(file, response, rep?.text ?? body, langCode);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
Future<Event?> sendAudioEvent(
|
||||
PangeaAudioFile file,
|
||||
TextToSpeechResponse response,
|
||||
String text,
|
||||
String langCode,
|
||||
) async {
|
||||
final String? eventId = await room.sendFileEvent(
|
||||
file,
|
||||
inReplyTo: _event,
|
||||
extraContent: {
|
||||
'info': {
|
||||
...file.info,
|
||||
'duration': response.durationMillis,
|
||||
},
|
||||
'org.matrix.msc3245.voice': {},
|
||||
'org.matrix.msc1767.audio': {
|
||||
'duration': response.durationMillis,
|
||||
'waveform': response.waveform,
|
||||
},
|
||||
ModelKey.transcription:
|
||||
response.toPangeaAudioEventData(text, langCode).toJson(),
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint("eventId in getTextToSpeechGlobal $eventId");
|
||||
final Event? audioEvent =
|
||||
eventId != null ? await room.getEventById(eventId) : null;
|
||||
|
||||
if (audioEvent == null) {
|
||||
return null;
|
||||
}
|
||||
allAudio.add(audioEvent);
|
||||
return audioEvent;
|
||||
}
|
||||
|
||||
Event? getTextToSpeechLocal(String langCode, String text) {
|
||||
return allAudio.firstWhereOrNull(
|
||||
(event) {
|
||||
try {
|
||||
// Safely access
|
||||
final dataMap = event.content.tryGetMap(ModelKey.transcription);
|
||||
|
||||
if (dataMap == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// old text to speech content will not have TTSToken data
|
||||
// we want to disregard them and just generate new ones
|
||||
// for that, we'll return false if 'tokens' are null
|
||||
// while in-development, we'll pause here to inspect
|
||||
// debugger can be removed after we're sure it's working
|
||||
if (dataMap['tokens'] == null) {
|
||||
// events before today will definitely not have the tokens
|
||||
debugger(
|
||||
when: kDebugMode &&
|
||||
event.originServerTs.isAfter(DateTime(2024, 10, 16)),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final PangeaAudioEventData audioData =
|
||||
PangeaAudioEventData.fromJson(dataMap as dynamic);
|
||||
|
||||
// Check if both language code and text match
|
||||
return audioData.langCode == langCode && audioData.text == text;
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {},
|
||||
m: "error parsing data in getTextToSpeechLocal",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// get audio events that are related to this event
|
||||
Set<Event> get allAudio => _latestEdit
|
||||
.aggregatedEvents(
|
||||
|
|
@ -227,153 +105,25 @@ class PangeaMessageEvent {
|
|||
null;
|
||||
}).toSet();
|
||||
|
||||
SpeechToTextModel? getSpeechToTextLocal() {
|
||||
final rawBotTranscription =
|
||||
event.content.tryGetMap(ModelKey.botTranscription);
|
||||
|
||||
if (rawBotTranscription != null) {
|
||||
try {
|
||||
return SpeechToTextModel.fromJson(
|
||||
Map<String, dynamic>.from(rawBotTranscription),
|
||||
);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"event": _event.toJson(),
|
||||
},
|
||||
m: "error parsing botTranscription",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return representations
|
||||
.firstWhereOrNull(
|
||||
(element) => element.content.speechToText != null,
|
||||
)
|
||||
?.content
|
||||
.speechToText;
|
||||
}
|
||||
|
||||
Future<SpeechToTextModel> getSpeechToText(
|
||||
String l1Code,
|
||||
String l2Code,
|
||||
) async {
|
||||
if (!isAudioMessage) {
|
||||
throw 'Calling getSpeechToText on non-audio message';
|
||||
}
|
||||
|
||||
final rawBotTranscription =
|
||||
event.content.tryGetMap(ModelKey.botTranscription);
|
||||
if (rawBotTranscription != null) {
|
||||
final SpeechToTextModel botTranscription = SpeechToTextModel.fromJson(
|
||||
Map<String, dynamic>.from(rawBotTranscription),
|
||||
);
|
||||
|
||||
_representations ??= [];
|
||||
_representations!.add(
|
||||
RepresentationEvent(
|
||||
timeline: timeline,
|
||||
parentMessageEvent: _event,
|
||||
content: PangeaRepresentation(
|
||||
langCode: botTranscription.langCode,
|
||||
text: botTranscription.transcript.text,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
speechToText: botTranscription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return botTranscription;
|
||||
}
|
||||
|
||||
final SpeechToTextModel? speechToTextLocal = representations
|
||||
.firstWhereOrNull(
|
||||
(element) => element.content.speechToText != null,
|
||||
)
|
||||
?.content
|
||||
.speechToText;
|
||||
|
||||
if (speechToTextLocal != null) {
|
||||
return speechToTextLocal;
|
||||
}
|
||||
|
||||
final matrixFile = await _event.downloadAndDecryptAttachment();
|
||||
|
||||
final SpeechToTextModel response =
|
||||
await MatrixState.pangeaController.speechToText.get(
|
||||
SpeechToTextRequestModel(
|
||||
audioContent: matrixFile.bytes,
|
||||
audioEvent: _event,
|
||||
config: SpeechToTextAudioConfigModel(
|
||||
encoding: mimeTypeToAudioEncoding(matrixFile.mimeType),
|
||||
//this is the default in the RecordConfig in record package
|
||||
//TODO: check if this is the correct value and make it a constant somewhere
|
||||
sampleRateHertz: 22050,
|
||||
userL1: l1Code,
|
||||
userL2: l2Code,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
_representations?.add(
|
||||
RepresentationEvent(
|
||||
timeline: timeline,
|
||||
parentMessageEvent: _event,
|
||||
content: PangeaRepresentation(
|
||||
langCode: response.langCode,
|
||||
text: response.transcript.text,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
speechToText: response,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<String> sttTranslationByLanguageGlobal({
|
||||
required String langCode,
|
||||
required String l1Code,
|
||||
required String l2Code,
|
||||
}) async {
|
||||
if (!representations.any(
|
||||
(element) => element.content.speechToText != null,
|
||||
)) {
|
||||
await getSpeechToText(l1Code, l2Code);
|
||||
}
|
||||
|
||||
final rep = representations.firstWhereOrNull(
|
||||
(element) => element.content.speechToText != null,
|
||||
);
|
||||
|
||||
if (rep == null) {
|
||||
throw Exception("No speech to text representation found");
|
||||
}
|
||||
|
||||
final resp = await rep.getSttTranslation(userL1: l1Code, userL2: l2Code);
|
||||
return resp.translation;
|
||||
}
|
||||
|
||||
PangeaMessageTokens? _tokensSafe(Map<String, dynamic>? content) {
|
||||
try {
|
||||
if (content == null) return null;
|
||||
return PangeaMessageTokens.fromJson(content);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: content ?? {},
|
||||
m: "error parsing tokensSent",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
List<RepresentationEvent> get _repEvents => _latestEdit
|
||||
.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.representation,
|
||||
)
|
||||
.map(
|
||||
(e) => RepresentationEvent(
|
||||
event: e,
|
||||
parentMessageEvent: _event,
|
||||
timeline: timeline,
|
||||
),
|
||||
)
|
||||
.sorted(
|
||||
(a, b) {
|
||||
if (a.event == null) return -1;
|
||||
if (b.event == null) return 1;
|
||||
return b.event!.originServerTs.compareTo(a.event!.originServerTs);
|
||||
},
|
||||
).toList();
|
||||
|
||||
ChoreoRecordModel? get _embeddedChoreo {
|
||||
try {
|
||||
|
|
@ -393,6 +143,22 @@ class PangeaMessageEvent {
|
|||
}
|
||||
}
|
||||
|
||||
PangeaMessageTokens? _tokensSafe(Map<String, dynamic>? content) {
|
||||
try {
|
||||
if (content == null) return null;
|
||||
return PangeaMessageTokens.fromJson(content);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: content ?? {},
|
||||
m: "error parsing tokensSent",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<RepresentationEvent>? _representations;
|
||||
List<RepresentationEvent> get representations {
|
||||
if (_representations != null) return _representations!;
|
||||
|
|
@ -410,18 +176,6 @@ class PangeaMessageEvent {
|
|||
choreo: _embeddedChoreo,
|
||||
timeline: timeline,
|
||||
);
|
||||
if (_latestEdit.content[ModelKey.choreoRecord] == null) {
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "originalSent created without _event or _choreo",
|
||||
data: {
|
||||
"eventId": _latestEdit.eventId,
|
||||
"room": _latestEdit.room.id,
|
||||
"sender": _latestEdit.senderId,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If originalSent has no tokens, there is not way to generate a tokens event
|
||||
// and send it as a related event, since original sent has not eventID to set
|
||||
|
|
@ -471,130 +225,10 @@ class PangeaMessageEvent {
|
|||
}
|
||||
}
|
||||
|
||||
_representations!.addAll(
|
||||
_latestEdit
|
||||
.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.representation,
|
||||
)
|
||||
.map(
|
||||
(e) => RepresentationEvent(
|
||||
event: e,
|
||||
parentMessageEvent: _event,
|
||||
timeline: timeline,
|
||||
),
|
||||
)
|
||||
.sorted(
|
||||
(a, b) {
|
||||
//TODO - test with edited events to make sure this is working
|
||||
if (a.event == null) return -1;
|
||||
if (b.event == null) return 1;
|
||||
return b.event!.originServerTs.compareTo(a.event!.originServerTs);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
|
||||
_representations!.addAll(_repEvents);
|
||||
return _representations!;
|
||||
}
|
||||
|
||||
RepresentationEvent? representationByLanguage(
|
||||
String langCode, {
|
||||
bool Function(RepresentationEvent)? filter,
|
||||
}) =>
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
|
||||
Future<String?> representationByDetectedLanguage() async {
|
||||
LanguageDetectionResponse? resp;
|
||||
try {
|
||||
resp = await LanguageDetectionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request: LanguageDetectionRequest(
|
||||
text: _latestEdit.body,
|
||||
senderl1: l1Code,
|
||||
senderl2: l2Code,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"event": _event.toJson(),
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final langCode = resp.detections.firstOrNull?.langCode;
|
||||
if (langCode == null) return null;
|
||||
if (langCode == originalSent?.langCode) {
|
||||
return originalSent?.event?.eventId;
|
||||
}
|
||||
|
||||
// clear representations cache so the new representation event can be added when next requested
|
||||
_representations = null;
|
||||
|
||||
return MessageDataController.getPangeaRepresentationEvent(
|
||||
req: FullTextTranslationRequestModel(
|
||||
text: originalSent?.content.text ?? _latestEdit.body,
|
||||
srcLang: originalSent?.langCode,
|
||||
tgtLang: langCode,
|
||||
userL2: l2Code ?? LanguageKeys.unknownLanguage,
|
||||
userL1: l1Code ?? LanguageKeys.unknownLanguage,
|
||||
),
|
||||
messageEvent: this,
|
||||
originalSent: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> l1Respresentation() async {
|
||||
if (l1Code == null || l2Code == null) {
|
||||
throw Exception("Missing language codes");
|
||||
}
|
||||
|
||||
final includedIT =
|
||||
(originalSent?.choreo?.endedWithIT(originalSent!.text) ?? false) &&
|
||||
!(originalSent?.choreo?.includedIGC ?? true);
|
||||
|
||||
RepresentationEvent? rep;
|
||||
if (!includedIT) {
|
||||
// if the message didn't go through translation, get any l1 rep
|
||||
rep = representationByLanguage(l1Code!);
|
||||
} else {
|
||||
// if the message went through translation, get the non-original
|
||||
// l1 rep since originalWritten could contain some l2 words
|
||||
// (https://github.com/pangeachat/client/issues/3591)
|
||||
rep = representationByLanguage(
|
||||
l1Code!,
|
||||
filter: (rep) => !rep.content.originalWritten,
|
||||
);
|
||||
}
|
||||
|
||||
if (rep != null) return rep.content.text;
|
||||
|
||||
final String srcLang = includedIT
|
||||
? (originalWritten?.langCode ?? l1Code!)
|
||||
: (originalSent?.langCode ?? l2Code!);
|
||||
|
||||
// clear representations cache so the new representation event can be added when next requested
|
||||
_representations = null;
|
||||
final resp = await MessageDataController.getPangeaRepresentation(
|
||||
req: FullTextTranslationRequestModel(
|
||||
text: includedIT ? originalWrittenContent : messageDisplayText,
|
||||
srcLang: srcLang,
|
||||
tgtLang: l1Code!,
|
||||
userL2: l2Code!,
|
||||
userL1: l1Code!,
|
||||
),
|
||||
messageEvent: _event,
|
||||
);
|
||||
return resp.text;
|
||||
}
|
||||
|
||||
RepresentationEvent? get originalSent => representations
|
||||
.firstWhereOrNull((element) => element.content.originalSent);
|
||||
|
||||
|
|
@ -612,12 +246,6 @@ class PangeaMessageEvent {
|
|||
return written ?? body;
|
||||
}
|
||||
|
||||
String? get l2Code =>
|
||||
MatrixState.pangeaController.languageController.activeL2Code();
|
||||
|
||||
String? get l1Code =>
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
|
||||
String get messageDisplayLangCode {
|
||||
if (isAudioMessage) {
|
||||
final stt = getSpeechToTextLocal();
|
||||
|
|
@ -625,13 +253,12 @@ class PangeaMessageEvent {
|
|||
return stt.langCode;
|
||||
}
|
||||
|
||||
final bool immersionMode = MatrixState
|
||||
.pangeaController.permissionsController
|
||||
final bool immersionMode = MatrixState.pangeaController.userController
|
||||
.isToolEnabled(ToolSetting.immersionMode);
|
||||
|
||||
final String? originalLangCode = originalSent?.langCode;
|
||||
|
||||
final String? langCode = immersionMode ? l2Code : originalLangCode;
|
||||
final String? langCode = immersionMode ? _l2Code : originalLangCode;
|
||||
return langCode ?? LanguageKeys.unknownLanguage;
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +271,330 @@ class PangeaMessageEvent {
|
|||
String get messageDisplayText => messageDisplayRepresentation?.text ?? body;
|
||||
|
||||
TextDirection get textDirection =>
|
||||
PLanguageStore.rtlLanguageCodes.contains(messageDisplayLangCode)
|
||||
LanguageConstants.rtlLanguageCodes.contains(messageDisplayLangCode)
|
||||
? TextDirection.rtl
|
||||
: TextDirection.ltr;
|
||||
|
||||
void updateLatestEdit() {
|
||||
_latestEditCache = null;
|
||||
_representations = null;
|
||||
}
|
||||
|
||||
RepresentationEvent? representationByLanguage(
|
||||
String langCode, {
|
||||
bool Function(RepresentationEvent)? filter,
|
||||
}) =>
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
|
||||
Event? getTextToSpeechLocal(String langCode, String text) {
|
||||
for (final audio in allAudio) {
|
||||
final dataMap = audio.content.tryGetMap(ModelKey.transcription);
|
||||
if (dataMap == null || !dataMap.containsKey('tokens')) continue;
|
||||
|
||||
try {
|
||||
final PangeaAudioEventData audioData = PangeaAudioEventData.fromJson(
|
||||
dataMap as dynamic,
|
||||
);
|
||||
|
||||
if (audioData.langCode == langCode && audioData.text == text) {
|
||||
return audio;
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"event": audio.toJson(),
|
||||
},
|
||||
m: "error parsing data in getTextToSpeechLocal",
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SpeechToTextResponseModel? getSpeechToTextLocal() {
|
||||
final rawBotTranscription =
|
||||
event.content.tryGetMap(ModelKey.botTranscription);
|
||||
|
||||
if (rawBotTranscription != null) {
|
||||
try {
|
||||
return SpeechToTextResponseModel.fromJson(
|
||||
Map<String, dynamic>.from(rawBotTranscription),
|
||||
);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"event": _event.toJson(),
|
||||
},
|
||||
m: "error parsing botTranscription",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return representations
|
||||
.firstWhereOrNull(
|
||||
(element) => element.content.speechToText != null,
|
||||
)
|
||||
?.content
|
||||
.speechToText;
|
||||
}
|
||||
|
||||
Future<PangeaAudioFile> requestTextToSpeech(
|
||||
String langCode,
|
||||
) async {
|
||||
final local = getTextToSpeechLocal(langCode, messageDisplayText);
|
||||
if (local != null) {
|
||||
final file = await local.getPangeaAudioFile();
|
||||
if (file != null) return file;
|
||||
}
|
||||
|
||||
final rep = representationByLanguage(langCode);
|
||||
final tokensResp = await rep?.requestTokens();
|
||||
final request = TextToSpeechRequestModel(
|
||||
text: rep?.content.text ?? body,
|
||||
tokens: tokensResp?.result?.map((t) => t.text).toList() ?? [],
|
||||
langCode: langCode,
|
||||
userL1: _l1Code ?? LanguageKeys.unknownLanguage,
|
||||
userL2: _l2Code ?? LanguageKeys.unknownLanguage,
|
||||
);
|
||||
|
||||
final result = await TextToSpeechRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
|
||||
if (result.error != null) {
|
||||
throw Exception(
|
||||
"Error getting text to speech: ${result.error}",
|
||||
);
|
||||
}
|
||||
|
||||
final response = result.result!;
|
||||
final audioBytes = base64.decode(response.audioContent);
|
||||
final fileName =
|
||||
"audio_for_${_event.eventId}_$langCode.${response.fileExtension}";
|
||||
|
||||
final file = PangeaAudioFile(
|
||||
bytes: audioBytes,
|
||||
name: fileName,
|
||||
mimeType: response.mimeType,
|
||||
duration: response.durationMillis,
|
||||
waveform: response.waveform,
|
||||
tokens: response.ttsTokens,
|
||||
);
|
||||
|
||||
room.sendFileEvent(
|
||||
file,
|
||||
inReplyTo: _event,
|
||||
extraContent: {
|
||||
'info': {
|
||||
...file.info,
|
||||
'duration': response.durationMillis,
|
||||
},
|
||||
'org.matrix.msc3245.voice': {},
|
||||
'org.matrix.msc1767.audio': {
|
||||
'duration': response.durationMillis,
|
||||
'waveform': response.waveform,
|
||||
},
|
||||
ModelKey.transcription: response
|
||||
.toPangeaAudioEventData(rep?.text ?? body, langCode)
|
||||
.toJson(),
|
||||
},
|
||||
).then((eventId) async {
|
||||
final Event? audioEvent =
|
||||
eventId != null ? await room.getEventById(eventId) : null;
|
||||
|
||||
if (audioEvent != null) {
|
||||
allAudio.add(audioEvent);
|
||||
}
|
||||
});
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
Future<SpeechToTextResponseModel> requestSpeechToText(
|
||||
String l1Code,
|
||||
String l2Code,
|
||||
) async {
|
||||
if (!isAudioMessage) {
|
||||
throw 'Calling getSpeechToText on non-audio message';
|
||||
}
|
||||
|
||||
final speechToTextLocal = getSpeechToTextLocal();
|
||||
if (speechToTextLocal != null) {
|
||||
return speechToTextLocal;
|
||||
}
|
||||
|
||||
final matrixFile = await _event.downloadAndDecryptAttachment();
|
||||
final result = await SpeechToTextRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
SpeechToTextRequestModel(
|
||||
audioContent: matrixFile.bytes,
|
||||
audioEvent: _event,
|
||||
config: SpeechToTextAudioConfigModel(
|
||||
encoding: mimeTypeToAudioEncoding(matrixFile.mimeType),
|
||||
sampleRateHertz: 22050,
|
||||
userL1: l1Code,
|
||||
userL2: l2Code,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (result.error != null) {
|
||||
throw Exception(
|
||||
"Error getting speech to text: ${result.error}",
|
||||
);
|
||||
}
|
||||
|
||||
_representations = null;
|
||||
return result.result!;
|
||||
}
|
||||
|
||||
Future<String> requestSttTranslation({
|
||||
required String langCode,
|
||||
required String l1Code,
|
||||
required String l2Code,
|
||||
}) async {
|
||||
if (!representations.any(
|
||||
(element) => element.content.speechToText != null,
|
||||
)) {
|
||||
await requestSpeechToText(l1Code, l2Code);
|
||||
}
|
||||
|
||||
final rep = representations.firstWhereOrNull(
|
||||
(element) => element.content.speechToText != null,
|
||||
);
|
||||
|
||||
if (rep == null) {
|
||||
throw Exception("No speech to text representation found");
|
||||
}
|
||||
|
||||
final resp = await rep.requestSttTranslation(
|
||||
userL1: l1Code,
|
||||
userL2: l2Code,
|
||||
);
|
||||
return resp.translation;
|
||||
}
|
||||
|
||||
Future<String?> requestRepresentationByDetectedLanguage() async {
|
||||
LanguageDetectionResponse? resp;
|
||||
final result = await LanguageDetectionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
LanguageDetectionRequest(
|
||||
text: _latestEdit.body,
|
||||
senderl1: _l1Code,
|
||||
senderl2: _l2Code,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.isError) return null;
|
||||
resp = result.result!;
|
||||
|
||||
final langCode = resp.detections.firstOrNull?.langCode;
|
||||
if (langCode == null) return null;
|
||||
if (langCode == originalSent?.langCode) {
|
||||
return originalSent?.event?.eventId;
|
||||
}
|
||||
|
||||
final res = await _requestRepresentation(
|
||||
originalSent?.content.text ?? _latestEdit.body,
|
||||
langCode,
|
||||
originalSent?.langCode ?? LanguageKeys.unknownLanguage,
|
||||
originalSent: originalSent == null,
|
||||
);
|
||||
|
||||
if (res.isError) return null;
|
||||
return _sendRepresentationEvent(res.result!);
|
||||
}
|
||||
|
||||
Future<String> requestRespresentationByL1() async {
|
||||
if (_l1Code == null || _l2Code == null) {
|
||||
throw Exception("Missing language codes");
|
||||
}
|
||||
|
||||
final includedIT =
|
||||
(originalSent?.choreo?.endedWithIT(originalSent!.text) ?? false) &&
|
||||
!(originalSent?.choreo?.includedIGC ?? true);
|
||||
|
||||
RepresentationEvent? rep;
|
||||
if (!includedIT) {
|
||||
// if the message didn't go through translation, get any l1 rep
|
||||
rep = representationByLanguage(_l1Code!);
|
||||
} else {
|
||||
// if the message went through translation, get the non-original
|
||||
// l1 rep since originalWritten could contain some l2 words
|
||||
// (https://github.com/pangeachat/client/issues/3591)
|
||||
rep = representationByLanguage(
|
||||
_l1Code!,
|
||||
filter: (rep) => !rep.content.originalWritten,
|
||||
);
|
||||
}
|
||||
|
||||
if (rep != null) return rep.content.text;
|
||||
|
||||
final String srcLang = includedIT
|
||||
? (originalWritten?.langCode ?? _l1Code!)
|
||||
: (originalSent?.langCode ?? _l2Code!);
|
||||
|
||||
final resp = await _requestRepresentation(
|
||||
includedIT ? originalWrittenContent : messageDisplayText,
|
||||
_l1Code!,
|
||||
srcLang,
|
||||
);
|
||||
|
||||
if (resp.isError) throw resp.error!;
|
||||
_sendRepresentationEvent(resp.result!);
|
||||
return resp.result!.text;
|
||||
}
|
||||
|
||||
Future<Result<PangeaRepresentation>> _requestRepresentation(
|
||||
String text,
|
||||
String targetLang,
|
||||
String sourceLang, {
|
||||
bool originalSent = false,
|
||||
}) async {
|
||||
_representations = null;
|
||||
|
||||
final res = await FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
FullTextTranslationRequestModel(
|
||||
text: text,
|
||||
srcLang: sourceLang,
|
||||
tgtLang: targetLang,
|
||||
userL2: _l2Code ?? LanguageKeys.unknownLanguage,
|
||||
userL1: _l1Code ?? LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
|
||||
return res.isError
|
||||
? Result.error(res.error!)
|
||||
: Result.value(
|
||||
PangeaRepresentation(
|
||||
langCode: targetLang,
|
||||
text: res.result!,
|
||||
originalSent: originalSent,
|
||||
originalWritten: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> _sendRepresentationEvent(
|
||||
PangeaRepresentation representation,
|
||||
) async {
|
||||
final repEvent = await room.sendPangeaEvent(
|
||||
content: representation.toJson(),
|
||||
parentEventId: eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
);
|
||||
return repEvent?.eventId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart' hide Result;
|
||||
import 'package:matrix/src/utils/markdown.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_choreo_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/language_detection_model.dart';
|
||||
|
|
@ -23,10 +21,10 @@ import 'package:fluffychat/pangea/events/models/representation_content_model.dar
|
|||
import 'package:fluffychat/pangea/events/models/stt_translation_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/parts_of_speech_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -60,6 +58,33 @@ class RepresentationEvent {
|
|||
|
||||
Event? get event => _event;
|
||||
|
||||
String get text => content.text;
|
||||
|
||||
String get langCode => content.langCode;
|
||||
|
||||
List<LanguageDetectionModel>? get detections => _tokens?.detections;
|
||||
|
||||
Set<Event> get tokenEvents =>
|
||||
_event?.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.tokens,
|
||||
) ??
|
||||
{};
|
||||
|
||||
Set<Event> get sttEvents =>
|
||||
_event?.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.sttTranslation,
|
||||
) ??
|
||||
{};
|
||||
|
||||
Set<Event> get choreoEvents =>
|
||||
_event?.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.choreoRecord,
|
||||
) ??
|
||||
{};
|
||||
|
||||
// Note: in the case where the event is the originalSent or originalWritten event,
|
||||
// the content will be set on initialization by the PangeaMessageEvent
|
||||
// Otherwise, the content will be fetched from the event where it is stored in content[type]
|
||||
|
|
@ -69,99 +94,38 @@ class RepresentationEvent {
|
|||
return _content!;
|
||||
}
|
||||
|
||||
String get text => content.text;
|
||||
|
||||
String get langCode => content.langCode;
|
||||
|
||||
bool get botAuthored =>
|
||||
content.originalSent == false && content.originalWritten == false;
|
||||
|
||||
List<LanguageDetectionModel>? get detections => _tokens?.detections;
|
||||
|
||||
List<PangeaToken>? get tokens {
|
||||
if (_tokens != null) return _tokens!.tokens;
|
||||
if (_event == null) return null;
|
||||
|
||||
final Set<Event> tokenEvents = _event?.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.tokens,
|
||||
) ??
|
||||
{};
|
||||
|
||||
if (tokenEvents.isEmpty) return null;
|
||||
_tokens = tokenEvents.last.getPangeaContent<PangeaMessageTokens>();
|
||||
return _tokens?.tokens;
|
||||
}
|
||||
|
||||
Future<Result<List<PangeaToken>>> tokensGlobal(
|
||||
String senderID,
|
||||
DateTime timestamp,
|
||||
) async {
|
||||
if (tokens != null) return Result.value(tokens!);
|
||||
ChoreoRecordModel? get choreo {
|
||||
if (_choreo != null) return _choreo;
|
||||
|
||||
if (_event == null && timestamp.isAfter(DateTime(2024, 9, 25))) {
|
||||
if (_event == null) {
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message:
|
||||
'representation with no _event and no tokens got tokens directly. This means an original_sent with no tokens. This should not happen in messages sent after September 25',
|
||||
data: {
|
||||
'content': content.toJson(),
|
||||
'event': _event?.toJson(),
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'senderID': senderID,
|
||||
},
|
||||
message: "_event and _choreo both null",
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final res = await MessageDataController.getTokens(
|
||||
repEventId: _event?.eventId,
|
||||
room: _event?.room ?? parentMessageEvent.room,
|
||||
req: TokensRequestModel(
|
||||
fullText: text,
|
||||
langCode: langCode,
|
||||
senderL1:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
// since langCode is known, senderL2 will be used to determine whether these tokens
|
||||
// need pos/mporph tags whether lemmas are eligible to marked as "save_vocab=true"
|
||||
senderL2:
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
return Result.error(res.error!);
|
||||
} else {
|
||||
return Result.value(res.result!.tokens);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendTokensEvent(
|
||||
String repEventID,
|
||||
Room room,
|
||||
String userl1,
|
||||
String userl2,
|
||||
) async {
|
||||
if (tokens != null) return;
|
||||
if (_event == null) {
|
||||
if (choreoEvents.isEmpty) return null;
|
||||
if (choreoEvents.length > 1) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: "Called getTokensEvent with no _event",
|
||||
data: {},
|
||||
m: 'should not have more than one choreoEvent per representation ${_event?.eventId}',
|
||||
s: StackTrace.current,
|
||||
data: {"event": _event?.toJson()},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await MessageDataController.getTokens(
|
||||
repEventId: repEventID,
|
||||
room: room,
|
||||
req: TokensRequestModel(
|
||||
fullText: text,
|
||||
langCode: langCode,
|
||||
senderL1: userl1,
|
||||
senderL2: userl2,
|
||||
),
|
||||
);
|
||||
return ChoreoEvent(event: choreoEvents.first).content;
|
||||
}
|
||||
|
||||
List<SttTranslationModel> get sttTranslations {
|
||||
|
|
@ -175,11 +139,6 @@ class RepresentationEvent {
|
|||
return [];
|
||||
}
|
||||
|
||||
final Set<Event> sttEvents = _event!.aggregatedEvents(
|
||||
timeline,
|
||||
PangeaEventTypes.sttTranslation,
|
||||
);
|
||||
|
||||
if (sttEvents.isEmpty) return [];
|
||||
final List<SttTranslationModel> sttTranslations = [];
|
||||
for (final event in sttEvents) {
|
||||
|
|
@ -200,151 +159,10 @@ class RepresentationEvent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
return sttTranslations;
|
||||
}
|
||||
|
||||
Future<SttTranslationModel> getSttTranslation({
|
||||
required String userL1,
|
||||
required String userL2,
|
||||
}) async {
|
||||
if (content.speechToText == null) {
|
||||
throw Exception(
|
||||
"RepresentationEvent.getSttTranslation called on a representation without speechToText",
|
||||
);
|
||||
}
|
||||
|
||||
final local = sttTranslations.firstWhereOrNull((t) => t.langCode == userL1);
|
||||
if (local != null) return local;
|
||||
|
||||
return MessageDataController.getSttTranslation(
|
||||
repEventId: _event?.eventId,
|
||||
room: _event?.room,
|
||||
req: FullTextTranslationRequestModel(
|
||||
text: content.speechToText!.transcript.text,
|
||||
tgtLang: userL1,
|
||||
userL2: userL2,
|
||||
userL1: userL1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ChoreoRecordModel? get choreo {
|
||||
if (_choreo != null) return _choreo;
|
||||
|
||||
if (_event == null) {
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "_event and _choreo both null",
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Set<Event> choreoMatrixEvents =
|
||||
_event?.aggregatedEvents(timeline, PangeaEventTypes.choreoRecord) ?? {};
|
||||
|
||||
if (choreoMatrixEvents.isEmpty) return null;
|
||||
|
||||
if (choreoMatrixEvents.length > 1) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: 'should not have more than one choreoEvent per representation ${_event?.eventId}',
|
||||
s: StackTrace.current,
|
||||
data: {"event": _event?.toJson()},
|
||||
);
|
||||
}
|
||||
|
||||
_choreo = ChoreoEvent(event: choreoMatrixEvents.first).content;
|
||||
|
||||
return _choreo;
|
||||
}
|
||||
|
||||
String? formatBody() {
|
||||
return markdown(content.text);
|
||||
}
|
||||
|
||||
/// Finds the closest non-punctuation token to the given token.
|
||||
///
|
||||
/// This method checks if the provided token is a punctuation token. If it is not,
|
||||
/// it returns the token itself. If the token is a punctuation token, it searches
|
||||
/// through the list of tokens to find the closest non-punctuation token either to
|
||||
/// the left or right of the given token.
|
||||
///
|
||||
/// If both left and right non-punctuation tokens are found, it returns the one
|
||||
/// that is closest to the given token. If only one of them is found, it returns
|
||||
/// that token. If no non-punctuation tokens are found, it returns null.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - token: The token for which to find the closest non-punctuation token.
|
||||
///
|
||||
/// - Returns: The closest non-punctuation token, or null if no such token exists.
|
||||
PangeaToken? getClosestNonPunctToken(PangeaToken token) {
|
||||
if (token.pos != "PUNCT") return token;
|
||||
if (tokens == null) return null;
|
||||
final index = tokens!.indexOf(token);
|
||||
if (index > -1) {
|
||||
final leftTokens = tokens!.sublist(0, index);
|
||||
final rightTokens = tokens!.sublist(index + 1);
|
||||
final leftMostToken = leftTokens.lastWhereOrNull(
|
||||
(element) => element.pos != "PUNCT",
|
||||
);
|
||||
final rightMostToken = rightTokens.firstWhereOrNull(
|
||||
(element) => element.pos != "PUNCT",
|
||||
);
|
||||
|
||||
if (leftMostToken != null && rightMostToken != null) {
|
||||
final leftDistance = token.start - leftMostToken.end;
|
||||
final rightDistance = rightMostToken.start - token.end;
|
||||
return leftDistance < rightDistance ? leftMostToken : rightMostToken;
|
||||
} else if (leftMostToken != null) {
|
||||
return leftMostToken;
|
||||
} else if (rightMostToken != null) {
|
||||
return rightMostToken;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<PangeaToken> get tokensToSave =>
|
||||
tokens?.where((token) => token.lemma.saveVocab).toList() ?? [];
|
||||
|
||||
// List<ConstructIdentifier> get allTokenMorphsToConstructIdentifiers => tokens?.map((t) => t.morphConstructIds).toList() ??
|
||||
// [];
|
||||
|
||||
/// get allTokenMorphsToConstructIdentifiers
|
||||
Set<MorphFeaturesEnum> get morphFeatureSetToPractice =>
|
||||
MorphFeaturesEnum.values.where((feature) {
|
||||
// pos is always included
|
||||
if (feature == MorphFeaturesEnum.Pos) {
|
||||
return true;
|
||||
}
|
||||
return tokens?.any((token) => token.morph.containsKey(feature)) ??
|
||||
false;
|
||||
}).toSet();
|
||||
|
||||
Set<PartOfSpeechEnum> posSetToPractice(ActivityTypeEnum a) =>
|
||||
PartOfSpeechEnum.values.where((pos) {
|
||||
// some pos are not eligible for practice at all
|
||||
if (!pos.eligibleForPractice(a)) {
|
||||
return false;
|
||||
}
|
||||
return tokens?.any(
|
||||
(token) => token.pos.toLowerCase() == pos.name.toLowerCase(),
|
||||
) ??
|
||||
false;
|
||||
}).toSet();
|
||||
|
||||
List<String> tagsByFeature(MorphFeaturesEnum feature) {
|
||||
return tokens
|
||||
?.where((t) => t.morph.containsKey(feature))
|
||||
.map((t) => t.morph[feature])
|
||||
.cast<String>()
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
List<OneConstructUse> vocabAndMorphUses() {
|
||||
List<OneConstructUse> get vocabAndMorphUses {
|
||||
if (tokens == null || tokens!.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -361,4 +179,117 @@ class RepresentationEvent {
|
|||
choreo: choreo,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds the closest non-punctuation token to the given token.
|
||||
PangeaToken? getClosestNonPunctToken(PangeaToken token) {
|
||||
// If it's not punctuation, it's already the closest.
|
||||
if (token.pos != "PUNCT") return token;
|
||||
|
||||
final list = tokens;
|
||||
if (list == null) return null;
|
||||
|
||||
final index = list.indexOf(token);
|
||||
if (index == -1) return null;
|
||||
|
||||
PangeaToken? left;
|
||||
PangeaToken? right;
|
||||
|
||||
// Scan left
|
||||
for (int i = index - 1; i >= 0; i--) {
|
||||
if (list[i].pos != "PUNCT") {
|
||||
left = list[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Scan right
|
||||
for (int i = index + 1; i < list.length; i++) {
|
||||
if (list[i].pos != "PUNCT") {
|
||||
right = list[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (left == null) return right;
|
||||
if (right == null) return left;
|
||||
|
||||
// Choose the nearest by distance
|
||||
final leftDistance = token.start - left.end;
|
||||
final rightDistance = right.start - token.end;
|
||||
|
||||
return leftDistance < rightDistance ? left : right;
|
||||
}
|
||||
|
||||
Future<Result<List<PangeaToken>>> requestTokens() async {
|
||||
if (tokens != null) return Result.value(tokens!);
|
||||
final res = await TokensRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
TokensRequestModel(
|
||||
fullText: text,
|
||||
langCode: langCode,
|
||||
senderL1:
|
||||
MatrixState.pangeaController.userController.userL1?.langCode ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
senderL2:
|
||||
MatrixState.pangeaController.userController.userL2?.langCode ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
|
||||
if (_event != null) {
|
||||
_event!.room.sendPangeaEvent(
|
||||
content: PangeaMessageTokens(
|
||||
tokens: res.result!.tokens,
|
||||
detections: res.result!.detections,
|
||||
).toJson(),
|
||||
parentEventId: _event!.eventId,
|
||||
type: PangeaEventTypes.tokens,
|
||||
);
|
||||
}
|
||||
|
||||
return res.isError
|
||||
? Result.error(res.error!)
|
||||
: Result.value(res.result!.tokens);
|
||||
}
|
||||
|
||||
Future<SttTranslationModel> requestSttTranslation({
|
||||
required String userL1,
|
||||
required String userL2,
|
||||
}) async {
|
||||
if (content.speechToText == null) {
|
||||
throw Exception(
|
||||
"RepresentationEvent.getSttTranslation called on a representation without speechToText",
|
||||
);
|
||||
}
|
||||
|
||||
final local = sttTranslations.firstWhereOrNull((t) => t.langCode == userL1);
|
||||
if (local != null) return local;
|
||||
|
||||
final res = await FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
FullTextTranslationRequestModel(
|
||||
text: content.speechToText!.transcript.text,
|
||||
tgtLang: userL1,
|
||||
userL2: userL2,
|
||||
userL1: userL1,
|
||||
),
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
throw res.error!;
|
||||
}
|
||||
|
||||
final translation = SttTranslationModel(
|
||||
translation: res.result!,
|
||||
langCode: userL1,
|
||||
);
|
||||
|
||||
_event?.room.sendPangeaEvent(
|
||||
content: translation.toJson(),
|
||||
parentEventId: _event!.eventId,
|
||||
type: PangeaEventTypes.sttTranslation,
|
||||
);
|
||||
|
||||
return translation;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
|||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
|
||||
extension PangeaEvent on Event {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_status_enum.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
/// this class is contained within a [RepresentationEvent]
|
||||
|
|
@ -30,7 +30,7 @@ class PangeaRepresentation {
|
|||
bool originalWritten;
|
||||
|
||||
// a representation can be create via speech to text on the original message
|
||||
SpeechToTextModel? speechToText;
|
||||
SpeechToTextResponseModel? speechToText;
|
||||
|
||||
// how do we know which representation was sent by author?
|
||||
// RepresentationEvent.text == PangeaMessageEvent.event.body
|
||||
|
|
@ -70,7 +70,7 @@ class PangeaRepresentation {
|
|||
originalWritten: json[_originalWrittenKey] ?? false,
|
||||
speechToText: json[_speechToTextKey] == null
|
||||
? null
|
||||
: SpeechToTextModel.fromJson(json[_speechToTextKey]),
|
||||
: SpeechToTextResponseModel.fromJson(json[_speechToTextKey]),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ class PangeaRepresentation {
|
|||
|
||||
bool get langCodeMatchesL2 =>
|
||||
langCode.split("-").first ==
|
||||
MatrixState.pangeaController.languageController.userL2?.langCodeShort;
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort;
|
||||
|
||||
/// Get construct uses for the message that weren't captured during language assistance.
|
||||
/// Takes a list of tokens and a choreo record, which is searched
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
|
||||
class SttTranslationModel {
|
||||
final String translation;
|
||||
final String langCode;
|
||||
|
|
@ -8,9 +12,14 @@ class SttTranslationModel {
|
|||
});
|
||||
|
||||
factory SttTranslationModel.fromJson(Map<String, dynamic> json) {
|
||||
final content = json.tryGetMap(PangeaEventTypes.sttTranslation);
|
||||
if (content == null) {
|
||||
throw Exception("STT Translation content is null");
|
||||
}
|
||||
|
||||
return SttTranslationModel(
|
||||
translation: json['translation'] as String,
|
||||
langCode: json['lang_code'] as String,
|
||||
translation: content['translation'] as String,
|
||||
langCode: content['lang_code'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,47 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/language_detection_request.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/language_detection_response.dart';
|
||||
|
||||
class _LanguageDetectionCacheItem {
|
||||
final Future<LanguageDetectionResponse> data;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _LanguageDetectionCacheItem({
|
||||
required this.data,
|
||||
required this.timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
class LanguageDetectionRepo {
|
||||
static Future<LanguageDetectionResponse> get(
|
||||
String? accessToken, {
|
||||
required LanguageDetectionRequest request,
|
||||
}) async {
|
||||
static final Map<String, _LanguageDetectionCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<LanguageDetectionResponse>> get(
|
||||
String accessToken,
|
||||
LanguageDetectionRequest request,
|
||||
) {
|
||||
final cached = _getCached(request);
|
||||
if (cached != null) {
|
||||
return _getResult(request, cached);
|
||||
}
|
||||
|
||||
final future = _fetch(accessToken, request);
|
||||
_setCached(request, future);
|
||||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<LanguageDetectionResponse> _fetch(
|
||||
String accessToken,
|
||||
LanguageDetectionRequest request,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
accessToken: accessToken,
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
|
|
@ -22,12 +51,54 @@ class LanguageDetectionRepo {
|
|||
body: request.toJson(),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> json =
|
||||
jsonDecode(utf8.decode(res.bodyBytes).toString());
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to detect language: ${res.statusCode} ${res.reasonPhrase}',
|
||||
);
|
||||
}
|
||||
|
||||
final LanguageDetectionResponse response =
|
||||
LanguageDetectionResponse.fromJson(json);
|
||||
|
||||
return response;
|
||||
return LanguageDetectionResponse.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes)),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Result<LanguageDetectionResponse>> _getResult(
|
||||
LanguageDetectionRequest request,
|
||||
Future<LanguageDetectionResponse> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
return Result.value(res);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: request.toJson(),
|
||||
);
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LanguageDetectionResponse>? _getCached(
|
||||
LanguageDetectionRequest request,
|
||||
) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
for (final key in cacheKeys) {
|
||||
if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) {
|
||||
_cache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
return _cache[request.hashCode.toString()]?.data;
|
||||
}
|
||||
|
||||
static void _setCached(
|
||||
LanguageDetectionRequest request,
|
||||
Future<LanguageDetectionResponse> response,
|
||||
) =>
|
||||
_cache[request.hashCode.toString()] = _LanguageDetectionCacheItem(
|
||||
data: response,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,16 @@ class LanguageDetectionRequest {
|
|||
'sender_l2': senderl2,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => text.hashCode ^ senderl1.hashCode ^ senderl2.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is LanguageDetectionRequest &&
|
||||
other.text == text &&
|
||||
other.senderl1 == senderl1 &&
|
||||
other.senderl2 == senderl2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/models/language_detection_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
|
||||
class TokensRequestModel {
|
||||
/// the text to be tokenized
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/space_code.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/space_code_extension.dart';
|
||||
|
||||
extension JoinRuleExtension on Client {
|
||||
Future<StateEvent> pangeaJoinRules(
|
||||
|
|
@ -11,7 +11,7 @@ extension JoinRuleExtension on Client {
|
|||
}) async {
|
||||
String? joinCode;
|
||||
try {
|
||||
joinCode = await SpaceCodeUtil.generateSpaceCode(this);
|
||||
joinCode = await requestSpaceCode();
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
@ -42,7 +42,7 @@ extension JoinRuleExtensionOnRoom on Room {
|
|||
final currentJoinRules = getState(EventTypes.RoomJoinRules)?.content ?? {};
|
||||
if (currentJoinRules[ModelKey.accessCode] != null) return;
|
||||
|
||||
final joinCode = await SpaceCodeUtil.generateSpaceCode(client);
|
||||
final joinCode = await client.requestSpaceCode();
|
||||
currentJoinRules[ModelKey.accessCode] = joinCode;
|
||||
|
||||
await client.setRoomStateWithKey(
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'
|
|||
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../choreographer/choreo_record_model.dart';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:matrix/matrix_api_lite/generated/model.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
|
||||
extension PangeaRoomsChunk on PublicRoomsChunk {
|
||||
/// Use Random with a seed to get the default
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
|
||||
class ResetInstructionsListTile extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ enum L2SupportEnum {
|
|||
na,
|
||||
alpha,
|
||||
beta,
|
||||
full,
|
||||
}
|
||||
full;
|
||||
|
||||
extension L2SupportEnumExtension on L2SupportEnum {
|
||||
String get storageString {
|
||||
switch (this) {
|
||||
case L2SupportEnum.na:
|
||||
25
lib/pangea/languages/language_arc_model.dart
Normal file
25
lib/pangea/languages/language_arc_model.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
|
||||
class LanguageArc {
|
||||
final LanguageModel l1;
|
||||
final LanguageModel l2;
|
||||
|
||||
LanguageArc({
|
||||
required this.l1,
|
||||
required this.l2,
|
||||
});
|
||||
|
||||
factory LanguageArc.fromJson(Map<String, dynamic> json) {
|
||||
return LanguageArc(
|
||||
l1: LanguageModel.fromJson(json['l1'] as Map<String, dynamic>),
|
||||
l2: LanguageModel.fromJson(json['l2'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'l1': l1.toJson(),
|
||||
'l2': l2.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
import 'package:fluffychat/pangea/events/models/language_detection_model.dart';
|
||||
|
||||
class LanguageKeys {
|
||||
static const unknownLanguage = "unk";
|
||||
static const mixedLanguage = "mixed";
|
||||
static const defaultLanguage = "en";
|
||||
static const multiLanguage = "multi";
|
||||
}
|
||||
|
||||
class PrefKey {
|
||||
static const lastFetched = 'p_lang_lastfetched';
|
||||
static const languagesKey = 'p_lang_flag';
|
||||
}
|
||||
class LanguageConstants {
|
||||
static final List<String> rtlLanguageCodes = [
|
||||
'ar',
|
||||
'arc',
|
||||
'dv',
|
||||
'fa',
|
||||
'ha',
|
||||
'he',
|
||||
'khw',
|
||||
'ks',
|
||||
'ku',
|
||||
'ps',
|
||||
'ur',
|
||||
'yi',
|
||||
];
|
||||
|
||||
const LanguageDetectionModel unknownLanguageDetection = LanguageDetectionModel(
|
||||
langCode: LanguageKeys.unknownLanguage,
|
||||
confidence: 0.5,
|
||||
);
|
||||
|
||||
class FallbackLanguage {
|
||||
static List<Map<String, dynamic>> get languageList => [
|
||||
{
|
||||
"language_code": "ab",
|
||||
|
|
@ -3,9 +3,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/languages/l2_support_enum.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
|
||||
class LanguageModel {
|
||||
final String langCode;
|
||||
|
|
@ -44,7 +43,7 @@ class LanguageModel {
|
|||
);
|
||||
}
|
||||
|
||||
toJson() => {
|
||||
Map<String, dynamic> toJson() => {
|
||||
'language_code': langCode,
|
||||
'language_name': displayName,
|
||||
'script': script,
|
||||
|
|
@ -303,7 +302,7 @@ class LanguageModel {
|
|||
String get langCodeShort => langCode.split('-').first;
|
||||
|
||||
TextDirection get _defaultTextDirection {
|
||||
return PLanguageStore.rtlLanguageCodes.contains(langCodeShort)
|
||||
return LanguageConstants.rtlLanguageCodes.contains(langCodeShort)
|
||||
? TextDirection.rtl
|
||||
: TextDirection.ltr;
|
||||
}
|
||||
|
|
@ -338,27 +337,3 @@ class LanguageModel {
|
|||
@override
|
||||
int get hashCode => langCode.hashCode;
|
||||
}
|
||||
|
||||
class LanguageArc {
|
||||
final LanguageModel l1;
|
||||
final LanguageModel l2;
|
||||
|
||||
LanguageArc({
|
||||
required this.l1,
|
||||
required this.l2,
|
||||
});
|
||||
|
||||
factory LanguageArc.fromJson(Map<String, dynamic> json) {
|
||||
return LanguageArc(
|
||||
l1: LanguageModel.fromJson(json['l1'] as Map<String, dynamic>),
|
||||
l2: LanguageModel.fromJson(json['l2'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'l1': l1.toJson(),
|
||||
'l2': l2.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
53
lib/pangea/languages/language_repo.dart
Normal file
53
lib/pangea/languages/language_repo.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import '../common/network/requests.dart';
|
||||
|
||||
class LanguageRepo {
|
||||
static Future<Result<List<LanguageModel>>> get() async {
|
||||
try {
|
||||
final languages = await _fetch();
|
||||
return Result.value(languages);
|
||||
} catch (e) {
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<LanguageModel>> _fetch() async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
);
|
||||
|
||||
final Response res = await req.get(
|
||||
url: PApiUrls.getLanguages,
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to fetch languages: ${res.statusCode} ${res.reasonPhrase}',
|
||||
);
|
||||
}
|
||||
|
||||
return (jsonDecode(utf8.decode(res.bodyBytes)) as List)
|
||||
.map((e) {
|
||||
try {
|
||||
return LanguageModel.fromJson(e);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack, data: e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.whereType<LanguageModel>()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
27
lib/pangea/languages/language_service.dart
Normal file
27
lib/pangea/languages/language_service.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../learning_settings/p_language_dialog.dart';
|
||||
|
||||
class LanguageService {
|
||||
static void showDialogOnEmptyLanguage(
|
||||
BuildContext context,
|
||||
Function callback,
|
||||
) {
|
||||
if (!MatrixState.pangeaController.userController.languagesSet) {
|
||||
pLanguageDialog(context, callback);
|
||||
}
|
||||
}
|
||||
|
||||
static LanguageModel? get systemLanguage {
|
||||
if (Platform.localeName.length < 2) return null;
|
||||
final String systemLang = Platform.localeName.substring(0, 2);
|
||||
return PLanguageStore.byLangCode(systemLang);
|
||||
}
|
||||
}
|
||||
145
lib/pangea/languages/p_language_store.dart
Normal file
145
lib/pangea/languages/p_language_store.dart
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_repo.dart';
|
||||
|
||||
class PrefKey {
|
||||
static const lastFetched = 'p_lang_lastfetched';
|
||||
static const languagesKey = 'p_lang_flag';
|
||||
}
|
||||
|
||||
class PLanguageStore {
|
||||
PLanguageStore() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
static List<LanguageModel> _langList = [];
|
||||
|
||||
List<LanguageModel> get targetOptions =>
|
||||
_langList.where((element) => element.l2).toList();
|
||||
|
||||
List<LanguageModel> get baseOptions => _langList.toList();
|
||||
|
||||
List<LanguageModel> get unlocalizedTargetOptions => _langList
|
||||
.where(
|
||||
(element) =>
|
||||
element.l2 &&
|
||||
(element.langCode == element.langCodeShort ||
|
||||
!element.displayName.contains("(")),
|
||||
)
|
||||
.toList();
|
||||
|
||||
static Future<void> initialize({forceRefresh = false}) async {
|
||||
_langList = await _getCachedLanguages();
|
||||
final isOutdated = await _shouldFetch;
|
||||
final shouldFetch = forceRefresh ||
|
||||
isOutdated ||
|
||||
_langList.isEmpty ||
|
||||
_langList.every((lang) => !lang.l2);
|
||||
|
||||
if (shouldFetch) {
|
||||
final result = await LanguageRepo.get();
|
||||
_langList = result.isValue
|
||||
? result.asValue!.value
|
||||
: LanguageConstants.languageList
|
||||
.map((e) => LanguageModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
await _MyShared.saveJson(PrefKey.languagesKey, {
|
||||
PrefKey.languagesKey: _langList.map((e) => e.toJson()).toList(),
|
||||
});
|
||||
|
||||
await _MyShared.saveString(
|
||||
PrefKey.lastFetched,
|
||||
DateTime.now().toIso8601String(),
|
||||
);
|
||||
|
||||
_langList.removeWhere(
|
||||
(element) => element.langCode == LanguageKeys.unknownLanguage,
|
||||
);
|
||||
_langList = _langList.toSet().toList();
|
||||
_langList.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||
}
|
||||
|
||||
static Future<bool> get _shouldFetch async {
|
||||
final String? dateString = await _MyShared.readString(PrefKey.lastFetched);
|
||||
if (dateString == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final DateTime? lastFetchedDate = DateTime.tryParse(dateString);
|
||||
if (lastFetchedDate == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final DateTime targetDate = DateTime(2025, 2, 26);
|
||||
if (lastFetchedDate.isBefore(targetDate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int lastFetched = lastFetchedDate.millisecondsSinceEpoch;
|
||||
final int now = DateTime.now().millisecondsSinceEpoch;
|
||||
const int fetchIntervalInMilliseconds = 86534601;
|
||||
return (now - lastFetched) >= fetchIntervalInMilliseconds;
|
||||
}
|
||||
|
||||
static Future<List<LanguageModel>> _getCachedLanguages() async {
|
||||
final Map<dynamic, dynamic>? languagesMap = await _MyShared.readJson(
|
||||
PrefKey.languagesKey,
|
||||
);
|
||||
|
||||
if (languagesMap == null) return [];
|
||||
try {
|
||||
return (languagesMap[PrefKey.languagesKey] as List)
|
||||
.map((e) => LanguageModel.fromJson(e))
|
||||
.toList();
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static LanguageModel? byLangCode(String langCode) =>
|
||||
_langList.firstWhereOrNull(
|
||||
(element) => element.langCode == langCode,
|
||||
);
|
||||
}
|
||||
|
||||
class _MyShared {
|
||||
static saveString(String key, String value) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(key, value);
|
||||
}
|
||||
|
||||
static Future<String?>? readString(String key) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? source = prefs.getString(key);
|
||||
return source;
|
||||
}
|
||||
|
||||
static saveJson(String key, Map value) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(key, json.encode(value));
|
||||
}
|
||||
|
||||
static Future<Map?>? readJson(String key) async {
|
||||
try {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? source = prefs.getString(key);
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
final decodedJson = json.decoder.convert(source);
|
||||
//var decodedJson = json.decode(source);
|
||||
return decodedJson;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import '../widgets/p_language_dialog.dart';
|
||||
|
||||
class LanguageController {
|
||||
late PangeaController _pangeaController;
|
||||
|
||||
LanguageController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
}
|
||||
//show diloag when user does not have languages selected
|
||||
showDialogOnEmptyLanguage(BuildContext context, Function callback) {
|
||||
if (!languagesSet) {
|
||||
pLanguageDialog(context, callback);
|
||||
}
|
||||
}
|
||||
|
||||
bool get languagesSet =>
|
||||
_userL1Code != null &&
|
||||
_userL2Code != null &&
|
||||
_userL1Code!.isNotEmpty &&
|
||||
_userL2Code!.isNotEmpty &&
|
||||
_userL1Code != LanguageKeys.unknownLanguage &&
|
||||
_userL2Code != LanguageKeys.unknownLanguage;
|
||||
|
||||
LanguageModel? get systemLanguage {
|
||||
if (Platform.localeName.length < 2) return null;
|
||||
final String systemLang = Platform.localeName.substring(0, 2);
|
||||
return PLanguageStore.byLangCode(systemLang);
|
||||
}
|
||||
|
||||
String? get _userL1Code {
|
||||
final source =
|
||||
_pangeaController.userController.profile.userSettings.sourceLanguage;
|
||||
return source == null || source.isEmpty ? systemLanguage?.langCode : source;
|
||||
}
|
||||
|
||||
String? get _userL2Code {
|
||||
final target =
|
||||
_pangeaController.userController.profile.userSettings.targetLanguage;
|
||||
return target == null || target.isEmpty ? null : target;
|
||||
}
|
||||
|
||||
LanguageModel? get userL1 {
|
||||
if (_userL1Code == null) return null;
|
||||
final langModel = PLanguageStore.byLangCode(_userL1Code!);
|
||||
return langModel?.langCode == LanguageKeys.unknownLanguage
|
||||
? null
|
||||
: langModel;
|
||||
}
|
||||
|
||||
LanguageModel? get userL2 {
|
||||
if (_userL2Code == null) return null;
|
||||
final langModel = PLanguageStore.byLangCode(_userL2Code!);
|
||||
return langModel?.langCode == LanguageKeys.unknownLanguage
|
||||
? null
|
||||
: langModel;
|
||||
}
|
||||
|
||||
String? activeL1Code() {
|
||||
return _userL1Code;
|
||||
// final String? activeL2 = activeL2Code(roomID: roomID);
|
||||
// if (roomID == null || activeL2 != _userL1Code) {
|
||||
// return _userL1Code;
|
||||
// }
|
||||
// final LanguageSettingsModel? classContext = _pangeaController
|
||||
// .matrixState.client
|
||||
// .getRoomById(roomID)
|
||||
// ?.firstLanguageSettings;
|
||||
// final String? classL1 = classContext?.dominantLanguage;
|
||||
// if (classL1 == LanguageKeys.mixedLanguage ||
|
||||
// classL1 == LanguageKeys.multiLanguage ||
|
||||
// classL1 == null) {
|
||||
// if (_userL2Code != _userL1Code) {
|
||||
// return _userL2Code;
|
||||
// }
|
||||
// return LanguageKeys.unknownLanguage;
|
||||
// }
|
||||
// return classL1;
|
||||
}
|
||||
|
||||
/// Class languages override user languages within a class context
|
||||
String? activeL2Code() {
|
||||
return _userL2Code;
|
||||
// if (roomID == null) {
|
||||
// return _userL2Code;
|
||||
// }
|
||||
// final LanguageSettingsModel? classContext = _pangeaController
|
||||
// .matrixState.client
|
||||
// .getRoomById(roomID)
|
||||
// ?.firstLanguageSettings;
|
||||
// return classContext?.targetLanguage ?? _userL2Code;
|
||||
}
|
||||
|
||||
LanguageModel? activeL1Model() {
|
||||
return userL1;
|
||||
// final activeL1 = activeL1Code(roomID: roomID);
|
||||
// return activeL1 != null ? PangeaLanguage.byLangCode(activeL1) : null;
|
||||
}
|
||||
|
||||
LanguageModel? activeL2Model() {
|
||||
return userL2;
|
||||
// final activeL2 = activeL2Code(roomID: roomID);
|
||||
// final model = activeL2 != null ? PangeaLanguage.byLangCode(activeL2) : null;
|
||||
// return model;
|
||||
}
|
||||
|
||||
bool get showTranscription =>
|
||||
(_pangeaController.languageController.userL1 != null &&
|
||||
_pangeaController.languageController.userL2 != null &&
|
||||
_pangeaController.languageController.userL1?.script !=
|
||||
_pangeaController.languageController.userL2?.script) ||
|
||||
(_pangeaController.languageController.userL1?.script !=
|
||||
LanguageKeys.unknownLanguage ||
|
||||
_pangeaController.languageController.userL2?.script ==
|
||||
LanguageKeys.unknownLanguage);
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import 'package:dropdown_button2/dropdown_button2.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/country_display.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/country_display.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class CountryPickerDropdown extends StatefulWidget {
|
||||
|
|
@ -2,9 +2,15 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
|
||||
enum LanguageLevelTypeEnum { preA1, a1, a2, b1, b2, c1, c2 }
|
||||
enum LanguageLevelTypeEnum {
|
||||
preA1,
|
||||
a1,
|
||||
a2,
|
||||
b1,
|
||||
b2,
|
||||
c1,
|
||||
c2;
|
||||
|
||||
extension LanguageLevelTypeEnumExtension on LanguageLevelTypeEnum {
|
||||
// Makes enum a string
|
||||
String get string {
|
||||
switch (this) {
|
||||
|
|
@ -6,12 +6,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_service.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../../config/themes.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../config/themes.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'p_language_dropdown.dart';
|
||||
import 'p_question_container.dart';
|
||||
|
||||
|
|
@ -21,10 +22,9 @@ Future<void> pLanguageDialog(
|
|||
) async {
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
//PTODO: if source language not set by user, default to languge from device settings
|
||||
final LanguageModel? userL1 = pangeaController.languageController.userL1;
|
||||
final LanguageModel? userL2 = pangeaController.languageController.userL2;
|
||||
final LanguageModel? systemLanguage =
|
||||
pangeaController.languageController.systemLanguage;
|
||||
final LanguageModel? userL1 = pangeaController.userController.userL1;
|
||||
final LanguageModel? userL2 = pangeaController.userController.userL2;
|
||||
final LanguageModel? systemLanguage = LanguageService.systemLanguage;
|
||||
|
||||
LanguageModel? selectedSourceLanguage = systemLanguage;
|
||||
if (userL1 != null && userL1.langCode != LanguageKeys.unknownLanguage) {
|
||||
|
|
@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'flag.dart';
|
||||
import 'package:fluffychat/pangea/languages/l2_support_enum.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
||||
class PLanguageDropdown extends StatefulWidget {
|
||||
final List<LanguageModel> languages;
|
||||
|
|
@ -210,8 +210,9 @@ class LanguageDropDownEntry extends StatelessWidget {
|
|||
children: [
|
||||
Opacity(
|
||||
opacity: enabled ? 1 : 0.5,
|
||||
child: LanguageFlag(
|
||||
language: languageModel,
|
||||
child: Avatar(
|
||||
name: languageModel.langCode,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import '../../common/network/requests.dart';
|
||||
|
||||
class LanguageRepo {
|
||||
static Future<List<LanguageModel>> fetchLanguages() async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
);
|
||||
|
||||
List<dynamic> languageResp = [];
|
||||
try {
|
||||
final Response res = await req.get(url: PApiUrls.getLanguages);
|
||||
languageResp = jsonDecode(utf8.decode(res.bodyBytes).toString()) as List;
|
||||
} catch (e) {
|
||||
languageResp = FallbackLanguage.languageList;
|
||||
}
|
||||
|
||||
final List<LanguageModel> langFlag = languageResp.map((e) {
|
||||
try {
|
||||
return LanguageModel.fromJson(e);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack, data: e);
|
||||
return LanguageModel.unknown;
|
||||
}
|
||||
}).toList();
|
||||
return langFlag;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,25 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:app_settings/app_settings.dart';
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instruction_settings.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning_view.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_service.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning_view.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/user_model.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -203,6 +207,76 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void showKeyboardSettingsDialog() {
|
||||
String title;
|
||||
String? steps;
|
||||
String? description;
|
||||
String buttonText;
|
||||
VoidCallback buttonAction;
|
||||
|
||||
if (kIsWeb) {
|
||||
title = L10n.of(context).autocorrectNotAvailable; // Default
|
||||
buttonText = 'OK';
|
||||
buttonAction = () {
|
||||
Navigator.of(context).pop();
|
||||
};
|
||||
} else if (Platform.isIOS) {
|
||||
title = L10n.of(context).enableAutocorrectPopupTitle;
|
||||
steps = L10n.of(context).enableAutocorrectPopupSteps;
|
||||
description = L10n.of(context).enableAutocorrectPopupDescription;
|
||||
buttonText = L10n.of(context).settings;
|
||||
buttonAction = () {
|
||||
AppSettings.openAppSettings();
|
||||
};
|
||||
} else {
|
||||
title = L10n.of(context).downloadGboardTitle;
|
||||
steps = L10n.of(context).downloadGboardSteps;
|
||||
description = L10n.of(context).downloadGboardDescription;
|
||||
buttonText = L10n.of(context).downloadGboard;
|
||||
buttonAction = () {
|
||||
launchUrlString(
|
||||
'https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin',
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(L10n.of(context).enableAutocorrectWarning),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(title),
|
||||
if (steps != null)
|
||||
Text(
|
||||
steps,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
if (description != null) Text(description),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
onPressed: buttonAction,
|
||||
child: Text(buttonText),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
LanguageModel? get _targetLanguage =>
|
||||
_profile.userSettings.targetLanguage != null
|
||||
? PLanguageStore.byLangCode(_profile.userSettings.targetLanguage!)
|
||||
|
|
@ -234,8 +308,7 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
_profile.userSettings.targetLanguage != null && _targetLanguage != null;
|
||||
|
||||
LanguageModel? get selectedSourceLanguage {
|
||||
return _selectedBaseLanguage ??
|
||||
pangeaController.languageController.systemLanguage;
|
||||
return _selectedBaseLanguage ?? LanguageService.systemLanguage;
|
||||
}
|
||||
|
||||
LanguageModel? get selectedTargetLanguage {
|
||||
|
|
@ -255,8 +328,8 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
? PLanguageStore.byLangCode(_profile.userSettings.targetLanguage!)
|
||||
: null;
|
||||
|
||||
LanguageModel? get userL1 => pangeaController.languageController.userL1;
|
||||
LanguageModel? get userL2 => pangeaController.languageController.userL2;
|
||||
LanguageModel? get userL1 => pangeaController.userController.userL1;
|
||||
LanguageModel? get userL2 => pangeaController.userController.userL2;
|
||||
|
||||
bool get publicProfile => _profile.userSettings.publicProfile ?? false;
|
||||
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:app_settings/app_settings.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
|
|
@ -13,12 +7,12 @@ import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
|
||||
import 'package:fluffychat/pangea/instructions/reset_instructions_list_tile.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/country_picker_tile.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/p_settings_switch_list_tile.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/country_picker_tile.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/p_settings_switch_list_tile.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -26,76 +20,6 @@ class SettingsLearningView extends StatelessWidget {
|
|||
final SettingsLearningController controller;
|
||||
const SettingsLearningView(this.controller, {super.key});
|
||||
|
||||
void _showKeyboardSettingsDialog(BuildContext context) {
|
||||
String title;
|
||||
String? steps;
|
||||
String? description;
|
||||
String buttonText;
|
||||
VoidCallback buttonAction;
|
||||
|
||||
if (kIsWeb) {
|
||||
title = L10n.of(context).autocorrectNotAvailable; // Default
|
||||
buttonText = 'OK';
|
||||
buttonAction = () {
|
||||
Navigator.of(context).pop();
|
||||
};
|
||||
} else if (Platform.isIOS) {
|
||||
title = L10n.of(context).enableAutocorrectPopupTitle;
|
||||
steps = L10n.of(context).enableAutocorrectPopupSteps;
|
||||
description = L10n.of(context).enableAutocorrectPopupDescription;
|
||||
buttonText = L10n.of(context).settings;
|
||||
buttonAction = () {
|
||||
AppSettings.openAppSettings();
|
||||
};
|
||||
} else {
|
||||
title = L10n.of(context).downloadGboardTitle;
|
||||
steps = L10n.of(context).downloadGboardSteps;
|
||||
description = L10n.of(context).downloadGboardDescription;
|
||||
buttonText = L10n.of(context).downloadGboard;
|
||||
buttonAction = () {
|
||||
launchUrlString(
|
||||
'https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin',
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(L10n.of(context).enableAutocorrectWarning),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(title),
|
||||
if (steps != null)
|
||||
Text(
|
||||
steps,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
if (description != null) Text(description),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
onPressed: buttonAction,
|
||||
child: Text(buttonText),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
|
|
@ -255,9 +179,8 @@ class SettingsLearningView extends StatelessWidget {
|
|||
value,
|
||||
);
|
||||
if (value) {
|
||||
_showKeyboardSettingsDialog(
|
||||
context,
|
||||
);
|
||||
controller
|
||||
.showKeyboardSettingsDialog();
|
||||
}
|
||||
},
|
||||
enabled: true,
|
||||
70
lib/pangea/learning_settings/tool_settings_enum.dart
Normal file
70
lib/pangea/learning_settings/tool_settings_enum.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum ToolSetting {
|
||||
interactiveTranslator,
|
||||
interactiveGrammar,
|
||||
immersionMode,
|
||||
definitions,
|
||||
autoIGC,
|
||||
enableTTS,
|
||||
enableAutocorrect;
|
||||
|
||||
String toolName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
return L10n.of(context).interactiveTranslatorSliderHeader;
|
||||
case ToolSetting.interactiveGrammar:
|
||||
return L10n.of(context).interactiveGrammarSliderHeader;
|
||||
case ToolSetting.immersionMode:
|
||||
return L10n.of(context).toggleImmersionMode;
|
||||
case ToolSetting.definitions:
|
||||
return L10n.of(context).definitionsToolName;
|
||||
case ToolSetting.autoIGC:
|
||||
return L10n.of(context).autoIGCToolName;
|
||||
case ToolSetting.enableTTS:
|
||||
return L10n.of(context).enableTTSToolName;
|
||||
case ToolSetting.enableAutocorrect:
|
||||
return L10n.of(context).enableAutocorrectToolName;
|
||||
}
|
||||
}
|
||||
|
||||
//use l10n to get tool name
|
||||
String toolDescription(BuildContext context) {
|
||||
switch (this) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
return L10n.of(context).itToggleDescription;
|
||||
case ToolSetting.interactiveGrammar:
|
||||
return L10n.of(context).igcToggleDescription;
|
||||
case ToolSetting.immersionMode:
|
||||
return L10n.of(context).toggleImmersionModeDesc;
|
||||
case ToolSetting.definitions:
|
||||
return L10n.of(context).definitionsToolDescription;
|
||||
case ToolSetting.autoIGC:
|
||||
return L10n.of(context).autoIGCToolDescription;
|
||||
case ToolSetting.enableTTS:
|
||||
return L10n.of(context).enableTTSToolDescription;
|
||||
case ToolSetting.enableAutocorrect:
|
||||
return L10n.of(context).enableAutocorrectDescription;
|
||||
}
|
||||
}
|
||||
|
||||
bool get isAvailableSetting {
|
||||
switch (this) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
case ToolSetting.interactiveGrammar:
|
||||
case ToolSetting.definitions:
|
||||
case ToolSetting.immersionMode:
|
||||
return false;
|
||||
case ToolSetting.autoIGC:
|
||||
case ToolSetting.enableTTS:
|
||||
case ToolSetting.enableAutocorrect:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool get enabled =>
|
||||
MatrixState.pangeaController.userController.isToolEnabled(this);
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/repo/language_repo.dart';
|
||||
import 'shared_prefs.dart';
|
||||
|
||||
class PLanguageStore {
|
||||
PLanguageStore() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
static List<LanguageModel> _langList = [];
|
||||
|
||||
List<LanguageModel> get langList => _langList;
|
||||
|
||||
List<LanguageModel> get targetOptions =>
|
||||
_langList.where((element) => element.l2).toList();
|
||||
|
||||
List<LanguageModel> get unlocalizedTargetOptions => _langList
|
||||
.where(
|
||||
(element) =>
|
||||
element.l2 &&
|
||||
(element.langCode == element.langCodeShort ||
|
||||
!element.displayName.contains("(")),
|
||||
)
|
||||
.toList();
|
||||
|
||||
List<LanguageModel> get baseOptions => _langList.toList();
|
||||
|
||||
static Future<void> initialize({forceRefresh = false}) async {
|
||||
try {
|
||||
_langList = await _getCachedLanguages();
|
||||
if (forceRefresh ||
|
||||
await _shouldFetch ||
|
||||
_langList.isEmpty ||
|
||||
_langList.every((lang) => !lang.l2)) {
|
||||
_langList = await LanguageRepo.fetchLanguages();
|
||||
|
||||
await _saveLanguages(_langList);
|
||||
await saveLastFetchDate();
|
||||
}
|
||||
_langList.removeWhere(
|
||||
(element) => element.langCode == LanguageKeys.unknownLanguage,
|
||||
);
|
||||
// remove any duplicates
|
||||
_langList = _langList.toSet().toList();
|
||||
|
||||
_langList.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: stack,
|
||||
data: {
|
||||
"langList": _langList.map((e) => e.toJson()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static saveLastFetchDate() async {
|
||||
final String now = DateTime.now().toIso8601String();
|
||||
await MyShared.saveString(PrefKey.lastFetched, now);
|
||||
}
|
||||
|
||||
static Future<bool> get _shouldFetch async {
|
||||
final String? dateString = await MyShared.readString(PrefKey.lastFetched);
|
||||
if (dateString == null) {
|
||||
return true;
|
||||
}
|
||||
// return true;
|
||||
final DateTime lastFetchedDate = DateTime.parse(dateString);
|
||||
final DateTime targetDate = DateTime(2025, 2, 26);
|
||||
if (lastFetchedDate.isBefore(targetDate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int lastFetched = DateTime.parse(dateString).millisecondsSinceEpoch;
|
||||
final int now = DateTime.now().millisecondsSinceEpoch;
|
||||
const int fetchIntervalInMilliseconds = 86534601;
|
||||
return (now - lastFetched) >= fetchIntervalInMilliseconds ? true : false;
|
||||
}
|
||||
|
||||
static Future<void> _saveLanguages(List<LanguageModel> languages) async {
|
||||
final Map languagesMaps = {
|
||||
PrefKey.languagesKey: languages.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
await MyShared.saveJson(PrefKey.languagesKey, languagesMaps);
|
||||
}
|
||||
|
||||
static Future<List<LanguageModel>> _getCachedLanguages() async {
|
||||
final Map<dynamic, dynamic>? languagesMap =
|
||||
await MyShared.readJson(PrefKey.languagesKey);
|
||||
if (languagesMap == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<LanguageModel> languages = [];
|
||||
final List mapList = languagesMap[PrefKey.languagesKey] as List;
|
||||
for (final element in mapList) {
|
||||
languages.add(LanguageModel.fromJson(element));
|
||||
}
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
static LanguageModel? byLangCode(String langCode) {
|
||||
for (final element in _langList) {
|
||||
if (element.langCode == langCode) return element;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static final List<String> rtlLanguageCodes = [
|
||||
'ar',
|
||||
'arc',
|
||||
'dv',
|
||||
'fa',
|
||||
'ha',
|
||||
'he',
|
||||
'khw',
|
||||
'ks',
|
||||
'ku',
|
||||
'ps',
|
||||
'ur',
|
||||
'yi',
|
||||
];
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class MyShared {
|
||||
static saveString(String key, String value) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(key, value);
|
||||
}
|
||||
|
||||
static Future<String?>? readString(String key) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? source = prefs.getString(key);
|
||||
return source;
|
||||
}
|
||||
|
||||
static saveJson(String key, Map value) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(key, json.encode(value));
|
||||
}
|
||||
|
||||
static Future<Map?>? readJson(String key) async {
|
||||
try {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? source = prefs.getString(key);
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
final decodedJson = json.decoder.convert(source);
|
||||
//var decodedJson = json.decode(source);
|
||||
return decodedJson;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import '../models/language_model.dart';
|
||||
|
||||
class LanguageFlag extends StatelessWidget {
|
||||
final LanguageModel? language;
|
||||
final double size;
|
||||
const LanguageFlag({
|
||||
super.key,
|
||||
required this.language,
|
||||
this.size = 30,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Avatar(
|
||||
name: language?.langCode,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue