feat: allow token feedback for word card in vocab analytics (#4900)
* feat: allow token feedback for word card in vocab analytics * fix: remove duplicate global keys
This commit is contained in:
parent
99336960d2
commit
c507c7b54b
8 changed files with 146 additions and 79 deletions
|
|
@ -67,8 +67,7 @@ import 'package:fluffychat/pangea/learning_settings/language_mismatch_repo.dart'
|
|||
import 'package:fluffychat/pangea/learning_settings/p_language_dialog.dart';
|
||||
import 'package:fluffychat/pangea/spaces/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';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/show_token_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/message_practice/message_practice_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart';
|
||||
|
|
@ -2304,31 +2303,12 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
PangeaMessageEvent event,
|
||||
) async {
|
||||
clearSelectedEvents();
|
||||
final resp = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => TokenInfoFeedbackDialog(
|
||||
requestData: requestData,
|
||||
langCode: langCode,
|
||||
event: event,
|
||||
),
|
||||
await TokenFeedbackUtil.showTokenFeedbackDialog(
|
||||
context,
|
||||
requestData: requestData,
|
||||
langCode: langCode,
|
||||
event: event,
|
||||
);
|
||||
|
||||
if (resp != null && resp is String) {
|
||||
OverlayUtil.showOverlay(
|
||||
overlayKey: "token_feedback_snackbar",
|
||||
context: context,
|
||||
child: TokenFeedbackNotification(message: resp),
|
||||
transformTargetId: '',
|
||||
position: OverlayPositionEnum.top,
|
||||
backDropToDismiss: false,
|
||||
closePrevOverlay: false,
|
||||
canPop: false,
|
||||
);
|
||||
|
||||
Future.delayed(const Duration(seconds: 10), () {
|
||||
MatrixState.pAnyState.closeOverlay("token_feedback_snackbar");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void toggleShowDropdown() {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ import 'package:fluffychat/pangea/analytics_misc/analytics_navigation_util.dart'
|
|||
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/show_token_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/word_card/word_zoom_widget.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -72,6 +77,18 @@ class VocabDetailsView extends StatelessWidget {
|
|||
.toList() ??
|
||||
[];
|
||||
|
||||
final tokenText = PangeaTokenText.fromString(constructId.lemma);
|
||||
final token = PangeaToken(
|
||||
text: tokenText,
|
||||
pos: constructId.category,
|
||||
morph: {},
|
||||
lemma: Lemma(
|
||||
text: constructId.lemma,
|
||||
form: constructId.lemma,
|
||||
saveVocab: true,
|
||||
),
|
||||
);
|
||||
|
||||
return MaxWidthBody(
|
||||
maxWidth: 600.0,
|
||||
showBorder: false,
|
||||
|
|
@ -82,11 +99,32 @@ class VocabDetailsView extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
WordZoomWidget(
|
||||
token: PangeaTokenText.fromString(constructId.lemma),
|
||||
token: tokenText,
|
||||
langCode:
|
||||
MatrixState.pangeaController.userController.userL2Code!,
|
||||
construct: constructId,
|
||||
onClose: Navigator.of(context).pop,
|
||||
onFlagTokenInfo:
|
||||
(LemmaInfoResponse lemmaInfo, String phonetics) {
|
||||
final requestData = TokenInfoFeedbackRequestData(
|
||||
userId: Matrix.of(context).client.userID!,
|
||||
detectedLanguage: MatrixState
|
||||
.pangeaController.userController.userL2Code!,
|
||||
tokens: [token],
|
||||
selectedToken: 0,
|
||||
wordCardL1: MatrixState
|
||||
.pangeaController.userController.userL1Code!,
|
||||
lemmaInfo: lemmaInfo,
|
||||
phonetics: phonetics,
|
||||
);
|
||||
|
||||
TokenFeedbackUtil.showTokenFeedbackDialog(
|
||||
context,
|
||||
requestData: requestData,
|
||||
langCode: MatrixState
|
||||
.pangeaController.userController.userL2Code!,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
class LemmaHighlightEmojiRow extends StatefulWidget {
|
||||
final ConstructIdentifier cId;
|
||||
final String langCode;
|
||||
final String targetId;
|
||||
|
||||
final Function(String) onEmojiSelected;
|
||||
final Function(String, String) onEmojiSelected;
|
||||
final Map<String, dynamic> messageInfo;
|
||||
|
||||
final String? emoji;
|
||||
|
|
@ -26,6 +27,7 @@ class LemmaHighlightEmojiRow extends StatefulWidget {
|
|||
super.key,
|
||||
required this.cId,
|
||||
required this.langCode,
|
||||
required this.targetId,
|
||||
required this.onEmojiSelected,
|
||||
required this.messageInfo,
|
||||
this.emoji,
|
||||
|
|
@ -73,22 +75,23 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
|
|||
),
|
||||
),
|
||||
)
|
||||
: emojis
|
||||
.map(
|
||||
(emoji) => EmojiChoiceItem(
|
||||
: emojis.map(
|
||||
(emoji) {
|
||||
final targetId = "${widget.targetId}-$emoji";
|
||||
return EmojiChoiceItem(
|
||||
cId: widget.cId,
|
||||
emoji: emoji,
|
||||
onSelectEmoji: () => widget.onEmojiSelected(emoji),
|
||||
onSelectEmoji: () =>
|
||||
widget.onEmojiSelected(emoji, targetId),
|
||||
selected: widget.emoji == emoji,
|
||||
transformTargetId:
|
||||
"emoji-choice-item-$emoji-${widget.cId.lemma}",
|
||||
transformTargetId: targetId,
|
||||
badge: widget.emoji == emoji
|
||||
? widget.selectedEmojiBadge
|
||||
: null,
|
||||
showShimmer: widget.emoji == null,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class _PhoneticTranscriptionWidgetState
|
|||
extends State<PhoneticTranscriptionWidget> {
|
||||
bool _isPlaying = false;
|
||||
|
||||
Future<void> _handleAudioTap() async {
|
||||
Future<void> _handleAudioTap(String targetId) async {
|
||||
if (_isPlaying) {
|
||||
await TtsController.stop();
|
||||
setState(() => _isPlaying = false);
|
||||
|
|
@ -49,7 +49,7 @@ class _PhoneticTranscriptionWidgetState
|
|||
await TtsController.tryToSpeak(
|
||||
widget.text,
|
||||
context: context,
|
||||
targetID: 'phonetic-transcription-${widget.text}',
|
||||
targetID: targetId,
|
||||
langCode: widget.textLanguage.langCode,
|
||||
onStart: () {
|
||||
if (mounted) setState(() => _isPlaying = true);
|
||||
|
|
@ -63,10 +63,11 @@ class _PhoneticTranscriptionWidgetState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final targetId = 'phonetic-transcription-${widget.text}-$hashCode';
|
||||
return HoverBuilder(
|
||||
builder: (context, hovering) {
|
||||
return GestureDetector(
|
||||
onTap: _handleAudioTap,
|
||||
onTap: () => _handleAudioTap(targetId),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -77,13 +78,9 @@ class _PhoneticTranscriptionWidgetState
|
|||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState
|
||||
.layerLinkAndKey("phonetic-transcription-${widget.text}")
|
||||
.link,
|
||||
link: MatrixState.pAnyState.layerLinkAndKey(targetId).link,
|
||||
child: PhoneticTranscriptionBuilder(
|
||||
key: MatrixState.pAnyState
|
||||
.layerLinkAndKey("phonetic-transcription-${widget.text}")
|
||||
.key,
|
||||
key: MatrixState.pAnyState.layerLinkAndKey(targetId).key,
|
||||
textLanguage: widget.textLanguage,
|
||||
text: widget.text,
|
||||
builder: (context, controller) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.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_notification.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TokenFeedbackUtil {
|
||||
static Future<void> showTokenFeedbackDialog(
|
||||
BuildContext context, {
|
||||
required TokenInfoFeedbackRequestData requestData,
|
||||
required String langCode,
|
||||
PangeaMessageEvent? event,
|
||||
}) async {
|
||||
final resp = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => TokenInfoFeedbackDialog(
|
||||
requestData: requestData,
|
||||
langCode: langCode,
|
||||
event: event,
|
||||
),
|
||||
);
|
||||
|
||||
if (resp != null && resp is String) {
|
||||
OverlayUtil.showOverlay(
|
||||
overlayKey: "token_feedback_snackbar",
|
||||
context: context,
|
||||
child: TokenFeedbackNotification(message: resp),
|
||||
transformTargetId: '',
|
||||
position: OverlayPositionEnum.top,
|
||||
backDropToDismiss: false,
|
||||
closePrevOverlay: false,
|
||||
canPop: false,
|
||||
);
|
||||
|
||||
Future.delayed(const Duration(seconds: 10), () {
|
||||
MatrixState.pAnyState.closeOverlay("token_feedback_snackbar");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,13 +24,13 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
class TokenInfoFeedbackDialog extends StatelessWidget {
|
||||
final TokenInfoFeedbackRequestData requestData;
|
||||
final String langCode;
|
||||
final PangeaMessageEvent event;
|
||||
final PangeaMessageEvent? event;
|
||||
|
||||
const TokenInfoFeedbackDialog({
|
||||
super.key,
|
||||
required this.requestData,
|
||||
required this.langCode,
|
||||
required this.event,
|
||||
this.event,
|
||||
});
|
||||
|
||||
Future<String> _submitFeedback(String feedback) async {
|
||||
|
|
@ -60,7 +60,7 @@ class TokenInfoFeedbackDialog extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final originalSent = event.originalSent;
|
||||
final originalSent = event?.originalSent;
|
||||
|
||||
// if no other changes, just return the message
|
||||
final hasTokenUpdate = response.updatedToken != null;
|
||||
|
|
@ -82,23 +82,25 @@ class TokenInfoFeedbackDialog extends StatelessWidget {
|
|||
originalSent.content.langCode = response.updatedLanguage!;
|
||||
}
|
||||
|
||||
await event.room.pangeaSendTextEvent(
|
||||
requestData.fullText,
|
||||
editEventId: event.eventId,
|
||||
originalSent: originalSent?.content,
|
||||
originalWritten: event.originalWritten?.content,
|
||||
tokensSent: PangeaMessageTokens(
|
||||
tokens: tokens,
|
||||
),
|
||||
tokensWritten: event.originalWritten?.tokens != null
|
||||
? PangeaMessageTokens(
|
||||
tokens: event.originalWritten!.tokens!,
|
||||
detections: event.originalWritten?.detections,
|
||||
)
|
||||
: null,
|
||||
choreo: originalSent?.choreo,
|
||||
messageTag: ModelKey.tokenFeedbackEdit,
|
||||
);
|
||||
if (requestData.fullText != null && event != null) {
|
||||
await event!.room.pangeaSendTextEvent(
|
||||
requestData.fullText!,
|
||||
editEventId: event!.eventId,
|
||||
originalSent: originalSent?.content,
|
||||
originalWritten: event!.originalWritten?.content,
|
||||
tokensSent: PangeaMessageTokens(
|
||||
tokens: tokens,
|
||||
),
|
||||
tokensWritten: event!.originalWritten?.tokens != null
|
||||
? PangeaMessageTokens(
|
||||
tokens: event!.originalWritten!.tokens!,
|
||||
detections: event!.originalWritten?.detections,
|
||||
)
|
||||
: null,
|
||||
choreo: originalSent?.choreo,
|
||||
messageTag: ModelKey.tokenFeedbackEdit,
|
||||
);
|
||||
}
|
||||
|
||||
return response.userFriendlyMessage;
|
||||
}
|
||||
|
|
@ -119,7 +121,9 @@ class TokenInfoFeedbackDialog extends StatelessWidget {
|
|||
LemmaInfoResponse response,
|
||||
) =>
|
||||
LemmaInfoRepo.set(
|
||||
token.vocabConstructID.lemmaInfoRequest(event.event.content),
|
||||
token.vocabConstructID.lemmaInfoRequest(
|
||||
event?.event.content ?? {},
|
||||
),
|
||||
response,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
|||
|
||||
class TokenInfoFeedbackRequestData {
|
||||
final String userId;
|
||||
final String roomId;
|
||||
final String fullText;
|
||||
final String? roomId;
|
||||
final String? fullText;
|
||||
final String detectedLanguage;
|
||||
final List<PangeaToken> tokens;
|
||||
final int selectedToken;
|
||||
|
|
@ -14,14 +14,14 @@ class TokenInfoFeedbackRequestData {
|
|||
|
||||
TokenInfoFeedbackRequestData({
|
||||
required this.userId,
|
||||
required this.roomId,
|
||||
required this.fullText,
|
||||
required this.detectedLanguage,
|
||||
required this.tokens,
|
||||
required this.selectedToken,
|
||||
required this.lemmaInfo,
|
||||
required this.phonetics,
|
||||
required this.wordCardL1,
|
||||
this.roomId,
|
||||
this.fullText,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@ class LemmaReactionPicker extends StatelessWidget with LemmaEmojiSetter {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _setEmoji(String emoji, BuildContext context) async {
|
||||
await setLemmaEmoji(
|
||||
constructId,
|
||||
emoji,
|
||||
"emoji-choice-item-$emoji-${constructId.lemma}",
|
||||
);
|
||||
Future<void> _setEmoji(
|
||||
String emoji,
|
||||
BuildContext context,
|
||||
String targetId,
|
||||
) async {
|
||||
await setLemmaEmoji(constructId, emoji, targetId);
|
||||
showLemmaEmojiSnackbar(context, constructId, emoji);
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +78,7 @@ class LemmaReactionPicker extends StatelessWidget with LemmaEmojiSetter {
|
|||
.updateDispatcher
|
||||
.lemmaUpdateStream(constructId);
|
||||
|
||||
final targetId = "emoji-choice-item-${constructId.lemma}-$hashCode";
|
||||
return StreamBuilder(
|
||||
stream: stream,
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -87,8 +88,9 @@ class LemmaReactionPicker extends StatelessWidget with LemmaEmojiSetter {
|
|||
return LemmaHighlightEmojiRow(
|
||||
cId: constructId,
|
||||
langCode: langCode,
|
||||
onEmojiSelected: (emoji) => emoji != selectedEmoji
|
||||
? _setEmoji(emoji, context)
|
||||
targetId: targetId,
|
||||
onEmojiSelected: (emoji, target) => emoji != selectedEmoji
|
||||
? _setEmoji(emoji, context, target)
|
||||
: _sendOrRedactReaction(emoji, context),
|
||||
emoji: selectedEmoji,
|
||||
messageInfo: event?.content ?? {},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue