Merge pull request #5671 from pangeachat/5606-allowed-to-have-same-base-and-target-language-from-chat

chore: prevent users from setting their l1 to their l2
This commit is contained in:
ggurdin 2026-02-12 10:15:27 -05:00 committed by GitHub
commit 9c68ce6982
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 117 additions and 19 deletions

View file

@ -45,9 +45,12 @@ extension ActivityMenuLogic on ChatController {
return false;
}
final l1 =
MatrixState.pangeaController.userController.userL1?.langCodeShort;
final l2 =
MatrixState.pangeaController.userController.userL2?.langCodeShort;
final activityLang = room.activityPlan?.req.targetLanguage.split('-').first;
return activityLang != null && l2 != activityLang;
return activityLang != null && activityLang != l1 && l2 != activityLang;
}
}

View file

@ -14,6 +14,8 @@ import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dar
import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
import 'package:fluffychat/pangea/learning_settings/voice_dropdown.dart';
import 'package:fluffychat/pangea/user/user_model.dart' as user;
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:fluffychat/widgets/matrix.dart';
class BotChatSettingsDialog extends StatefulWidget {
@ -50,7 +52,10 @@ class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
user.Profile get _userProfile =>
MatrixState.pangeaController.userController.profile;
Future<void> _update(user.Profile Function(user.Profile) update) async {
Future<void> _update(
user.Profile Function(user.Profile) update,
VoidCallback reset,
) async {
try {
await MatrixState.pangeaController.userController
.updateProfile(update, waitForDataInSync: true)
@ -59,50 +64,117 @@ class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
context,
).client.updateBotOptions(_userProfile.userSettings);
} catch (e, s) {
reset();
ErrorHandler.logError(
e: e,
s: s,
data: {'roomId': widget.room.id, 'model': _userProfile.toJson()},
);
await _showErrorDialog(e);
}
}
Future<void> _showErrorDialog(Object error) async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog.adaptive(
title: Icon(
Icons.error_outline_outlined,
color: Theme.of(context).colorScheme.error,
size: 48,
),
content: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 256),
child: Row(
crossAxisAlignment: .center,
children: [
Expanded(
child: Text(
error.toLocalizedString(context),
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
actions: [
AdaptiveDialogAction(
onPressed: () => Navigator.of(context).pop(),
child: Text(L10n.of(context).close),
),
],
);
},
);
}
Future<void> _setLanguage(LanguageModel? lang) async {
if (lang == null ||
lang.langCode == _userProfile.userSettings.targetLanguage) {
return;
}
final prevLang = MatrixState.pangeaController.userController.userL2;
final prevVoice = _userProfile.userSettings.voice;
setState(() {
_selectedLang = lang;
_selectedVoice = null;
});
await _update((model) {
model.userSettings.targetLanguage = lang.langCode;
model.userSettings.voice = null;
return model;
});
await _update(
(model) {
model.userSettings.targetLanguage = lang.langCode;
model.userSettings.voice = null;
return model;
},
() {
if (mounted) {
setState(() {
_selectedLang = prevLang;
_selectedVoice = prevVoice;
});
}
},
);
}
Future<void> _setLevel(LanguageLevelTypeEnum? level) async {
if (level == null || level == _userProfile.userSettings.cefrLevel) return;
final prevLevel = _userProfile.userSettings.cefrLevel;
setState(() => _selectedLevel = level);
await _update((model) {
model.userSettings.cefrLevel = level;
return model;
});
await _update(
(model) {
model.userSettings.cefrLevel = level;
return model;
},
() {
if (mounted) {
setState(() => _selectedLevel = prevLevel);
}
},
);
}
Future<void> _setVoice(String? voice) async {
if (voice == _userProfile.userSettings.voice) return;
final prevVoice = _userProfile.userSettings.voice;
setState(() => _selectedVoice = voice);
await _update((model) {
model.userSettings.voice = voice;
return model;
});
await _update(
(model) {
model.userSettings.voice = voice;
return model;
},
() {
if (mounted) {
setState(() => _selectedVoice = prevVoice);
}
},
);
}
@override

View file

@ -6,6 +6,8 @@ import 'package:fluffychat/pangea/common/widgets/card_header.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
class IdenticalLanguageException implements Exception {}
class LanguageMismatchPopup extends StatelessWidget {
final String message;
final String overlayId;

View file

@ -9,12 +9,11 @@ import 'package:fluffychat/pangea/common/widgets/shrinkable_text.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_mismatch_popup.dart';
import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
import 'package:fluffychat/pangea/login/utils/lang_code_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
class IdenticalLanguageException implements Exception {}
class LanguageSelectionPage extends StatefulWidget {
const LanguageSelectionPage({super.key});

View file

@ -237,6 +237,8 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
Future<void> modeDisabled() async {
final target = controller.messageEvent.originalSent?.langCode;
final l1 =
MatrixState.pangeaController.userController.userL1?.langCodeShort;
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
@ -250,7 +252,7 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
textAlign: TextAlign.center,
),
),
if (target != null)
if (target != null && target != l1)
TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(

View file

@ -13,6 +13,7 @@ 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/pangea/learning_settings/language_mismatch_popup.dart';
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
import 'package:fluffychat/pangea/user/analytics_profile_model.dart';
import 'package:fluffychat/pangea/user/public_profile_model.dart';
@ -124,7 +125,21 @@ class UserController {
await initialize();
final prevHash = profile.hashCode;
final Profile updatedProfile = update(profile);
final Profile updatedProfile = update(profile.copy());
final sourceCodeShort = updatedProfile.userSettings.sourceLanguage
?.split("-")
.first;
final targetCodeShort = updatedProfile.userSettings.targetLanguage
?.split("-")
.first;
if (sourceCodeShort != null &&
targetCodeShort != null &&
sourceCodeShort == targetCodeShort) {
throw IdenticalLanguageException();
}
if (updatedProfile.hashCode == prevHash) {
// no changes were made, so don't save
return;

View file

@ -11,6 +11,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/recording_view_model.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_repo.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/learning_settings/language_mismatch_popup.dart';
import 'package:fluffychat/utils/other_party_can_receive.dart';
import 'uia_request_manager.dart';
@ -44,6 +45,10 @@ extension LocalizedExceptionExtension on Object {
if (this is EmptyAudioException) {
return L10n.of(context).emptyAudioError;
}
if (this is IdenticalLanguageException) {
return L10n.of(context).noIdenticalLanguages;
}
// Pangea#
if (this is FileTooBigMatrixException) {
final exception = this as FileTooBigMatrixException;