From 5347b4764f1f4331888e5bfb8f6ffa23a4d1b04e Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:40:45 -0500 Subject: [PATCH] feat: add submit button to learning settings popup (#1717) --- lib/pages/chat/chat.dart | 9 +- .../instructions/instruction_settings.dart | 4 + .../pages/settings_learning.dart | 151 +++++----- .../pages/settings_learning_view.dart | 282 +++++++++++------- .../widgets/country_picker_tile.dart | 7 +- .../widgets/language_tile.dart | 90 ------ lib/pangea/user/models/user_model.dart | 34 +++ 7 files changed, 284 insertions(+), 293 deletions(-) delete mode 100644 lib/pangea/learning_settings/widgets/language_tile.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6002813f6..9a15720fd 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -352,9 +352,12 @@ class ChatController extends State _levelSubscription = pangeaController.getAnalytics.analyticsStream.stream .where((update) => update.levelUp) .listen( - (update) => LevelUpUtil.showLevelUpDialog( - pangeaController.getAnalytics.constructListModel.level, - context, + (update) => Future.delayed( + const Duration(milliseconds: 500), + () => LevelUpUtil.showLevelUpDialog( + pangeaController.getAnalytics.constructListModel.level, + context, + ), ), ); // Pangea# diff --git a/lib/pangea/instructions/instruction_settings.dart b/lib/pangea/instructions/instruction_settings.dart index 420c70b18..e75988261 100644 --- a/lib/pangea/instructions/instruction_settings.dart +++ b/lib/pangea/instructions/instruction_settings.dart @@ -50,4 +50,8 @@ class InstructionSettings { void setStatus(InstructionsEnum instruction, bool status) { _instructions[instruction.toString()] = status; } + + InstructionSettings copy() { + return InstructionSettings(Map.from(_instructions)); + } } diff --git a/lib/pangea/learning_settings/pages/settings_learning.dart b/lib/pangea/learning_settings/pages/settings_learning.dart index 8cac708eb..2206851e3 100644 --- a/lib/pangea/learning_settings/pages/settings_learning.dart +++ b/lib/pangea/learning_settings/pages/settings_learning.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_en 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/language_list_util.dart'; -import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dialog.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'; @@ -23,27 +22,15 @@ class SettingsLearning extends StatefulWidget { class SettingsLearningController extends State { PangeaController pangeaController = MatrixState.pangeaController; + late Profile _profile; final tts = TtsController(); - LanguageModel? get selectedSourceLanguage { - return userL1 ?? pangeaController.languageController.systemLanguage; - } - - LanguageModel? get selectedTargetLanguage { - return userL2 ?? - ((selectedSourceLanguage?.langCode != 'en') - ? PangeaLanguage.byLangCode('en')! - : PangeaLanguage.byLangCode('es')!); - } - - LanguageModel? get userL1 => pangeaController.languageController.userL1; - LanguageModel? get userL2 => pangeaController.languageController.userL2; - final GlobalKey formKey = GlobalKey(); @override void initState() { super.initState(); + _profile = pangeaController.userController.profile.copy(); tts.setupTTS().then((_) => setState(() {})); } @@ -53,87 +40,73 @@ class SettingsLearningController extends State { super.dispose(); } + Future submit() async { + if (formKey.currentState!.validate()) { + await showFutureLoadingDialog( + context: context, + future: () async => pangeaController.userController.updateProfile( + (_) => _profile, + ), + ); + Navigator.of(context).pop(); + } + } + Future setSelectedLanguage({ LanguageModel? sourceLanguage, LanguageModel? targetLanguage, }) async { - if (targetLanguage == null && sourceLanguage == null) return; - if (!formKey.currentState!.validate()) return; - - await showFutureLoadingDialog( - context: context, - future: () async { - pangeaController.userController.updateProfile( - (profile) { - if (sourceLanguage != null) { - profile.userSettings.sourceLanguage = sourceLanguage.langCode; - } - if (targetLanguage != null) { - profile.userSettings.targetLanguage = targetLanguage.langCode; - } - return profile; - }, - waitForDataInSync: true, - ); - }, - ); + if (sourceLanguage != null) { + _profile.userSettings.sourceLanguage = sourceLanguage.langCode; + } + if (targetLanguage != null) { + _profile.userSettings.targetLanguage = targetLanguage.langCode; + } + if (mounted) setState(() {}); } void setPublicProfile(bool isPublic) { - pangeaController.userController.updateProfile( - (profile) { - // set user DOB to younger that 18 if private and older than 18 if public - profile.userSettings.publicProfile = isPublic; - return profile; - }, - ); - setState(() {}); + _profile.userSettings.publicProfile = isPublic; + if (mounted) setState(() {}); } void setCefrLevel(LanguageLevelTypeEnum? cefrLevel) { - pangeaController.userController.updateProfile( - (profile) { - profile.userSettings.cefrLevel = cefrLevel ?? LanguageLevelTypeEnum.a1; - return profile; - }, - ); - setState(() {}); - } - - void changeLanguage() { - pLanguageDialog(context, () {}).then((_) => setState(() {})); + _profile.userSettings.cefrLevel = cefrLevel ?? LanguageLevelTypeEnum.a1; + if (mounted) setState(() {}); } void changeCountry(Country country) { - pangeaController.userController.updateProfile((Profile profile) { - profile.userSettings.country = country.displayNameNoCountryCode; - return profile; - }); - setState(() {}); + _profile.userSettings.country = country.displayNameNoCountryCode; + if (mounted) setState(() {}); } void updateToolSetting(ToolSetting toolSetting, bool value) { - pangeaController.userController.updateProfile((Profile profile) { - switch (toolSetting) { - case ToolSetting.interactiveTranslator: - return profile..toolSettings.interactiveTranslator = value; - case ToolSetting.interactiveGrammar: - return profile..toolSettings.interactiveGrammar = value; - case ToolSetting.immersionMode: - return profile..toolSettings.immersionMode = value; - case ToolSetting.definitions: - return profile..toolSettings.definitions = value; - case ToolSetting.autoIGC: - return profile..toolSettings.autoIGC = value; - case ToolSetting.enableTTS: - return profile..toolSettings.enableTTS = value; - } - }); + switch (toolSetting) { + case ToolSetting.interactiveTranslator: + _profile.toolSettings.interactiveTranslator = value; + break; + case ToolSetting.interactiveGrammar: + _profile.toolSettings.interactiveGrammar = value; + break; + case ToolSetting.immersionMode: + _profile.toolSettings.immersionMode = value; + break; + case ToolSetting.definitions: + _profile.toolSettings.definitions = value; + break; + case ToolSetting.autoIGC: + _profile.toolSettings.autoIGC = value; + break; + case ToolSetting.enableTTS: + _profile.toolSettings.enableTTS = value; + break; + } + if (mounted) setState(() {}); } bool getToolSetting(ToolSetting toolSetting) { - final toolSettings = pangeaController.userController.profile.toolSettings; + final toolSettings = _profile.toolSettings; switch (toolSetting) { case ToolSetting.interactiveTranslator: return toolSettings.interactiveTranslator; @@ -150,11 +123,29 @@ class SettingsLearningController extends State { } } - bool get publicProfile => - pangeaController.userController.profile.userSettings.publicProfile; + LanguageModel? get selectedSourceLanguage { + return userL1 ?? pangeaController.languageController.systemLanguage; + } + + LanguageModel? get selectedTargetLanguage { + return userL2 ?? + ((selectedSourceLanguage?.langCode != 'en') + ? PangeaLanguage.byLangCode('en')! + : PangeaLanguage.byLangCode('es')!); + } + + LanguageModel? get userL1 => _profile.userSettings.sourceLanguage != null + ? PangeaLanguage.byLangCode(_profile.userSettings.sourceLanguage!) + : null; + LanguageModel? get userL2 => _profile.userSettings.targetLanguage != null + ? PangeaLanguage.byLangCode(_profile.userSettings.targetLanguage!) + : null; + + bool get publicProfile => _profile.userSettings.publicProfile; + + LanguageLevelTypeEnum get cefrLevel => _profile.userSettings.cefrLevel; - LanguageLevelTypeEnum get cefrLevel => - pangeaController.userController.profile.userSettings.cefrLevel; + String? get country => _profile.userSettings.country; @override Widget build(BuildContext context) { diff --git a/lib/pangea/learning_settings/pages/settings_learning_view.dart b/lib/pangea/learning_settings/pages/settings_learning_view.dart index a30ac7ec5..b2d63a82c 100644 --- a/lib/pangea/learning_settings/pages/settings_learning_view.dart +++ b/lib/pangea/learning_settings/pages/settings_learning_view.dart @@ -15,7 +15,6 @@ import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown. 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/utils/platform_infos.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; class SettingsLearningView extends StatelessWidget { @@ -45,125 +44,178 @@ class SettingsLearningView extends StatelessWidget { ), body: ListTileTheme( iconColor: Theme.of(context).textTheme.bodyLarge!.color, - child: MaxWidthBody( - withScrolling: true, - child: Form( - key: controller.formKey, + child: Form( + key: controller.formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), child: Column( children: [ - const SizedBox(height: 8.0), - PLanguageDropdown( - onChange: (lang) => - controller.setSelectedLanguage(sourceLanguage: lang), - initialLanguage: controller.selectedSourceLanguage ?? - LanguageModel.unknown, - languages: MatrixState - .pangeaController.pLanguageStore.baseOptions, - isL2List: false, - decorationText: L10n.of(context).myBaseLanguage, - validator: (lang) { - if (lang == controller.selectedTargetLanguage) { - return L10n.of(context).noIdenticalLanguages; - } - return null; - }, - ), - const SizedBox(height: 24.0), - PLanguageDropdown( - onChange: (lang) => - controller.setSelectedLanguage(targetLanguage: lang), - initialLanguage: controller.selectedTargetLanguage, - languages: MatrixState - .pangeaController.pLanguageStore.targetOptions, - isL2List: true, - decorationText: L10n.of(context).iWantToLearn, - validator: (lang) { - if (lang == controller.selectedSourceLanguage) { - return L10n.of(context).noIdenticalLanguages; - } - return null; - }, - ), - const SizedBox(height: 16.0), - CountryPickerTile(controller), - Padding( - padding: const EdgeInsets.only(top: 16.0, bottom: 24.0), - child: LanguageLevelDropdown( - initialLevel: controller.cefrLevel, - onChanged: controller.setCefrLevel, - ), - ), - const Divider(height: 1), - ListTile( - title: - Text(L10n.of(context).toggleToolSettingsDescription), - ), - for (final toolSetting in ToolSetting.values - .where((tool) => tool.isAvailableSetting)) - Column( - children: [ - ProfileSettingsSwitchListTile.adaptive( - defaultValue: - controller.getToolSetting(toolSetting), - title: toolSetting.toolName(context), - subtitle: toolSetting == ToolSetting.enableTTS && - !controller.tts.isLanguageFullySupported - ? null - : toolSetting.toolDescription(context), - onChange: (bool value) => - controller.updateToolSetting( - toolSetting, - value, - ), - enabled: toolSetting == ToolSetting.enableTTS - ? controller.tts.isLanguageFullySupported - : true, - ), - if (toolSetting == ToolSetting.enableTTS && - !controller.tts.isLanguageFullySupported) - ListTile( - trailing: const Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Icon(Icons.info_outlined), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + spacing: 16.0, + children: [ + PLanguageDropdown( + onChange: (lang) => + controller.setSelectedLanguage( + sourceLanguage: lang, + ), + initialLanguage: + controller.selectedSourceLanguage ?? + LanguageModel.unknown, + languages: MatrixState.pangeaController + .pLanguageStore.baseOptions, + isL2List: false, + decorationText: L10n.of(context).myBaseLanguage, + validator: (lang) { + if (lang == + controller.selectedTargetLanguage) { + return L10n.of(context) + .noIdenticalLanguages; + } + return null; + }, ), - subtitle: RichText( - text: TextSpan( - text: L10n.of(context).couldNotFindTTS, - style: DefaultTextStyle.of(context).style, - children: [ - if (PlatformInfos.isWindows || - PlatformInfos.isAndroid) - TextSpan( - text: L10n.of(context) - .ttsInstructionsHyperlink, - style: const TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrlString( - PlatformInfos.isWindows - ? AppConfig - .windowsTTSDownloadInstructions - : AppConfig - .androidTTSDownloadInstructions, - ); - }, - ), - ], + PLanguageDropdown( + onChange: (lang) => + controller.setSelectedLanguage( + targetLanguage: lang, ), + initialLanguage: + controller.selectedTargetLanguage, + languages: MatrixState.pangeaController + .pLanguageStore.targetOptions, + isL2List: true, + decorationText: L10n.of(context).iWantToLearn, + validator: (lang) { + if (lang == + controller.selectedSourceLanguage) { + return L10n.of(context) + .noIdenticalLanguages; + } + return null; + }, + ), + CountryPickerTile(controller), + LanguageLevelDropdown( + initialLevel: controller.cefrLevel, + onChanged: controller.setCefrLevel, + ), + const Divider(height: 1), + Column( + children: [ + ListTile( + title: Text( + L10n.of(context) + .toggleToolSettingsDescription, + ), + ), + for (final toolSetting in ToolSetting.values + .where((tool) => tool.isAvailableSetting)) + Column( + children: [ + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller + .getToolSetting(toolSetting), + title: toolSetting.toolName(context), + subtitle: toolSetting == + ToolSetting.enableTTS && + !controller.tts + .isLanguageFullySupported + ? null + : toolSetting + .toolDescription(context), + onChange: (bool value) => + controller.updateToolSetting( + toolSetting, + value, + ), + enabled: toolSetting == + ToolSetting.enableTTS + ? controller + .tts.isLanguageFullySupported + : true, + ), + if (toolSetting == + ToolSetting.enableTTS && + !controller + .tts.isLanguageFullySupported) + ListTile( + trailing: const Padding( + padding: EdgeInsets.symmetric( + horizontal: 16.0, + ), + child: Icon(Icons.info_outlined), + ), + subtitle: RichText( + text: TextSpan( + text: L10n.of(context) + .couldNotFindTTS, + style: + DefaultTextStyle.of(context) + .style, + children: [ + if (PlatformInfos.isWindows || + PlatformInfos.isAndroid) + TextSpan( + text: L10n.of(context) + .ttsInstructionsHyperlink, + style: const TextStyle( + color: Colors.blue, + fontWeight: + FontWeight.bold, + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + launchUrlString( + PlatformInfos + .isWindows + ? AppConfig + .windowsTTSDownloadInstructions + : AppConfig + .androidTTSDownloadInstructions, + ); + }, + ), + ], + ), + ), + ), + ], + ), + SwitchListTile.adaptive( + value: controller.publicProfile, + onChanged: controller.setPublicProfile, + title: Text( + L10n.of(context).publicProfileTitle, + ), + subtitle: Text( + L10n.of(context).publicProfileDesc, + ), + activeColor: AppConfig.activeToggleColor, + ), + ], ), - ), - ], + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: controller.submit, + child: Text(L10n.of(context).submit), + ), ), - SwitchListTile.adaptive( - value: controller.publicProfile, - onChanged: controller.setPublicProfile, - title: Text(L10n.of(context).publicProfileTitle), - subtitle: Text(L10n.of(context).publicProfileDesc), - activeColor: AppConfig.activeToggleColor, ), ], ), @@ -175,7 +227,7 @@ class SettingsLearningView extends StatelessWidget { return FullWidthDialog( dialogContent: dialogContent, maxWidth: 600, - maxHeight: 600, + maxHeight: 800, ); }, ); diff --git a/lib/pangea/learning_settings/widgets/country_picker_tile.dart b/lib/pangea/learning_settings/widgets/country_picker_tile.dart index b4b24048c..8c67e294e 100644 --- a/lib/pangea/learning_settings/widgets/country_picker_tile.dart +++ b/lib/pangea/learning_settings/widgets/country_picker_tile.dart @@ -7,7 +7,6 @@ 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/widgets/matrix.dart'; -import '../../user/models/user_model.dart'; class CountryPickerTile extends StatelessWidget { final SettingsLearningController learningController; @@ -17,16 +16,14 @@ class CountryPickerTile extends StatelessWidget { @override Widget build(BuildContext context) { - final Profile profile = pangeaController.userController.profile; - final String displayName = CountryDisplayUtil.countryDisplayName( - profile.userSettings.country, + learningController.country, context, ) ?? ''; final String flag = CountryDisplayUtil.flagEmoji( - profile.userSettings.country, + learningController.country, ); return ListTile( diff --git a/lib/pangea/learning_settings/widgets/language_tile.dart b/lib/pangea/learning_settings/widgets/language_tile.dart deleted file mode 100644 index 69536d73b..000000000 --- a/lib/pangea/learning_settings/widgets/language_tile.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; -import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'flag.dart'; - -//PTODO - move this to settings_learning_view.dart and make callback a setState - -class LanguageTile extends StatelessWidget { - final SettingsLearningController learningController; - final PangeaController pangeaController = MatrixState.pangeaController; - - LanguageTile(this.learningController, {super.key}); - - @override - Widget build(BuildContext context) { - final LanguageModel? sourceLanguage = - pangeaController.languageController.userL1; - - final LanguageModel? targetLanguage = - pangeaController.languageController.userL2; - - //PTODO - placeholder saying 'select your languages' - // if (targetLanguage == null || sourceLanguage == null) { - // debugger(when: kDebugMode); - // return const SizedBox(); - // } - - return ListTile( - // title: Row( - // mainAxisSize: MainAxisSize.min, - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisAlignment: MainAxisAlignment.start, - // children: const [ - // Text("Source Language"), - // SizedBox( - // width: 10, - // ), - // Icon(Icons.arrow_right_alt_outlined, size: 20), - // SizedBox( - // width: 10, - // ), - // Text("Target Language"), - // ]), - title: Text(L10n.of(context).myLanguages), - subtitle: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - LanguageFlag( - language: sourceLanguage, - ), - const SizedBox( - width: 10, - ), - Text( - sourceLanguage?.getDisplayName(context) ?? - L10n.of(context).sourceLanguage, - ), - const SizedBox( - width: 10, - ), - const Icon(Icons.arrow_right_alt_outlined, size: 20), - const SizedBox( - width: 10, - ), - LanguageFlag( - language: targetLanguage, - ), - const SizedBox( - width: 10, - ), - Text( - targetLanguage?.getDisplayName(context) ?? - L10n.of(context).targetLanguage, - ), - ], - ), - trailing: const Icon(Icons.edit_outlined), - onTap: () async { - learningController.changeLanguage(); - }, - ); - } -} diff --git a/lib/pangea/user/models/user_model.dart b/lib/pangea/user/models/user_model.dart index e971dddbd..da3cfd51c 100644 --- a/lib/pangea/user/models/user_model.dart +++ b/lib/pangea/user/models/user_model.dart @@ -128,6 +128,21 @@ class UserSettings { as String?, ); } + + UserSettings copy() { + return UserSettings( + dateOfBirth: dateOfBirth, + createdAt: createdAt, + autoPlayMessages: autoPlayMessages, + activatedFreeTrial: activatedFreeTrial, + publicProfile: publicProfile, + targetLanguage: targetLanguage, + sourceLanguage: sourceLanguage, + country: country, + hasJoinedHelpSpace: hasJoinedHelpSpace, + cefrLevel: cefrLevel, + ); + } } /// The user's language tool settings. @@ -194,6 +209,17 @@ class UserToolSettings { true, ); } + + UserToolSettings copy() { + return UserToolSettings( + interactiveTranslator: interactiveTranslator, + interactiveGrammar: interactiveGrammar, + immersionMode: immersionMode, + definitions: definitions, + autoIGC: autoIGC, + enableTTS: enableTTS, + ); + } } /// A wrapper around the matrix account data for the user profile. @@ -305,6 +331,14 @@ class Profile { instructionSettings: InstructionSettings(), ); } + + Profile copy() { + return Profile( + userSettings: userSettings.copy(), + toolSettings: toolSettings.copy(), + instructionSettings: instructionSettings.copy(), + ); + } } /// Model of data from pangea chat server. Not used anymore, in favor of matrix account data.