Merge pull request #4757 from pangeachat/4747-in-image-mode-xp-animation-on-selection
initial work to normalize sending on emoji analytics / settings of us…
This commit is contained in:
commit
ab78bfbc8c
13 changed files with 299 additions and 398 deletions
|
|
@ -31,7 +31,6 @@ import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart
|
|||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_banner.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/message_analytics_feedback.dart';
|
||||
|
|
@ -469,21 +468,9 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void _onAnalyticsUpdate(AnalyticsStreamUpdate update) {
|
||||
if (update.targetID == null) return;
|
||||
OverlayUtil.showOverlay(
|
||||
overlayKey: "${update.targetID ?? ""}_points",
|
||||
followerAnchor: Alignment.bottomCenter,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
context: context,
|
||||
child: PointsGainedAnimation(
|
||||
points: update.points,
|
||||
targetID: update.targetID!,
|
||||
),
|
||||
transformTargetId: update.targetID ?? "",
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
ignorePointer: true,
|
||||
);
|
||||
if (update.targetID != null) {
|
||||
OverlayUtil.showPointsGained(update.targetID!, context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _botAudioListener(SyncUpdate update) async {
|
||||
|
|
|
|||
|
|
@ -442,12 +442,12 @@ class HtmlMessage extends StatelessWidget {
|
|||
children: [
|
||||
if (token != null && overlayController != null)
|
||||
TokenEmojiButton(
|
||||
token: token,
|
||||
enabled: token.lemma.saveVocab,
|
||||
emoji: token.vocabConstructID.userSetEmoji.firstOrNull,
|
||||
targetId: overlayController!.tokenEmojiPopupKey(token),
|
||||
onSelect: () =>
|
||||
overlayController!.showTokenEmojiPopup(token),
|
||||
selectModeNotifier: overlayController!.selectedMode,
|
||||
selectedTokenNotifier:
|
||||
overlayController!.selectedTokenNotifier,
|
||||
),
|
||||
if (renderer.showCenterStyling &&
|
||||
token != null &&
|
||||
|
|
@ -946,6 +946,8 @@ class HtmlMessage extends StatelessWidget {
|
|||
// Use TokenEmojiButton to ensure consistent vertical alignment for non-token elements (e.g., emojis) in practice mode.
|
||||
TokenEmojiButton(
|
||||
selectModeNotifier: overlayController!.selectedMode,
|
||||
selectedTokenNotifier:
|
||||
overlayController!.selectedTokenNotifier,
|
||||
enabled: false,
|
||||
),
|
||||
RichText(
|
||||
|
|
|
|||
54
lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart
Normal file
54
lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
mixin LemmaEmojiSetter {
|
||||
Future<void> setLemmaEmoji(
|
||||
ConstructIdentifier constructId,
|
||||
String emoji,
|
||||
String? targetId,
|
||||
) async {
|
||||
if (constructId.userSetEmoji.isEmpty) {
|
||||
_sendEmojiAnalytics(
|
||||
constructId,
|
||||
targetId: targetId,
|
||||
);
|
||||
}
|
||||
|
||||
await constructId.setUserLemmaInfo(
|
||||
constructId.userLemmaInfo.copyWith(emojis: [emoji]),
|
||||
);
|
||||
}
|
||||
|
||||
void _sendEmojiAnalytics(
|
||||
ConstructIdentifier constructId, {
|
||||
String? eventId,
|
||||
String? roomId,
|
||||
String? targetId,
|
||||
}) {
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
targetID: targetId,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.em,
|
||||
lemma: constructId.lemma,
|
||||
constructType: constructId.type,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: roomId,
|
||||
timeStamp: DateTime.now(),
|
||||
eventId: eventId,
|
||||
),
|
||||
category: constructId.category,
|
||||
form: constructId.lemma,
|
||||
xp: ConstructUseTypeEnum.em.pointValue,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,7 @@ class PangeaAnyState {
|
|||
if (entry != null) {
|
||||
try {
|
||||
entry.entry.remove();
|
||||
entry.entry.dispose();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
|
|
@ -117,6 +118,7 @@ class PangeaAnyState {
|
|||
for (int i = 0; i < shouldRemove.length; i++) {
|
||||
try {
|
||||
shouldRemove[i].entry.remove();
|
||||
shouldRemove[i].entry.dispose();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:developer';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_up/star_rain_widget.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
|
|
@ -284,4 +285,24 @@ class OverlayUtil {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void showPointsGained(
|
||||
String targetId,
|
||||
BuildContext context,
|
||||
) {
|
||||
showOverlay(
|
||||
overlayKey: "${targetId}_points",
|
||||
followerAnchor: Alignment.bottomCenter,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
context: context,
|
||||
child: PointsGainedAnimation(
|
||||
points: 2,
|
||||
targetID: targetId,
|
||||
),
|
||||
transformTargetId: targetId,
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
ignorePointer: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
|
|
@ -138,6 +135,9 @@ class ConstructIdentifier {
|
|||
);
|
||||
}
|
||||
|
||||
bool get isContentWord =>
|
||||
PartOfSpeechEnumExtensions.fromString(category)?.isContentWord ?? false;
|
||||
|
||||
ConstructUses get constructUses =>
|
||||
MatrixState.pangeaController.getAnalytics.constructListModel
|
||||
.getConstructUses(
|
||||
|
|
@ -150,75 +150,41 @@ class ConstructIdentifier {
|
|||
uses: [],
|
||||
);
|
||||
|
||||
List<String> get userSetEmoji => userLemmaInfo?.emojis ?? [];
|
||||
LemmaInfoRequest get _lemmaInfoRequest => LemmaInfoRequest(
|
||||
partOfSpeech: category,
|
||||
lemmaLang: MatrixState
|
||||
.pangeaController.languageController.userL2?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL1: MatrixState
|
||||
.pangeaController.languageController.userL1?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
lemma: lemma,
|
||||
);
|
||||
|
||||
UserSetLemmaInfo? get userLemmaInfo {
|
||||
/// [lemmmaLang] if not set, assumed to be userL2
|
||||
Future<LemmaInfoResponse> getLemmaInfo() => LemmaInfoRepo.get(
|
||||
_lemmaInfoRequest,
|
||||
);
|
||||
|
||||
List<String> get userSetEmoji => userLemmaInfo.emojis ?? [];
|
||||
|
||||
UserSetLemmaInfo get userLemmaInfo {
|
||||
switch (type) {
|
||||
case ConstructTypeEnum.vocab:
|
||||
return MatrixState.pangeaController.matrixState.client
|
||||
.analyticsRoomLocal()
|
||||
?.getUserSetLemmaInfo(this);
|
||||
.analyticsRoomLocal()
|
||||
?.getUserSetLemmaInfo(this) ??
|
||||
UserSetLemmaInfo();
|
||||
case ConstructTypeEnum.morph:
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: Exception("Morphs should not have userSetEmoji"),
|
||||
data: toJson(),
|
||||
);
|
||||
return null;
|
||||
return UserSetLemmaInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets emoji and awards XP if it's a NEW emoji selection or from game
|
||||
Future<void> setEmojiWithXP({
|
||||
required String emoji,
|
||||
bool isFromCorrectAnswer = false,
|
||||
String? eventId,
|
||||
String? roomId,
|
||||
}) async {
|
||||
final hadEmojiPreviously = userSetEmoji.isNotEmpty;
|
||||
//correct answers already award xp so we don't here, but we do still need to set the emoji if it isn't already set
|
||||
final shouldAwardXP = !hadEmojiPreviously && !isFromCorrectAnswer;
|
||||
|
||||
//Set emoji representation
|
||||
await setUserLemmaInfo(UserSetLemmaInfo(emojis: [emoji]));
|
||||
|
||||
if (shouldAwardXP) {
|
||||
await _recordEmojiAnalytics(
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _recordEmojiAnalytics({
|
||||
String? eventId,
|
||||
String? roomId,
|
||||
}) async {
|
||||
const useType = ConstructUseTypeEnum.em;
|
||||
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType: useType,
|
||||
lemma: lemma,
|
||||
constructType: type,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: roomId,
|
||||
timeStamp: DateTime.now(),
|
||||
eventId: eventId,
|
||||
),
|
||||
category: category,
|
||||
form: lemma,
|
||||
xp: useType.pointValue,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setUserLemmaInfo(UserSetLemmaInfo newLemmaInfo) async {
|
||||
final client = MatrixState.pangeaController.matrixState.client;
|
||||
final l2 = MatrixState.pangeaController.languageController.userL2;
|
||||
|
|
@ -239,23 +205,4 @@ class ConstructIdentifier {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
LemmaInfoRequest get _lemmaInfoRequest => LemmaInfoRequest(
|
||||
partOfSpeech: category,
|
||||
lemmaLang: MatrixState
|
||||
.pangeaController.languageController.userL2?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL1: MatrixState
|
||||
.pangeaController.languageController.userL1?.langCodeShort ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
lemma: lemma,
|
||||
);
|
||||
|
||||
/// [lemmmaLang] if not set, assumed to be userL2
|
||||
Future<LemmaInfoResponse> getLemmaInfo() => LemmaInfoRepo.get(
|
||||
_lemmaInfoRequest,
|
||||
);
|
||||
|
||||
bool get isContentWord =>
|
||||
PartOfSpeechEnumExtensions.fromString(category)?.isContentWord ?? false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
|||
import 'package:fluffychat/pangea/constructs/construct_form.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
|
|
@ -241,9 +240,6 @@ class PangeaToken {
|
|||
ConstructForm get vocabForm =>
|
||||
ConstructForm(form: text.content, cId: vocabConstructID);
|
||||
|
||||
Future<void> setEmoji(List<String> emojis) =>
|
||||
vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(emojis: emojis));
|
||||
|
||||
Set<String> morphActivityDistractors(
|
||||
MorphFeaturesEnum morphFeature,
|
||||
String morphTag,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/lemma_emoji_setter_mixin.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
|
|
@ -28,74 +29,70 @@ class LemmaHighlightEmojiRow extends StatefulWidget {
|
|||
LemmaHighlightEmojiRowState createState() => LemmaHighlightEmojiRowState();
|
||||
}
|
||||
|
||||
class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow> {
|
||||
String? displayEmoji;
|
||||
class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
|
||||
with LemmaEmojiSetter {
|
||||
bool _showShimmer = true;
|
||||
bool _hasShimmered = false;
|
||||
String? _selectedEmoji;
|
||||
|
||||
late StreamSubscription<AnalyticsStreamUpdate> _analyticsSubscription;
|
||||
Timer? _shimmerTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
displayEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
_showShimmer = (displayEmoji == null);
|
||||
}
|
||||
|
||||
void _startShimmer() {
|
||||
if (!widget.controller.isLoading && _showShimmer) {
|
||||
Future.delayed(const Duration(milliseconds: 1500), () {
|
||||
if (mounted) {
|
||||
setState(() => _showShimmer = false);
|
||||
setState(() => _hasShimmered = true);
|
||||
}
|
||||
});
|
||||
}
|
||||
_analyticsSubscription = MatrixState
|
||||
.pangeaController.getAnalytics.analyticsStream.stream
|
||||
.listen(_onAnalyticsUpdate);
|
||||
_setShimmer();
|
||||
}
|
||||
|
||||
@override
|
||||
didUpdateWidget(LemmaHighlightEmojiRow oldWidget) {
|
||||
//Reset shimmer state for diff constructs in 2 column mode
|
||||
if (oldWidget.cId != widget.cId) {
|
||||
setState(() {
|
||||
displayEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
_showShimmer = (displayEmoji == null);
|
||||
_hasShimmered = false;
|
||||
});
|
||||
}
|
||||
void didUpdateWidget(LemmaHighlightEmojiRow oldWidget) {
|
||||
if (oldWidget.cId != widget.cId) _setShimmer();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_analyticsSubscription.cancel();
|
||||
_shimmerTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String transformTargetId(String emoji) =>
|
||||
"emoji-choice-item-$emoji-${widget.cId.lemma}";
|
||||
void _setShimmer() {
|
||||
setState(() {
|
||||
_selectedEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
_showShimmer = _selectedEmoji == null;
|
||||
|
||||
Future<void> setEmoji(String emoji, BuildContext context) async {
|
||||
try {
|
||||
final String? userSetEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
setState(() => displayEmoji = emoji);
|
||||
await widget.cId.setEmojiWithXP(
|
||||
emoji: emoji,
|
||||
isFromCorrectAnswer: false,
|
||||
);
|
||||
if (userSetEmoji == null) {
|
||||
OverlayUtil.showOverlay(
|
||||
overlayKey: "${transformTargetId(emoji)}_points",
|
||||
followerAnchor: Alignment.bottomCenter,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
context: context,
|
||||
child: PointsGainedAnimation(
|
||||
points: 2,
|
||||
targetID: transformTargetId(emoji),
|
||||
),
|
||||
transformTargetId: transformTargetId(emoji),
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
ignorePointer: true,
|
||||
);
|
||||
if (_showShimmer) {
|
||||
_shimmerTimer?.cancel();
|
||||
_shimmerTimer = Timer(const Duration(milliseconds: 1500), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_showShimmer = false;
|
||||
_shimmerTimer?.cancel();
|
||||
_shimmerTimer = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onAnalyticsUpdate(AnalyticsStreamUpdate update) {
|
||||
if (update.targetID != null) {
|
||||
OverlayUtil.showPointsGained(update.targetID!, context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setEmoji(String emoji, BuildContext context) async {
|
||||
try {
|
||||
setState(() => _selectedEmoji = emoji);
|
||||
await setLemmaEmoji(
|
||||
widget.cId,
|
||||
emoji,
|
||||
"emoji-choice-item-$emoji-${widget.cId.lemma}",
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s);
|
||||
|
|
@ -107,7 +104,6 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow> {
|
|||
if (widget.controller.isLoading) {
|
||||
return const CircularProgressIndicator.adaptive();
|
||||
}
|
||||
_startShimmer();
|
||||
|
||||
final emojis = widget.controller.lemmaInfo?.emoji;
|
||||
if (widget.controller.error != null || emojis == null || emojis.isEmpty) {
|
||||
|
|
@ -129,10 +125,11 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow> {
|
|||
.map(
|
||||
(emoji) => EmojiChoiceItem(
|
||||
emoji: emoji,
|
||||
onSelectEmoji: () => setEmoji(emoji, context),
|
||||
isDisplay: (displayEmoji == emoji),
|
||||
showShimmer: (_showShimmer && !_hasShimmered),
|
||||
transformTargetId: transformTargetId(emoji),
|
||||
onSelectEmoji: () => _setEmoji(emoji, context),
|
||||
isDisplay: _selectedEmoji == emoji,
|
||||
showShimmer: _showShimmer,
|
||||
transformTargetId:
|
||||
"emoji-choice-item-$emoji-${widget.cId.lemma}",
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,16 @@ class UserSetLemmaInfo {
|
|||
};
|
||||
}
|
||||
|
||||
UserSetLemmaInfo copyWith({
|
||||
List<String>? emojis,
|
||||
String? meaning,
|
||||
}) {
|
||||
return UserSetLemmaInfo(
|
||||
emojis: emojis ?? this.emojis,
|
||||
meaning: meaning ?? this.meaning,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/lemma_emoji_setter_mixin.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_emoji_picker.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TokenEmojiButton extends StatefulWidget {
|
||||
final ValueNotifier<SelectMode?> selectModeNotifier;
|
||||
final bool enabled;
|
||||
final String? emoji;
|
||||
final ValueNotifier<PangeaToken?> selectedTokenNotifier;
|
||||
|
||||
final PangeaToken? token;
|
||||
final String? targetId;
|
||||
final VoidCallback? onSelect;
|
||||
final bool enabled;
|
||||
|
||||
const TokenEmojiButton({
|
||||
super.key,
|
||||
required this.selectModeNotifier,
|
||||
this.enabled = true,
|
||||
this.emoji,
|
||||
required this.selectedTokenNotifier,
|
||||
this.token,
|
||||
this.targetId,
|
||||
this.onSelect,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -25,24 +33,30 @@ class TokenEmojiButton extends StatefulWidget {
|
|||
}
|
||||
|
||||
class TokenEmojiButtonState extends State<TokenEmojiButton>
|
||||
with TickerProviderStateMixin {
|
||||
with TickerProviderStateMixin, LemmaEmojiSetter {
|
||||
final double buttonSize = 20.0;
|
||||
SelectMode? _prevMode;
|
||||
AnimationController? _controller;
|
||||
Animation<double>? _sizeAnimation;
|
||||
|
||||
String? _emoji;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_emoji = widget.token?.vocabConstructID.userSetEmoji.firstOrNull;
|
||||
|
||||
_initAnimation();
|
||||
_prevMode = widget.selectModeNotifier.value;
|
||||
widget.selectModeNotifier.addListener(_onUpdateSelectMode);
|
||||
widget.selectedTokenNotifier.addListener(_onSelectToken);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
widget.selectModeNotifier.removeListener(_onUpdateSelectMode);
|
||||
widget.selectedTokenNotifier.removeListener(_onSelectToken);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +87,69 @@ class TokenEmojiButtonState extends State<TokenEmojiButton>
|
|||
_prevMode = mode;
|
||||
}
|
||||
|
||||
void _onSelectToken() {
|
||||
final selected = widget.selectedTokenNotifier.value;
|
||||
if (selected != null && selected == widget.token) {
|
||||
showTokenEmojiPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void showTokenEmojiPopup() {
|
||||
if (widget.targetId == null || widget.token == null) return;
|
||||
OverlayUtil.showPositionedCard(
|
||||
overlayKey: "overlay_emoji_selector",
|
||||
context: context,
|
||||
cardToShow: LemmaMeaningBuilder(
|
||||
langCode:
|
||||
MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
constructId: widget.token!.vocabConstructID,
|
||||
builder: (context, controller) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
),
|
||||
child: LemmaEmojiPicker(
|
||||
emojis: controller.lemmaInfo?.emoji ?? [],
|
||||
onSelect: (emoji) {
|
||||
_setTokenEmoji(emoji);
|
||||
MatrixState.pAnyState.closeOverlay("overlay_emoji_selector");
|
||||
},
|
||||
loading: controller.isLoading,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
transformTargetId: widget.targetId!,
|
||||
closePrevOverlay: false,
|
||||
addBorder: false,
|
||||
maxWidth: (40 * 5) + (4 * 5) + 16,
|
||||
maxHeight: 60,
|
||||
);
|
||||
}
|
||||
|
||||
void _setTokenEmoji(String emoji) {
|
||||
setState(() => _emoji = emoji);
|
||||
|
||||
if (widget.targetId == null || widget.token == null) return;
|
||||
setLemmaEmoji(
|
||||
widget.token!.vocabConstructID,
|
||||
emoji,
|
||||
widget.targetId,
|
||||
).catchError((e, s) {
|
||||
ErrorHandler.logError(
|
||||
data: widget.token!.toJson(),
|
||||
e: e,
|
||||
s: s,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_sizeAnimation == null) {
|
||||
|
|
@ -81,11 +158,11 @@ class TokenEmojiButtonState extends State<TokenEmojiButton>
|
|||
|
||||
final child = widget.enabled
|
||||
? InkWell(
|
||||
onTap: widget.onSelect,
|
||||
onTap: showTokenEmojiPopup,
|
||||
borderRadius: BorderRadius.circular(99.0),
|
||||
child: widget.emoji != null
|
||||
child: _emoji != null
|
||||
? Text(
|
||||
widget.emoji!,
|
||||
_emoji!,
|
||||
style: TextStyle(fontSize: buttonSize - 4.0),
|
||||
textScaler: TextScaler.noScaling,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
// import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
// import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
// import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
// import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
// import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
|
||||
// import 'package:fluffychat/pangea/choreographer/widgets/it_shimmer.dart';
|
||||
// import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
// import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
// import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
// import 'package:fluffychat/widgets/matrix.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:fluffychat/l10n/l10n.dart';
|
||||
|
||||
// class WordEmojiChoice extends StatefulWidget {
|
||||
// const WordEmojiChoice({
|
||||
// super.key,
|
||||
// required this.constructID,
|
||||
// required this.onEmojiChosen,
|
||||
// required this.form,
|
||||
// this.roomId,
|
||||
// this.eventId,
|
||||
// });
|
||||
|
||||
// final ConstructIdentifier constructID;
|
||||
// final String form;
|
||||
// final String? roomId;
|
||||
// final String? eventId;
|
||||
// final void Function() onEmojiChosen;
|
||||
|
||||
// @override
|
||||
// WordEmojiChoiceState createState() => WordEmojiChoiceState();
|
||||
// }
|
||||
|
||||
// class WordEmojiChoiceState extends State<WordEmojiChoice> {
|
||||
// String? localSelected;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// localSelected = widget.constructID.userSetEmoji.single;
|
||||
// }
|
||||
|
||||
// Future<void> onChoice(BuildContext context, emoji) async {
|
||||
// setState(() => localSelected = emoji);
|
||||
|
||||
// MatrixState.pangeaController.putAnalytics.setState(
|
||||
// AnalyticsStream(
|
||||
// eventId: widget.eventId,
|
||||
// roomId: widget.roomId,
|
||||
// constructs: [
|
||||
// OneConstructUse(
|
||||
// useType: ConstructUseTypeEnum.em,
|
||||
// lemma: widget.constructID.lemma,
|
||||
// constructType: ConstructTypeEnum.vocab,
|
||||
// metadata: ConstructUseMetaData(
|
||||
// roomId: widget.roomId,
|
||||
// timeStamp: DateTime.now(),
|
||||
// eventId: widget.eventId,
|
||||
// ),
|
||||
// category: widget.constructID.category,
|
||||
// form: widget.form,
|
||||
// ),
|
||||
// ],
|
||||
// origin: AnalyticsUpdateOrigin.wordZoom,
|
||||
// ),
|
||||
// );
|
||||
|
||||
// await widget.constructID.setEmoji(emoji);
|
||||
|
||||
// await Future.delayed(
|
||||
// const Duration(milliseconds: choiceArrayAnimationDuration),
|
||||
// );
|
||||
|
||||
// widget.onEmojiChosen();
|
||||
|
||||
// setState(() => {});
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return SingleChildScrollView(
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: [
|
||||
// FutureBuilder(
|
||||
// future: widget.constructID.getEmojiChoices(),
|
||||
// builder: (context, snapshot) {
|
||||
// if (snapshot.hasError) {
|
||||
// return Text(L10n.of(context).oopsSomethingWentWrong);
|
||||
// }
|
||||
|
||||
// if (snapshot.connectionState == ConnectionState.waiting ||
|
||||
// snapshot.data == null) {
|
||||
// return const ItShimmer(originalSpan: "😀", fontSize: 26);
|
||||
// }
|
||||
|
||||
// return ChoicesArray(
|
||||
// isLoading: snapshot.connectionState == ConnectionState.waiting,
|
||||
// choices: snapshot.data!
|
||||
// .map(
|
||||
// (emoji) => Choice(
|
||||
// color: localSelected == emoji
|
||||
// ? Theme.of(context).colorScheme.primary
|
||||
// : Colors.transparent,
|
||||
// text: emoji,
|
||||
// isGold: localSelected == emoji,
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// onPressed: (emoji, index) => onChoice(context, emoji),
|
||||
// originalSpan: "😀",
|
||||
// uniqueKeyForLayerLink: (int index) => "emojiChoice$index",
|
||||
// selectedChoiceIndex: snapshot.data!.indexWhere(
|
||||
// (element) => element == widget.constructID.userSetEmoji,
|
||||
// ),
|
||||
// tts: null,
|
||||
// fontSize: 26,
|
||||
// enableMultiSelect: true,
|
||||
// isActive: true,
|
||||
// overflowMode: OverflowMode.horizontalScroll,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// const InstructionsInlineTooltip(
|
||||
// instructionsEnum: InstructionsEnum.chooseEmoji,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
@ -15,20 +15,16 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.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_emoji_picker.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/tokens_util.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
/// Controls data at the top level of the toolbar (mainly token / toolbar mode selection)
|
||||
|
|
@ -63,6 +59,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
Event get event => widget._event;
|
||||
|
||||
PangeaTokenText? _selectedSpan;
|
||||
ValueNotifier<PangeaToken?> selectedTokenNotifier = ValueNotifier(null);
|
||||
|
||||
List<PangeaTokenText>? _highlightedTokens;
|
||||
|
||||
|
|
@ -96,6 +93,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
);
|
||||
selectModeController.dispose();
|
||||
practiceController.dispose();
|
||||
selectedTokenNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -201,17 +199,35 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
if (selectedSpan == _selectedSpan) return;
|
||||
// if (selectedMorph != null) {
|
||||
// selectedMorph = null;
|
||||
// }
|
||||
|
||||
_selectedSpan = selectedSpan;
|
||||
if (selectedMode.value == SelectMode.emoji && selectedToken != null) {
|
||||
showTokenEmojiPopup(selectedToken!);
|
||||
}
|
||||
selectedTokenNotifier.value = selectedToken;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
if (selectedToken != null) _onSelectNewToken(selectedToken!);
|
||||
if (selectedToken != null && isNewToken(selectedToken!)) {
|
||||
final token = selectedToken!;
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: event.eventId,
|
||||
roomId: event.room.id,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.click,
|
||||
lemma: token.lemma.text,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: event.room.id,
|
||||
timeStamp: DateTime.now(),
|
||||
eventId: event.eventId,
|
||||
),
|
||||
category: token.pos,
|
||||
form: token.text.content,
|
||||
xp: ConstructUseTypeEnum.click.pointValue,
|
||||
),
|
||||
],
|
||||
targetID: "word-zoom-card-${token.text.uniqueKey}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,32 +319,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
updateSelectedSpan(token.text);
|
||||
}
|
||||
|
||||
void _onSelectNewToken(PangeaToken token) {
|
||||
if (!isNewToken(token)) return;
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: event.eventId,
|
||||
roomId: event.room.id,
|
||||
constructs: [
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.click,
|
||||
lemma: token.lemma.text,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: event.room.id,
|
||||
timeStamp: DateTime.now(),
|
||||
eventId: event.eventId,
|
||||
),
|
||||
category: token.pos,
|
||||
form: token.text.content,
|
||||
xp: ConstructUseTypeEnum.click.pointValue,
|
||||
),
|
||||
],
|
||||
targetID: "word-zoom-card-${token.text.uniqueKey}",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Whether the given token is currently selected or highlighted
|
||||
bool isTokenSelected(PangeaToken token) {
|
||||
final isSelected = _selectedSpan?.offset == token.text.offset &&
|
||||
|
|
@ -346,58 +336,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
);
|
||||
}
|
||||
|
||||
void showTokenEmojiPopup(
|
||||
PangeaToken token,
|
||||
) {
|
||||
OverlayUtil.showPositionedCard(
|
||||
overlayKey: "overlay_emoji_selector_${event.eventId}",
|
||||
context: context,
|
||||
cardToShow: LemmaMeaningBuilder(
|
||||
langCode:
|
||||
MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
constructId: token.vocabConstructID,
|
||||
builder: (context, controller) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
),
|
||||
child: LemmaEmojiPicker(
|
||||
emojis: controller.lemmaInfo?.emoji ?? [],
|
||||
onSelect: (emoji) async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _setTokenEmoji(token, emoji),
|
||||
);
|
||||
if (mounted && !resp.isError) {
|
||||
MatrixState.pAnyState.closeOverlay(
|
||||
"overlay_emoji_selector_${event.eventId}",
|
||||
);
|
||||
}
|
||||
},
|
||||
loading: controller.isLoading,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
transformTargetId: tokenEmojiPopupKey(token),
|
||||
closePrevOverlay: false,
|
||||
addBorder: false,
|
||||
maxWidth: (40 * 5) + (4 * 5) + 16,
|
||||
maxHeight: 60,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setTokenEmoji(PangeaToken token, String emoji) async {
|
||||
await token.setEmoji([emoji]);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
String tokenEmojiPopupKey(PangeaToken token) =>
|
||||
"${token.uniqueId}_${event.eventId}_emoji_button";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.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/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
|
|
@ -118,6 +117,9 @@ class PracticeController with ChangeNotifier {
|
|||
? _activity!.onMultipleChoiceSelect(token, choice)
|
||||
: _activity!.onMatch(token, choice);
|
||||
|
||||
final targetId =
|
||||
"message-token-${token.text.uniqueKey}-${pangeaMessageEvent.eventId}";
|
||||
|
||||
// we don't take off points for incorrect emoji matches
|
||||
if (_activity!.activityType != ActivityTypeEnum.emoji || isCorrect) {
|
||||
final constructUseType = _activity!.practiceTarget.record.responses.last
|
||||
|
|
@ -143,25 +145,25 @@ class PracticeController with ChangeNotifier {
|
|||
xp: constructUseType.pointValue,
|
||||
),
|
||||
],
|
||||
targetID:
|
||||
"message-token-${token.text.uniqueKey}-${pangeaMessageEvent.eventId}",
|
||||
targetID: targetId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isCorrect) {
|
||||
if (_activity!.activityType == ActivityTypeEnum.emoji) {
|
||||
choice.form.cId.setEmojiWithXP(
|
||||
emoji: choice.choiceContent,
|
||||
isFromCorrectAnswer: true,
|
||||
eventId: pangeaMessageEvent.eventId,
|
||||
roomId: pangeaMessageEvent.room.id,
|
||||
choice.form.cId.setUserLemmaInfo(
|
||||
choice.form.cId.userLemmaInfo.copyWith(
|
||||
emojis: [choice.choiceContent],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_activity!.activityType == ActivityTypeEnum.wordMeaning) {
|
||||
choice.form.cId.setUserLemmaInfo(
|
||||
UserSetLemmaInfo(meaning: choice.choiceContent),
|
||||
choice.form.cId.userLemmaInfo.copyWith(
|
||||
meaning: choice.choiceContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue