feat: allow users to give token feedback in word card
This commit is contained in:
parent
df4cda0875
commit
9790d2e56d
11 changed files with 630 additions and 30 deletions
|
|
@ -5304,6 +5304,9 @@
|
|||
"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?",
|
||||
"reportWordIssueTooltip": "Report word information issue",
|
||||
"tokenInfoFeedbackDialogTitle": "Word Information Feedback",
|
||||
"tokenInfoFeedbackDialogDesc": "AI makes mistakes. Please describe any issues you found with the information above.",
|
||||
"noPublicCoursesFound": "No public courses found. Would you like to create one?",
|
||||
"noCourseTemplatesFound": "We couldn't find any courses for your target language. You can chat with Pangea Bot in the meantime, and check back later for more courses."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ class PApiUrls {
|
|||
static String activityFeedback =
|
||||
"${PApiUrls._choreoEndpoint}/activity_plan/feedback";
|
||||
|
||||
static String tokenFeedback = "${PApiUrls._choreoEndpoint}/token/feedback";
|
||||
|
||||
static String morphFeaturesAndTags = "${PApiUrls._choreoEndpoint}/morphs";
|
||||
static String constructSummary =
|
||||
"${PApiUrls._choreoEndpoint}/construct_summary";
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ class LemmaInfoRequest {
|
|||
final String lemmaLang;
|
||||
final String userL1;
|
||||
|
||||
ContentFeedback<LemmaInfoResponse>? feedback;
|
||||
List<ContentFeedback<LemmaInfoResponse>> feedback;
|
||||
|
||||
LemmaInfoRequest({
|
||||
required String partOfSpeech,
|
||||
required String lemmaLang,
|
||||
required this.userL1,
|
||||
required this.lemma,
|
||||
this.feedback,
|
||||
this.feedback = const [],
|
||||
}) : partOfSpeech = partOfSpeech.toLowerCase(),
|
||||
lemmaLang = lemmaLang.toLowerCase();
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ class LemmaInfoRequest {
|
|||
'part_of_speech': partOfSpeech,
|
||||
'lemma_lang': lemmaLang,
|
||||
'user_l1': userL1,
|
||||
'feedback': feedback?.toJson(),
|
||||
'feedback': feedback.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ class PhoneticTranscriptionRepo {
|
|||
static final GetStorage _storage =
|
||||
GetStorage('phonetic_transcription_storage');
|
||||
|
||||
static void set(
|
||||
static Future<void> set(
|
||||
PhoneticTranscriptionRequest request,
|
||||
PhoneticTranscriptionResponse response,
|
||||
) {
|
||||
) async {
|
||||
response.expireAt ??= DateTime.now().add(const Duration(days: 100));
|
||||
_storage.write(request.storageKey, response.toJson());
|
||||
await _storage.write(request.storageKey, response.toJson());
|
||||
}
|
||||
|
||||
static Future<PhoneticTranscriptionResponse> _fetch(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
|
||||
class TokenInfoFeedbackButton extends StatelessWidget {
|
||||
final TokenInfoFeedbackRequestData requestData;
|
||||
final String langCode;
|
||||
final PangeaMessageEvent event;
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
const TokenInfoFeedbackButton({
|
||||
super.key,
|
||||
required this.requestData,
|
||||
required this.langCode,
|
||||
required this.event,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
Future<void> _submitFeedback(BuildContext context) async {
|
||||
final resp = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => TokenInfoFeedbackDialog(
|
||||
requestData: requestData,
|
||||
langCode: langCode,
|
||||
event: event,
|
||||
onUpdate: onUpdate,
|
||||
),
|
||||
);
|
||||
|
||||
if (resp != null && resp is String) {
|
||||
_showSuccessSnackBar(resp, context);
|
||||
}
|
||||
}
|
||||
|
||||
void _showSuccessSnackBar(String message, BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const BotFace(
|
||||
width: 30,
|
||||
expression: BotExpression.idle,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(message),
|
||||
),
|
||||
],
|
||||
),
|
||||
duration: const Duration(seconds: 30),
|
||||
action: SnackBarAction(
|
||||
label: L10n.of(context).close,
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.flag_outlined),
|
||||
onPressed: () => _submitFeedback(context),
|
||||
tooltip: L10n.of(context).reportWordIssueTooltip,
|
||||
);
|
||||
}
|
||||
}
|
||||
279
lib/pangea/token_info_feedback/token_info_feedback_dialog.dart
Normal file
279
lib/pangea/token_info_feedback/token_info_feedback_dialog.dart
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.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/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_repo.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_response.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TokenInfoFeedbackDialog extends StatefulWidget {
|
||||
final TokenInfoFeedbackRequestData requestData;
|
||||
final String langCode;
|
||||
final PangeaMessageEvent event;
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
const TokenInfoFeedbackDialog({
|
||||
super.key,
|
||||
required this.requestData,
|
||||
required this.langCode,
|
||||
required this.event,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TokenInfoFeedbackDialog> createState() =>
|
||||
_TokenInfoFeedbackDialogState();
|
||||
}
|
||||
|
||||
class _TokenInfoFeedbackDialogState extends State<TokenInfoFeedbackDialog> {
|
||||
final TextEditingController _feedbackController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_feedbackController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<String> _submitFeedback() async {
|
||||
final request = TokenInfoFeedbackRequest(
|
||||
userFeedback: _feedbackController.text,
|
||||
data: widget.requestData,
|
||||
);
|
||||
|
||||
final TokenInfoFeedbackResponse response =
|
||||
await TokenInfoFeedbackRepo.submitFeedback(request);
|
||||
|
||||
final originalToken =
|
||||
widget.requestData.tokens[widget.requestData.selectedToken];
|
||||
final token = response.updatedToken ?? originalToken;
|
||||
|
||||
// first, update lemma info if changed
|
||||
if (response.updatedLemmaInfo != null) {
|
||||
await _updateLemmaInfo(
|
||||
token,
|
||||
response.updatedLemmaInfo!,
|
||||
);
|
||||
}
|
||||
|
||||
// second, update the phonetic info if changed
|
||||
if (response.updatedPhonetics != null) {
|
||||
await _updatePhoneticTranscription(
|
||||
response.updatedPhonetics!,
|
||||
);
|
||||
}
|
||||
|
||||
final originalSent = widget.event.originalSent;
|
||||
|
||||
// if no other changes, just return the message
|
||||
final hasTokenUpdate = response.updatedToken != null;
|
||||
final hasLangUpdate = originalSent != null &&
|
||||
response.updatedLanguage != null &&
|
||||
response.updatedLanguage != originalSent.langCode;
|
||||
|
||||
if (!hasTokenUpdate && !hasLangUpdate) {
|
||||
widget.onUpdate();
|
||||
return response.userFriendlyMessage;
|
||||
}
|
||||
|
||||
// update the tokens to be sent in the message edit
|
||||
final tokens = List<PangeaToken>.from(widget.requestData.tokens);
|
||||
if (hasTokenUpdate) {
|
||||
tokens[widget.requestData.selectedToken] = response.updatedToken!;
|
||||
}
|
||||
|
||||
if (hasLangUpdate) {
|
||||
originalSent.content.langCode = response.updatedLanguage!;
|
||||
}
|
||||
|
||||
await widget.event.room.pangeaSendTextEvent(
|
||||
widget.requestData.fullText,
|
||||
editEventId: widget.event.eventId,
|
||||
originalSent: originalSent?.content,
|
||||
originalWritten: widget.event.originalWritten?.content,
|
||||
tokensSent: PangeaMessageTokens(
|
||||
tokens: tokens,
|
||||
),
|
||||
tokensWritten: widget.event.originalWritten?.tokens != null
|
||||
? PangeaMessageTokens(
|
||||
tokens: widget.event.originalWritten!.tokens!,
|
||||
detections: widget.event.originalWritten?.detections,
|
||||
)
|
||||
: null,
|
||||
choreo: originalSent?.choreo,
|
||||
);
|
||||
|
||||
widget.onUpdate();
|
||||
return response.userFriendlyMessage;
|
||||
}
|
||||
|
||||
Future<void> _updateLemmaInfo(
|
||||
PangeaToken token,
|
||||
LemmaInfoResponse response,
|
||||
) async {
|
||||
final construct = token.vocabConstructID;
|
||||
|
||||
final currentLemmaInfo = construct.userLemmaInfo;
|
||||
final updatedLemmaInfo = UserSetLemmaInfo(
|
||||
meaning: response.meaning,
|
||||
emojis: response.emoji,
|
||||
);
|
||||
|
||||
if (currentLemmaInfo != updatedLemmaInfo) {
|
||||
await construct.setUserLemmaInfo(updatedLemmaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updatePhoneticTranscription(
|
||||
PhoneticTranscriptionResponse response,
|
||||
) async {
|
||||
final req = PhoneticTranscriptionRequest(
|
||||
arc: LanguageArc(
|
||||
l1: PLanguageStore.byLangCode(widget.requestData.wordCardL1) ??
|
||||
MatrixState.pangeaController.languageController.userL1!,
|
||||
l2: PLanguageStore.byLangCode(widget.langCode) ??
|
||||
MatrixState.pangeaController.languageController.userL2!,
|
||||
),
|
||||
content: response.content,
|
||||
);
|
||||
await PhoneticTranscriptionRepo.set(req, response);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedToken =
|
||||
widget.requestData.tokens[widget.requestData.selectedToken];
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 2.5, sigmaY: 2.5),
|
||||
child: Dialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 325.0,
|
||||
child: Column(
|
||||
spacing: 20.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header with title and close button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
L10n.of(context).tokenInfoFeedbackDialogTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
child: Center(
|
||||
child: Icon(Icons.flag_outlined),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 20.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Placeholder for word card
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Center(
|
||||
child: WordZoomWidget(
|
||||
token: selectedToken.text,
|
||||
construct: selectedToken.vocabConstructID,
|
||||
langCode: widget.langCode,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Description text
|
||||
Text(
|
||||
L10n.of(context).tokenInfoFeedbackDialogDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
// Feedback text field
|
||||
TextField(
|
||||
controller: _feedbackController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).feedbackHint,
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
),
|
||||
// Submit button
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _feedbackController,
|
||||
builder: (context, value, _) {
|
||||
final isNotEmpty = value.text.isNotEmpty;
|
||||
return ElevatedButton(
|
||||
onPressed: isNotEmpty
|
||||
? () async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _submitFeedback(),
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
Navigator.of(context).pop(resp.result!);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(L10n.of(context).feedbackButton),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
lib/pangea/token_info_feedback/token_info_feedback_repo.dart
Normal file
40
lib/pangea/token_info_feedback/token_info_feedback_repo.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'dart:convert';
|
||||
|
||||
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/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_response.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TokenInfoFeedbackRepo {
|
||||
/// Submit token info feedback for processing
|
||||
///
|
||||
/// This method sends user feedback about token information to the server
|
||||
/// for evaluation and potential updates. The feedback is processed
|
||||
/// and may result in updated token data, lemma information, or phonetics.
|
||||
static Future<TokenInfoFeedbackResponse> submitFeedback(
|
||||
TokenInfoFeedbackRequest request,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.tokenFeedback,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to submit token info feedback: ${res.statusCode} ${res.body}',
|
||||
);
|
||||
}
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
return TokenInfoFeedbackResponse.fromJson(decodedBody);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
|
||||
class TokenInfoFeedbackRequestData {
|
||||
final String userId;
|
||||
final String roomId;
|
||||
final String fullText;
|
||||
final String detectedLanguage;
|
||||
final List<PangeaToken> tokens;
|
||||
final int selectedToken;
|
||||
final LemmaInfoResponse? lemmaInfo;
|
||||
final String? phonetics;
|
||||
final String wordCardL1;
|
||||
|
||||
TokenInfoFeedbackRequestData({
|
||||
required this.userId,
|
||||
required this.roomId,
|
||||
required this.fullText,
|
||||
required this.detectedLanguage,
|
||||
required this.tokens,
|
||||
required this.selectedToken,
|
||||
this.lemmaInfo,
|
||||
this.phonetics,
|
||||
required this.wordCardL1,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TokenInfoFeedbackRequestData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
userId == other.userId &&
|
||||
roomId == other.roomId &&
|
||||
fullText == other.fullText &&
|
||||
detectedLanguage == other.detectedLanguage &&
|
||||
selectedToken == other.selectedToken &&
|
||||
lemmaInfo == other.lemmaInfo &&
|
||||
phonetics == other.phonetics &&
|
||||
wordCardL1 == other.wordCardL1;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
userId.hashCode ^
|
||||
roomId.hashCode ^
|
||||
fullText.hashCode ^
|
||||
detectedLanguage.hashCode ^
|
||||
selectedToken.hashCode ^
|
||||
lemmaInfo.hashCode ^
|
||||
phonetics.hashCode ^
|
||||
wordCardL1.hashCode;
|
||||
}
|
||||
|
||||
class TokenInfoFeedbackRequest {
|
||||
final TokenInfoFeedbackRequestData data;
|
||||
final String userFeedback;
|
||||
|
||||
TokenInfoFeedbackRequest({
|
||||
required this.data,
|
||||
required this.userFeedback,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'user_id': data.userId,
|
||||
'room_id': data.roomId,
|
||||
'full_text': data.fullText,
|
||||
'detected_language': data.detectedLanguage,
|
||||
'tokens': data.tokens.map((token) => token.toJson()).toList(),
|
||||
'selected_token': data.selectedToken,
|
||||
'lemma_info': data.lemmaInfo?.toJson(),
|
||||
'phonetics': data.phonetics,
|
||||
'user_feedback': userFeedback,
|
||||
'word_card_l1': data.wordCardL1,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TokenInfoFeedbackRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
data == other.data &&
|
||||
userFeedback == other.userFeedback;
|
||||
|
||||
@override
|
||||
int get hashCode => data.hashCode ^ userFeedback.hashCode;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:fluffychat/pangea/events/models/content_feedback.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart';
|
||||
|
||||
class TokenInfoFeedbackResponse implements JsonSerializable {
|
||||
final String userFriendlyMessage;
|
||||
final PangeaToken? updatedToken;
|
||||
final LemmaInfoResponse? updatedLemmaInfo;
|
||||
final PhoneticTranscriptionResponse? updatedPhonetics;
|
||||
final String? updatedLanguage;
|
||||
|
||||
TokenInfoFeedbackResponse({
|
||||
required this.userFriendlyMessage,
|
||||
this.updatedToken,
|
||||
this.updatedLemmaInfo,
|
||||
this.updatedPhonetics,
|
||||
this.updatedLanguage,
|
||||
});
|
||||
|
||||
factory TokenInfoFeedbackResponse.fromJson(Map<String, dynamic> json) {
|
||||
return TokenInfoFeedbackResponse(
|
||||
userFriendlyMessage: json['user_friendly_message'] as String,
|
||||
updatedToken: json['updated_token'] != null
|
||||
? PangeaToken.fromJson(json['updated_token'] as Map<String, dynamic>)
|
||||
: null,
|
||||
updatedLemmaInfo: json['updated_lemma_info'] != null
|
||||
? LemmaInfoResponse.fromJson(
|
||||
json['updated_lemma_info'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
updatedPhonetics: json['updated_phonetics'] != null
|
||||
? PhoneticTranscriptionResponse.fromJson(
|
||||
json['updated_phonetics'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
updatedLanguage: json['updated_language'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'user_friendly_message': userFriendlyMessage,
|
||||
'updated_token': updatedToken?.toJson(),
|
||||
'updated_lemma_info': updatedLemmaInfo?.toJson(),
|
||||
'updated_phonetics': updatedPhonetics?.toJson(),
|
||||
'updated_language': updatedLanguage,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TokenInfoFeedbackResponse &&
|
||||
runtimeType == other.runtimeType &&
|
||||
userFriendlyMessage == other.userFriendlyMessage &&
|
||||
updatedToken == other.updatedToken &&
|
||||
updatedLemmaInfo == other.updatedLemmaInfo &&
|
||||
updatedPhonetics == other.updatedPhonetics &&
|
||||
updatedLanguage == other.updatedLanguage;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
userFriendlyMessage.hashCode ^
|
||||
updatedToken.hashCode ^
|
||||
updatedLemmaInfo.hashCode ^
|
||||
updatedPhonetics.hashCode ^
|
||||
updatedLanguage.hashCode;
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_unsubscribed_card.dart';
|
||||
|
|
@ -71,6 +72,16 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
// return MessageModeLockedCard(controller: widget.overlayController);
|
||||
// }
|
||||
|
||||
final tokens =
|
||||
widget.overlayController.pangeaMessageEvent.originalSent?.tokens;
|
||||
final selectedToken = widget.overlayController.selectedToken;
|
||||
final selectedTokenIndex = selectedToken != null
|
||||
? tokens?.indexWhere(
|
||||
(t) => t.text == selectedToken.text,
|
||||
) ??
|
||||
-1
|
||||
: -1;
|
||||
|
||||
switch (widget.overlayController.toolbarMode) {
|
||||
case MessageMode.messageTranslation:
|
||||
// return MessageTranslationCard(
|
||||
|
|
@ -125,6 +136,21 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
.overlayController.pangeaMessageEvent.messageDisplayLangCode,
|
||||
onDismissNewWordOverlay: () =>
|
||||
widget.overlayController.setState(() {}),
|
||||
requestData: selectedTokenIndex >= 0
|
||||
? TokenInfoFeedbackRequestData(
|
||||
userId: Matrix.of(context).client.userID!,
|
||||
roomId: widget.overlayController.event.room.id,
|
||||
fullText: widget
|
||||
.overlayController.pangeaMessageEvent.messageDisplayText,
|
||||
detectedLanguage: widget.overlayController.pangeaMessageEvent
|
||||
.messageDisplayLangCode,
|
||||
tokens: tokens ?? [],
|
||||
selectedToken: selectedTokenIndex,
|
||||
wordCardL1: MatrixState.pangeaController.languageController
|
||||
.activeL1Code()!,
|
||||
)
|
||||
: null,
|
||||
pangeaMessageEvent: widget.overlayController.pangeaMessageEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.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/lemmas/construct_xp_widget.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_reaction_picker.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_button.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart';
|
||||
|
|
@ -24,21 +24,26 @@ class WordZoomWidget extends StatelessWidget {
|
|||
final ConstructIdentifier construct;
|
||||
|
||||
final String langCode;
|
||||
final VoidCallback onClose;
|
||||
final VoidCallback? onClose;
|
||||
|
||||
final bool wordIsNew;
|
||||
final VoidCallback? onDismissNewWordOverlay;
|
||||
final Event? event;
|
||||
|
||||
final TokenInfoFeedbackRequestData? requestData;
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
|
||||
const WordZoomWidget({
|
||||
super.key,
|
||||
required this.token,
|
||||
required this.construct,
|
||||
required this.langCode,
|
||||
required this.onClose,
|
||||
this.onClose,
|
||||
this.wordIsNew = false,
|
||||
this.onDismissNewWordOverlay,
|
||||
this.event,
|
||||
this.requestData,
|
||||
this.pangeaMessageEvent,
|
||||
});
|
||||
|
||||
String get transformTargetId => "word-zoom-card-${token.uniqueKey}";
|
||||
|
|
@ -69,20 +74,25 @@ class WordZoomWidget extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onClose,
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16.0,
|
||||
onClose != null
|
||||
? SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onClose,
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
token.content,
|
||||
|
|
@ -98,12 +108,22 @@ class WordZoomWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
ConstructXpWidget(
|
||||
id: construct,
|
||||
onTap: () => context.go(
|
||||
"/rooms/analytics/${ConstructTypeEnum.vocab.string}/${construct.string}",
|
||||
),
|
||||
),
|
||||
requestData != null && pangeaMessageEvent != null
|
||||
? TokenInfoFeedbackButton(
|
||||
requestData: requestData!,
|
||||
langCode: langCode,
|
||||
event: pangeaMessageEvent!,
|
||||
onUpdate: () {
|
||||
// close the zoom when updating
|
||||
if (onClose != null) {
|
||||
onClose!();
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
LemmaMeaningBuilder(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue