feat: show warning popup on l2/activity language mixup (#4229)

This commit is contained in:
ggurdin 2025-10-02 13:29:49 -04:00 committed by GitHub
parent 500e9670fa
commit dc55796ea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 174 additions and 3 deletions

View file

@ -5300,5 +5300,7 @@
"playWithAI": "Play with AI for now",
"courseStartDesc": "Pangea Bot is ready to go anytime!\n\n...but learning is better with friends!",
"activityDropdownDesc": "When youre done with this activity, click below",
"activityAnalyticsListBody": "These are your completed activities! After finishing activities, you can view them here."
"activityAnalyticsListBody": "These are your completed activities! After finishing activities, you can view them here.",
"languageMismatchTitle": "Language mismatch",
"languageMismatchDesc": "Your target language doesn't match the language of this activity. Update your target language?"
}

View file

@ -42,6 +42,8 @@ import 'package:fluffychat/pangea/chat/widgets/event_too_large_dialog.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
import 'package:fluffychat/pangea/choreographer/utils/language_mismatch_repo.dart';
import 'package:fluffychat/pangea/choreographer/widgets/igc/language_mismatch_popup.dart';
import 'package:fluffychat/pangea/choreographer/widgets/igc/message_analytics_feedback.dart';
import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
@ -2158,6 +2160,42 @@ class ChatController extends State<ChatPageWithRoom>
}
}
bool get shouldShowLanguageMismatchPopup {
if (!LanguageMismatchRepo.shouldShow) {
return false;
}
final l2 = choreographer.l2Lang?.langCodeShort;
final activityLang = room.activityPlan?.req.targetLanguage.split('-').first;
return activityLang != null && l2 != null && l2 != activityLang;
}
Future<void> showLanguageMismatchPopup() async {
if (!shouldShowLanguageMismatchPopup) {
return;
}
final targetLanguage = room.activityPlan!.req.targetLanguage;
LanguageMismatchRepo.set();
OverlayUtil.showPositionedCard(
context: context,
cardToShow: LanguageMismatchPopup(
targetLanguage: targetLanguage,
choreographer: choreographer,
onUpdate: () async {
await choreographer.getLanguageHelp(manual: true);
final matches = choreographer.igc.igcTextData?.matches;
if (matches?.isNotEmpty == true) {
choreographer.igc.showFirstMatch(context);
}
},
),
maxHeight: 325,
maxWidth: 325,
transformTargetId: choreographer.inputTransformTargetKey,
);
}
void _showAnalyticsFeedback(
List<OneConstructUse> constructs,
String eventId,

View file

@ -122,6 +122,11 @@ class Choreographer {
return;
}
if (chatController.shouldShowLanguageMismatchPopup) {
chatController.showLanguageMismatchPopup();
return;
}
if (!igc.hasRelevantIGCTextData && !itController.dismissed) {
getLanguageHelp().then((value) => _sendWithIGC(context));
} else {

View file

@ -0,0 +1,42 @@
import 'package:get_storage/get_storage.dart';
class LanguageMismatchRepo {
static final GetStorage _storage = GetStorage('language_mismatch');
static const String key = 'shown_timestamp';
static const Duration displayInterval = Duration(minutes: 30);
static Future<void> set() async {
await _storage.write(key, DateTime.now().toIso8601String());
}
static DateTime? _get() {
final entry = _storage.read(key);
if (entry == null) return null;
try {
final value = DateTime.tryParse(entry);
if (value != null) {
final timeSince = DateTime.now().difference(value);
if (timeSince > displayInterval) {
_delete();
return null;
}
return value;
}
} catch (_) {
_delete();
}
return null;
}
static Future<void> _delete() async {
await _storage.remove(key);
}
static bool get shouldShow {
final lastShown = _get();
if (lastShown == null) return true;
return DateTime.now().difference(lastShown) >= displayInterval;
}
}

View file

@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/widgets/igc/card_header.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
class LanguageMismatchPopup extends StatelessWidget {
final String targetLanguage;
final Choreographer choreographer;
final VoidCallback onUpdate;
const LanguageMismatchPopup({
super.key,
required this.targetLanguage,
required this.choreographer,
required this.onUpdate,
});
Future<void> _onConfirm(BuildContext context) async {
await MatrixState.pangeaController.userController.updateProfile(
(profile) {
profile.userSettings.targetLanguage = targetLanguage;
return profile;
},
waitForDataInSync: true,
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CardHeader(
text: L10n.of(context).languageMismatchTitle,
botExpression: BotExpression.addled,
),
Padding(
padding: const EdgeInsets.all(8),
child: Column(
spacing: 12.0,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
L10n.of(context).languageMismatchDesc,
style: BotStyle.text(context),
textAlign: TextAlign.center,
),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () async {
await showFutureLoadingDialog(
context: context,
future: () => _onConfirm(context),
);
MatrixState.pAnyState.closeOverlay();
onUpdate();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all<Color>(
(Theme.of(context).colorScheme.primary).withAlpha(25),
),
),
child: Text(L10n.of(context).confirm),
),
),
],
),
),
],
);
}
}

View file

@ -100,8 +100,12 @@ class StartIGCButtonState extends State<StartIGCButton>
);
return;
case AssistanceState.notFetched:
await widget.controller.choreographer.getLanguageHelp(manual: true);
_showFirstMatch();
if (widget.controller.shouldShowLanguageMismatchPopup) {
widget.controller.showLanguageMismatchPopup();
} else {
await widget.controller.choreographer.getLanguageHelp(manual: true);
_showFirstMatch();
}
return;
case AssistanceState.fetched:
_showFirstMatch();

View file

@ -126,6 +126,7 @@ class PangeaController {
'course_location_storage',
'course_activity_storage',
'course_location_media_storage',
'language_mismatch',
];
Future<void> clearCache({List<String> exclude = const []}) async {