3895 emoji sequence on clicking the words in a sentence of the target language (#4004)
* cleanup * feat: toolbar emoji mode
This commit is contained in:
parent
414d28f78d
commit
802465c92c
35 changed files with 429 additions and 310 deletions
|
|
@ -165,7 +165,6 @@ class ChatEventList extends StatelessWidget {
|
|||
controller.scrollToEventId(eventId),
|
||||
longPressSelect: controller.selectedEvents.isNotEmpty,
|
||||
// #Pangea
|
||||
immersionMode: controller.choreographer.immersionMode,
|
||||
controller: controller,
|
||||
isButton: event.eventId == controller.buttonEventID,
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.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/message_token_text/message_token_button.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/token_emoji_button.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/token_practice_button.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/tokens_util.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';
|
||||
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -44,9 +46,6 @@ class HtmlMessage extends StatelessWidget {
|
|||
final Event? prevEvent;
|
||||
final bool isTransitionAnimation;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
final bool Function(PangeaToken)? isHighlighted;
|
||||
final bool Function(PangeaToken)? isSelected;
|
||||
final void Function(PangeaToken)? onClick;
|
||||
// Pangea#
|
||||
|
||||
|
|
@ -68,8 +67,6 @@ class HtmlMessage extends StatelessWidget {
|
|||
required this.controller,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
this.isHighlighted,
|
||||
this.isSelected,
|
||||
this.onClick,
|
||||
this.isTransitionAnimation = false,
|
||||
this.readingAssistanceMode,
|
||||
|
|
@ -276,7 +273,6 @@ class HtmlMessage extends StatelessWidget {
|
|||
return inverted.join().trim();
|
||||
}
|
||||
|
||||
debugPrint("HTML after adding token tags: $result");
|
||||
return result.join().trim();
|
||||
}
|
||||
|
||||
|
|
@ -414,12 +410,12 @@ class HtmlMessage extends StatelessWidget {
|
|||
int.tryParse(node.attributes['length'] ?? '') ?? 0,
|
||||
);
|
||||
|
||||
final selected = token != null && isSelected != null
|
||||
? isSelected!.call(token)
|
||||
final selected = token != null && overlayController != null
|
||||
? overlayController!.isTokenSelected(token)
|
||||
: false;
|
||||
|
||||
final highlighted = token != null && isHighlighted != null
|
||||
? isHighlighted!.call(token)
|
||||
final highlighted = token != null && overlayController != null
|
||||
? overlayController!.isTokenHighlighted(token)
|
||||
: false;
|
||||
|
||||
final isNew = token != null && newTokens.contains(token.text);
|
||||
|
|
@ -437,8 +433,17 @@ class HtmlMessage extends StatelessWidget {
|
|||
: PlaceholderAlignment.middle,
|
||||
child: Column(
|
||||
children: [
|
||||
if (token != null &&
|
||||
overlayController?.selectedMode == SelectMode.emoji)
|
||||
TokenEmojiButton(
|
||||
token: token,
|
||||
eventId: event.eventId,
|
||||
targetId: overlayController!.tokenEmojiPopupKey(token),
|
||||
onSelect: () =>
|
||||
overlayController!.showTokenEmojiPopup(token),
|
||||
),
|
||||
if (renderer.showCenterStyling && token != null)
|
||||
MessageTokenButton(
|
||||
TokenPracticeButton(
|
||||
token: token,
|
||||
overlayController: overlayController,
|
||||
textStyle: renderer.style(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ class Message extends StatelessWidget {
|
|||
final ScrollController scrollController;
|
||||
final List<Color> colors;
|
||||
// #Pangea
|
||||
final bool immersionMode;
|
||||
final ChatController controller;
|
||||
final bool isButton;
|
||||
// Pangea#
|
||||
|
|
@ -77,7 +76,6 @@ class Message extends StatelessWidget {
|
|||
required this.scrollController,
|
||||
required this.colors,
|
||||
// #Pangea
|
||||
required this.immersionMode,
|
||||
required this.controller,
|
||||
this.isButton = false,
|
||||
// Pangea#
|
||||
|
|
@ -737,8 +735,6 @@ class Message extends StatelessWidget {
|
|||
// #Pangea
|
||||
pangeaMessageEvent:
|
||||
pangeaMessageEvent,
|
||||
immersionMode:
|
||||
immersionMode,
|
||||
controller:
|
||||
controller,
|
||||
nextEvent:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class MessageContent extends StatelessWidget {
|
|||
//question: are there any performance benefits to using booleans
|
||||
//here rather than passing the choreographer? pangea rich text, a widget
|
||||
//further down in the chain is also using pangeaController so its not constant
|
||||
final bool immersionMode;
|
||||
final MessageOverlayController? overlayController;
|
||||
final ChatController controller;
|
||||
final Event? nextEvent;
|
||||
|
|
@ -58,7 +57,6 @@ class MessageContent extends StatelessWidget {
|
|||
required this.selected,
|
||||
// #Pangea
|
||||
this.pangeaMessageEvent,
|
||||
required this.immersionMode,
|
||||
this.overlayController,
|
||||
required this.controller,
|
||||
this.nextEvent,
|
||||
|
|
@ -362,8 +360,6 @@ class MessageContent extends StatelessWidget {
|
|||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
isHighlighted: overlayController?.isTokenHighlighted,
|
||||
isSelected: overlayController?.isTokenSelected,
|
||||
onClick: event.isActivityMessage ? null : onClick,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
|
|
|
|||
|
|
@ -269,6 +269,42 @@ extension AnalyticsRoomExtension on Room {
|
|||
}
|
||||
}
|
||||
|
||||
UserSetLemmaInfo? getUserSetLemmaInfo(ConstructIdentifier cId) {
|
||||
final state = getState(PangeaEventTypes.userSetLemmaInfo, cId.string);
|
||||
if (state == null) return null;
|
||||
try {
|
||||
return UserSetLemmaInfo.fromJson(state.content);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"roomID": id,
|
||||
"stateContent": state.content,
|
||||
"stateKey": state.stateKey,
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setUserSetLemmaInfo(
|
||||
ConstructIdentifier cId,
|
||||
UserSetLemmaInfo info,
|
||||
) async {
|
||||
final syncFuture = client.onRoomState.stream.firstWhere((event) {
|
||||
return event.roomId == id &&
|
||||
event.state.type == PangeaEventTypes.userSetLemmaInfo;
|
||||
});
|
||||
client.setRoomStateWithKey(
|
||||
id,
|
||||
PangeaEventTypes.userSetLemmaInfo,
|
||||
cId.string,
|
||||
info.toJson(),
|
||||
);
|
||||
await syncFuture.timeout(const Duration(seconds: 10));
|
||||
}
|
||||
|
||||
List<String> get activityRoomIds {
|
||||
final state = getState(PangeaEventTypes.activityRoomIds);
|
||||
if (state?.content[ModelKey.roomIds] is List) {
|
||||
|
|
|
|||
|
|
@ -705,12 +705,6 @@ class Choreographer {
|
|||
chatController.room,
|
||||
);
|
||||
|
||||
bool get immersionMode =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
ToolSetting.immersionMode,
|
||||
chatController.room,
|
||||
);
|
||||
|
||||
bool get isAutoIGCEnabled =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
ToolSetting.autoIGC,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OverlayContainer extends StatelessWidget {
|
||||
|
|
@ -40,8 +42,8 @@ class OverlayContainer extends StatelessWidget {
|
|||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
minHeight: 100,
|
||||
minWidth: 100,
|
||||
minHeight: min(100, maxHeight),
|
||||
minWidth: min(100, maxWidth),
|
||||
),
|
||||
child: isScrollable ? SingleChildScrollView(child: content) : content,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ 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/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/emojis/emoji_stack.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/message_token_button.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/token_practice_button.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
|
||||
import 'package:fluffychat/pangea/morphs/parts_of_speech_enum.dart';
|
||||
|
|
@ -162,31 +162,9 @@ class ConstructIdentifier {
|
|||
UserSetLemmaInfo? get userLemmaInfo {
|
||||
switch (type) {
|
||||
case ConstructTypeEnum.vocab:
|
||||
final dynamic lemmaInfoContent = MatrixState
|
||||
.pangeaController.matrixState.client
|
||||
return MatrixState.pangeaController.matrixState.client
|
||||
.analyticsRoomLocal()
|
||||
?.getState(PangeaEventTypes.userSetLemmaInfo, string)
|
||||
?.content;
|
||||
if (lemmaInfoContent != null && lemmaInfoContent is Map) {
|
||||
try {
|
||||
return UserSetLemmaInfo.fromJson(
|
||||
lemmaInfoContent as Map<String, dynamic>,
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
data: {
|
||||
"construct": string,
|
||||
"content": lemmaInfoContent,
|
||||
},
|
||||
s: s,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
?.getUserSetLemmaInfo(this);
|
||||
case ConstructTypeEnum.morph:
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -204,23 +182,10 @@ class ConstructIdentifier {
|
|||
|
||||
final analyticsRoom = await client.getMyAnalyticsRoom(l2);
|
||||
if (analyticsRoom == null) return;
|
||||
if (userLemmaInfo == newLemmaInfo) return;
|
||||
|
||||
try {
|
||||
final syncFuture = client.onRoomState.stream.firstWhere((event) {
|
||||
return event.roomId == analyticsRoom.id &&
|
||||
event.state.type == PangeaEventTypes.userSetLemmaInfo;
|
||||
});
|
||||
|
||||
client.setRoomStateWithKey(
|
||||
analyticsRoom.id,
|
||||
PangeaEventTypes.userSetLemmaInfo,
|
||||
string,
|
||||
UserSetLemmaInfo(
|
||||
emojis: newLemmaInfo.emojis ?? userLemmaInfo?.emojis,
|
||||
meaning: newLemmaInfo.meaning ?? userLemmaInfo?.meaning,
|
||||
).toJson(),
|
||||
);
|
||||
await syncFuture;
|
||||
await analyticsRoom.setUserSetLemmaInfo(this, newLemmaInfo);
|
||||
} catch (err, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
|
|
|
|||
|
|
@ -444,14 +444,9 @@ class PangeaToken {
|
|||
ConstructForm get vocabForm =>
|
||||
ConstructForm(form: text.content, cId: vocabConstructID);
|
||||
|
||||
/// [setEmoji] sets the emoji for the lemma
|
||||
/// NOTE: assumes that the language of the lemma is the same as the user's current l2
|
||||
Future<void> setEmoji(List<String> emojis) =>
|
||||
vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(emojis: emojis));
|
||||
|
||||
Future<void> setMeaning(String meaning) =>
|
||||
vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(meaning: meaning));
|
||||
|
||||
/// [getEmoji] gets the emoji for the lemma
|
||||
/// NOTE: assumes that the language of the lemma is the same as the user's current l2
|
||||
List<String> getEmoji() => vocabConstructID.userSetEmoji;
|
||||
|
|
@ -570,4 +565,6 @@ class PangeaToken {
|
|||
return daysSinceLastUseByType(a, morphFeature) *
|
||||
(vocabConstructID.isContentWord ? 10 : 9);
|
||||
}
|
||||
|
||||
String get uniqueId => "${text.content}::${text.offset}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart
|
|||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.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/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
|
|||
46
lib/pangea/lemmas/lemma_emoji_picker.dart
Normal file
46
lib/pangea/lemmas/lemma_emoji_picker.dart
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart';
|
||||
|
||||
class LemmaEmojiPicker extends StatelessWidget {
|
||||
final List<String> emojis;
|
||||
final Function(String) onSelect;
|
||||
|
||||
final bool loading;
|
||||
final Function(String)? disabled;
|
||||
|
||||
const LemmaEmojiPicker({
|
||||
super.key,
|
||||
required this.emojis,
|
||||
required this.onSelect,
|
||||
this.disabled,
|
||||
this.loading = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
width: (40 * 5) + (4 * 5) + 16, // 5 items max + padding
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: loading
|
||||
? List.generate(5, (_) => const LemmaEmojiChoicePlaceholder())
|
||||
: emojis.take(5).map((emoji) {
|
||||
final isDisabled = disabled?.call(emoji) == true;
|
||||
return Opacity(
|
||||
opacity: isDisabled ? 0.33 : 1,
|
||||
child: LemmaEmojiChoiceItem(
|
||||
content: emoji,
|
||||
onTap: isDisabled ? null : () => onSelect(emoji),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_emoji_picker.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -74,36 +74,11 @@ class LemmaReactionPicker extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4.0,
|
||||
children: loading
|
||||
? [1, 2, 3, 4, 5]
|
||||
.map(
|
||||
(e) => const LemmaEmojiChoicePlaceholder(),
|
||||
)
|
||||
.toList()
|
||||
: emojis
|
||||
.map(
|
||||
(emoji) => Opacity(
|
||||
opacity: sentReactions.contains(
|
||||
emoji,
|
||||
)
|
||||
? 0.33
|
||||
: 1,
|
||||
child: LemmaEmojiChoiceItem(
|
||||
content: emoji,
|
||||
onTap: () => sentReactions.contains(emoji)
|
||||
? null
|
||||
: setEmoji(emoji, context),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
return LemmaEmojiPicker(
|
||||
emojis: emojis,
|
||||
onSelect: (emoji) => setEmoji(emoji, context),
|
||||
disabled: (emoji) => sentReactions.contains(emoji),
|
||||
loading: loading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
class UserSetLemmaInfo {
|
||||
final String? meaning;
|
||||
final List<String>? emojis;
|
||||
|
|
@ -26,9 +28,9 @@ class UserSetLemmaInfo {
|
|||
identical(this, other) ||
|
||||
other is UserSetLemmaInfo &&
|
||||
runtimeType == other.runtimeType &&
|
||||
emojis == other.emojis &&
|
||||
const ListEquality().equals(emojis, other.emojis) &&
|
||||
meaning == other.meaning;
|
||||
|
||||
@override
|
||||
int get hashCode => emojis.hashCode ^ meaning.hashCode;
|
||||
int get hashCode => meaning.hashCode ^ Object.hashAll(emojis ?? []);
|
||||
}
|
||||
|
|
|
|||
89
lib/pangea/message_token_text/token_emoji_button.dart
Normal file
89
lib/pangea/message_token_text/token_emoji_button.dart
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TokenEmojiButton extends StatefulWidget {
|
||||
final PangeaToken token;
|
||||
final String eventId;
|
||||
final String targetId;
|
||||
final VoidCallback onSelect;
|
||||
|
||||
const TokenEmojiButton({
|
||||
super.key,
|
||||
required this.token,
|
||||
required this.eventId,
|
||||
required this.targetId,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TokenEmojiButton> createState() => TokenEmojiButtonState();
|
||||
}
|
||||
|
||||
class TokenEmojiButtonState extends State<TokenEmojiButton>
|
||||
with TickerProviderStateMixin {
|
||||
final double buttonSize = 20.0;
|
||||
AnimationController? _controller;
|
||||
Animation<double>? _sizeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
|
||||
_sizeAnimation = Tween<double>(
|
||||
begin: 0,
|
||||
end: buttonSize,
|
||||
).animate(CurvedAnimation(parent: _controller!, curve: Curves.easeOut));
|
||||
|
||||
_controller?.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final emoji = widget.token.vocabConstructID.userSetEmoji.firstOrNull;
|
||||
if (_sizeAnimation != null) {
|
||||
return CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState.layerLinkAndKey(widget.targetId).link,
|
||||
child: AnimatedBuilder(
|
||||
key: MatrixState.pAnyState.layerLinkAndKey(widget.targetId).key,
|
||||
animation: _sizeAnimation!,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
height: _sizeAnimation!.value,
|
||||
width: _sizeAnimation!.value,
|
||||
alignment: Alignment.center,
|
||||
child: InkWell(
|
||||
onTap: widget.onSelect,
|
||||
borderRadius: BorderRadius.circular(99.0),
|
||||
child: emoji != null
|
||||
? Text(
|
||||
emoji,
|
||||
style: TextStyle(fontSize: buttonSize - 4.0),
|
||||
)
|
||||
: Icon(
|
||||
Icons.add_reaction_outlined,
|
||||
size: buttonSize - 4.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
|
@ -25,14 +25,14 @@ const double tokenButtonDefaultFontSize = 10;
|
|||
const int maxEmojisPerLemma = 1;
|
||||
const double estimatedEmojiWidthRatio = 2;
|
||||
|
||||
class MessageTokenButton extends StatefulWidget {
|
||||
class TokenPracticeButton extends StatefulWidget {
|
||||
final MessageOverlayController? overlayController;
|
||||
final PangeaToken token;
|
||||
final TextStyle textStyle;
|
||||
final double width;
|
||||
final bool animateIn;
|
||||
|
||||
const MessageTokenButton({
|
||||
const TokenPracticeButton({
|
||||
super.key,
|
||||
required this.overlayController,
|
||||
required this.token,
|
||||
|
|
@ -42,10 +42,10 @@ class MessageTokenButton extends StatefulWidget {
|
|||
});
|
||||
|
||||
@override
|
||||
MessageTokenButtonState createState() => MessageTokenButtonState();
|
||||
TokenPracticeButtonState createState() => TokenPracticeButtonState();
|
||||
}
|
||||
|
||||
class MessageTokenButtonState extends State<MessageTokenButton>
|
||||
class TokenPracticeButtonState extends State<TokenPracticeButton>
|
||||
with TickerProviderStateMixin {
|
||||
AnimationController? _controller;
|
||||
Animation<double>? _heightAnimation;
|
||||
|
|
@ -102,7 +102,7 @@ class MessageTokenButtonState extends State<MessageTokenButton>
|
|||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MessageTokenButton oldWidget) {
|
||||
void didUpdateWidget(covariant TokenPracticeButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_setSelected();
|
||||
if (_isEmpty != _wasEmpty) {
|
||||
|
|
@ -12,7 +12,7 @@ class LemmaEmojiChoiceItem extends StatefulWidget {
|
|||
});
|
||||
|
||||
final String content;
|
||||
final Function onTap;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
LemmaEmojiChoiceItemState createState() => LemmaEmojiChoiceItemState();
|
||||
|
|
@ -45,12 +45,7 @@ class LemmaEmojiChoiceItemState extends State<LemmaEmojiChoiceItem> {
|
|||
child: InkWell(
|
||||
onHover: (isHovered) => setState(() => _isHovered = isHovered),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
onTap: () {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
widget.onTap();
|
||||
},
|
||||
onTap: widget.onTap,
|
||||
child: Text(
|
||||
widget.content,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/widgets/choice_animation.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_form.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_model.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
|
||||
|
|
@ -32,12 +31,10 @@ const int numberOfMorphDistractors = 3;
|
|||
class MessageMorphInputBarContent extends StatefulWidget {
|
||||
final MessageOverlayController overlayController;
|
||||
final PracticeActivityModel activity;
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
|
||||
const MessageMorphInputBarContent({
|
||||
super.key,
|
||||
required this.overlayController,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.activity,
|
||||
});
|
||||
|
||||
|
|
@ -150,7 +147,7 @@ class MessageMorphInputBarContentState
|
|||
form: token.text.content,
|
||||
),
|
||||
),
|
||||
widget.pangeaMessageEvent,
|
||||
widget.overlayController.pangeaMessageEvent,
|
||||
() => overlay.setState(() {}),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -69,10 +69,8 @@ class MatchActivityCard extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
if (overlayController.toolbarMode == MessageMode.listening &&
|
||||
overlayController.pangeaMessageEvent != null)
|
||||
if (overlayController.toolbarMode == MessageMode.listening)
|
||||
MessageAudioCard(
|
||||
messageEvent: overlayController.pangeaMessageEvent!,
|
||||
overlayController: overlayController,
|
||||
),
|
||||
Wrap(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_mode_locked_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
|
@ -13,11 +12,9 @@ import 'package:fluffychat/pangea/toolbar/widgets/practice_mode_buttons.dart';
|
|||
const double minContentHeight = 120;
|
||||
|
||||
class ReadingAssistanceInputBar extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
final MessageOverlayController overlayController;
|
||||
|
||||
const ReadingAssistanceInputBar(
|
||||
this.controller,
|
||||
this.overlayController, {
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -39,17 +36,15 @@ class ReadingAssistanceInputBarState extends State<ReadingAssistanceInputBar> {
|
|||
|
||||
Widget barContent(BuildContext context) {
|
||||
Widget? content;
|
||||
final target =
|
||||
overlayController.toolbarMode.associatedActivityType != null &&
|
||||
overlayController.pangeaMessageEvent != null
|
||||
? overlayController.practiceSelection?.getSelection(
|
||||
overlayController.toolbarMode.associatedActivityType!,
|
||||
overlayController.selectedMorph?.token,
|
||||
overlayController.selectedMorph?.morph,
|
||||
)
|
||||
: null;
|
||||
final target = overlayController.toolbarMode.associatedActivityType != null
|
||||
? overlayController.practiceSelection?.getSelection(
|
||||
overlayController.toolbarMode.associatedActivityType!,
|
||||
overlayController.selectedMorph?.token,
|
||||
overlayController.selectedMorph?.morph,
|
||||
)
|
||||
: null;
|
||||
|
||||
if (overlayController.pangeaMessageEvent?.isAudioMessage == true) {
|
||||
if (overlayController.pangeaMessageEvent.isAudioMessage == true) {
|
||||
return const SizedBox();
|
||||
// return ReactionsPicker(controller);
|
||||
} else {
|
||||
|
|
@ -72,7 +67,7 @@ class ReadingAssistanceInputBarState extends State<ReadingAssistanceInputBar> {
|
|||
case MessageMode.messageTranslation:
|
||||
if (overlayController.isTranslationUnlocked) {
|
||||
content = MessageTranslationCard(
|
||||
messageEvent: overlayController.pangeaMessageEvent!,
|
||||
messageEvent: overlayController.pangeaMessageEvent,
|
||||
);
|
||||
} else {
|
||||
content = MessageModeLockedCard(controller: overlayController);
|
||||
|
|
@ -83,7 +78,6 @@ class ReadingAssistanceInputBarState extends State<ReadingAssistanceInputBar> {
|
|||
case MessageMode.listening:
|
||||
if (target != null) {
|
||||
content = PracticeActivityCard(
|
||||
pangeaMessageEvent: overlayController.pangeaMessageEvent!,
|
||||
targetTokensAndActivityType: target,
|
||||
overlayController: overlayController,
|
||||
);
|
||||
|
|
@ -96,7 +90,6 @@ class ReadingAssistanceInputBarState extends State<ReadingAssistanceInputBar> {
|
|||
case MessageMode.wordMorph:
|
||||
if (target != null) {
|
||||
content = PracticeActivityCard(
|
||||
pangeaMessageEvent: overlayController.pangeaMessageEvent!,
|
||||
targetTokensAndActivityType: target,
|
||||
overlayController: overlayController,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,11 @@ import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.
|
|||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
||||
class MessageAudioCard extends StatefulWidget {
|
||||
final PangeaMessageEvent messageEvent;
|
||||
final MessageOverlayController overlayController;
|
||||
final VoidCallback? onError;
|
||||
|
||||
const MessageAudioCard({
|
||||
super.key,
|
||||
required this.messageEvent,
|
||||
required this.overlayController,
|
||||
this.onError,
|
||||
});
|
||||
|
|
@ -40,21 +38,24 @@ class MessageAudioCardState extends State<MessageAudioCard> {
|
|||
fetchAudio();
|
||||
}
|
||||
|
||||
PangeaMessageEvent get messageEvent =>
|
||||
widget.overlayController.pangeaMessageEvent;
|
||||
|
||||
Future<void> fetchAudio() async {
|
||||
if (!mounted) return;
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final String langCode = widget.messageEvent.messageDisplayLangCode;
|
||||
final Event? localEvent = widget.messageEvent.getTextToSpeechLocal(
|
||||
final String langCode = messageEvent.messageDisplayLangCode;
|
||||
final Event? localEvent = messageEvent.getTextToSpeechLocal(
|
||||
langCode,
|
||||
widget.messageEvent.messageDisplayText,
|
||||
messageEvent.messageDisplayText,
|
||||
);
|
||||
|
||||
if (localEvent != null) {
|
||||
audioFile = await localEvent.getPangeaAudioFile();
|
||||
} else {
|
||||
audioFile = await widget.messageEvent.getMatrixAudioFile(
|
||||
audioFile = await messageEvent.getMatrixAudioFile(
|
||||
langCode,
|
||||
);
|
||||
}
|
||||
|
|
@ -69,7 +70,7 @@ class MessageAudioCardState extends State<MessageAudioCard> {
|
|||
m: 'something wrong getting audio in MessageAudioCardState',
|
||||
data: {
|
||||
'widget.messageEvent.messageDisplayLangCode':
|
||||
widget.messageEvent.messageDisplayLangCode,
|
||||
messageEvent.messageDisplayLangCode,
|
||||
},
|
||||
);
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
|
|
@ -83,9 +84,9 @@ class MessageAudioCardState extends State<MessageAudioCard> {
|
|||
: audioFile != null
|
||||
? AudioPlayerWidget(
|
||||
null,
|
||||
eventId: "${widget.messageEvent.eventId}_practice",
|
||||
roomId: widget.messageEvent.room.id,
|
||||
senderId: widget.messageEvent.senderId,
|
||||
eventId: "${messageEvent.eventId}_practice",
|
||||
roomId: messageEvent.room.id,
|
||||
senderId: messageEvent.senderId,
|
||||
matrixFile: audioFile,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class MessageMeaningButton extends StatelessWidget {
|
|||
mode: MessageMode.messageMeaning,
|
||||
overlayController: overlayController,
|
||||
buttonSize: buttonSize,
|
||||
onPressed: overlayController.updateToolbarMode,
|
||||
),
|
||||
secondChild: Container(
|
||||
width: buttonSize,
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ 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/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
|
|
@ -34,6 +36,9 @@ import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart';
|
|||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.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/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
/// Controls data at the top level of the toolbar (mainly token / toolbar mode selection)
|
||||
|
|
@ -103,6 +108,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
double maxWidth = AppConfig.toolbarMinWidth;
|
||||
|
||||
SelectMode? selectedMode;
|
||||
|
||||
/////////////////////////////////////
|
||||
/// Lifecycle
|
||||
/////////////////////////////////////
|
||||
|
|
@ -127,12 +134,12 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
Future<void> initializeTokensAndMode() async {
|
||||
try {
|
||||
if (pangeaMessageEvent?.event.messageType != MessageTypes.Text) {
|
||||
if (pangeaMessageEvent.event.messageType != MessageTypes.Text) {
|
||||
return;
|
||||
}
|
||||
|
||||
RepresentationEvent? repEvent =
|
||||
pangeaMessageEvent?.messageDisplayRepresentation;
|
||||
pangeaMessageEvent.messageDisplayRepresentation;
|
||||
|
||||
if (repEvent == null ||
|
||||
(repEvent.event == null && repEvent.tokens == null)) {
|
||||
|
|
@ -153,7 +160,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"eventID": pangeaMessageEvent?.eventId,
|
||||
"eventID": pangeaMessageEvent.eventId,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
|
|
@ -259,20 +266,15 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
if (selectedMorph != null) {
|
||||
selectedMorph = null;
|
||||
}
|
||||
// close overlay of previous token
|
||||
if (selectedToken != null) {
|
||||
MatrixState.pAnyState.closeOverlay(
|
||||
"${selectedToken!.text.uniqueKey}_toolbar",
|
||||
);
|
||||
}
|
||||
|
||||
_selectedSpan = selectedSpan;
|
||||
if (mounted) setState(() {});
|
||||
|
||||
//Commented out so onSelectNewTokens can be manually called after animation is finished
|
||||
// if (selectedToken != null && isNewToken(selectedToken!)) {
|
||||
// _onSelectNewToken(selectedToken!);
|
||||
// }
|
||||
if (selectedMode == SelectMode.emoji && selectedToken != null) {
|
||||
showTokenEmojiPopup(selectedToken!);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
if (selectedToken != null) onSelectNewToken(selectedToken!);
|
||||
}
|
||||
}
|
||||
|
||||
void updateToolbarMode(MessageMode mode) => setState(() {
|
||||
|
|
@ -331,16 +333,12 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
/////////////////////////////////////
|
||||
/// Getters
|
||||
////////////////////////////////////
|
||||
PangeaMessageEvent? get pangeaMessageEvent => PangeaMessageEvent(
|
||||
PangeaMessageEvent get pangeaMessageEvent => PangeaMessageEvent(
|
||||
event: widget._event,
|
||||
timeline: widget._timeline,
|
||||
ownMessage: widget._event.room.client.userID == widget._event.senderId,
|
||||
);
|
||||
|
||||
bool get showToolbarButtons =>
|
||||
pangeaMessageEvent != null &&
|
||||
pangeaMessageEvent!.event.messageType == MessageTypes.Text;
|
||||
|
||||
bool get hideWordCardContent =>
|
||||
readingAssistanceMode == ReadingAssistanceMode.practiceMode;
|
||||
|
||||
|
|
@ -372,7 +370,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
/// you have to complete one of the mode mini-games to unlock translation
|
||||
bool get isTranslationUnlocked =>
|
||||
pangeaMessageEvent?.ownMessage == true ||
|
||||
pangeaMessageEvent.ownMessage == true ||
|
||||
!messageInUserL2 ||
|
||||
isEmojiDone ||
|
||||
isMeaningDone ||
|
||||
|
|
@ -383,35 +381,30 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
isEmojiDone && isMeaningDone && isListeningDone && isMorphDone;
|
||||
|
||||
PracticeSelection? get practiceSelection =>
|
||||
pangeaMessageEvent?.messageDisplayRepresentation?.tokens != null
|
||||
pangeaMessageEvent.messageDisplayRepresentation?.tokens != null
|
||||
? PracticeSelectionRepo.get(
|
||||
pangeaMessageEvent!.messageDisplayLangCode,
|
||||
pangeaMessageEvent!.messageDisplayRepresentation!.tokens!,
|
||||
pangeaMessageEvent.messageDisplayLangCode,
|
||||
pangeaMessageEvent.messageDisplayRepresentation!.tokens!,
|
||||
)
|
||||
: null;
|
||||
|
||||
bool get messageInUserL2 =>
|
||||
pangeaMessageEvent?.messageDisplayLangCode.split("-")[0] ==
|
||||
pangeaMessageEvent.messageDisplayLangCode.split("-")[0] ==
|
||||
MatrixState.pangeaController.languageController.userL2?.langCodeShort;
|
||||
|
||||
PangeaToken? get selectedToken {
|
||||
if (pangeaMessageEvent?.isAudioMessage == true) {
|
||||
final stt = pangeaMessageEvent!.getSpeechToTextLocal();
|
||||
if (pangeaMessageEvent.isAudioMessage == true) {
|
||||
final stt = pangeaMessageEvent.getSpeechToTextLocal();
|
||||
if (stt == null || stt.transcript.sttTokens.isEmpty) return null;
|
||||
return stt.transcript.sttTokens
|
||||
.firstWhereOrNull((t) => isTokenSelected(t.token))
|
||||
?.token;
|
||||
}
|
||||
|
||||
return pangeaMessageEvent?.messageDisplayRepresentation?.tokens
|
||||
return pangeaMessageEvent.messageDisplayRepresentation?.tokens
|
||||
?.firstWhereOrNull(isTokenSelected);
|
||||
}
|
||||
|
||||
/// Whether the overlay is currently displaying a selection
|
||||
bool get isSelection => _selectedSpan != null || _highlightedTokens != null;
|
||||
|
||||
PangeaTokenText? get selectedSpan => _selectedSpan;
|
||||
|
||||
bool get showingExtraContent =>
|
||||
(showTranslation && translation != null) ||
|
||||
(showSpeechTranslation && speechTranslation != null) ||
|
||||
|
|
@ -424,10 +417,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
if (event.messageType == MessageTypes.Text) {
|
||||
return pangeaMessageEvent != null &&
|
||||
pangeaMessageEvent!.messageDisplayLangCode.split("-").first ==
|
||||
MatrixState
|
||||
.pangeaController.languageController.userL2!.langCodeShort;
|
||||
return pangeaMessageEvent.messageDisplayLangCode.split("-").first ==
|
||||
MatrixState.pangeaController.languageController.userL2!.langCodeShort;
|
||||
}
|
||||
|
||||
return event.messageType == MessageTypes.Audio;
|
||||
|
|
@ -459,18 +450,17 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
Future<RepresentationEvent?> _fetchNewRepEvent() async {
|
||||
final RepresentationEvent? repEvent =
|
||||
pangeaMessageEvent?.messageDisplayRepresentation;
|
||||
pangeaMessageEvent.messageDisplayRepresentation;
|
||||
|
||||
if (repEvent != null) return repEvent;
|
||||
final eventID =
|
||||
await pangeaMessageEvent?.representationByDetectedLanguage();
|
||||
final eventID = await pangeaMessageEvent.representationByDetectedLanguage();
|
||||
|
||||
if (eventID == null) return null;
|
||||
final event = await widget._event.room.getEventById(eventID);
|
||||
if (event == null) return null;
|
||||
return RepresentationEvent(
|
||||
timeline: pangeaMessageEvent!.timeline,
|
||||
parentMessageEvent: pangeaMessageEvent!.event,
|
||||
timeline: pangeaMessageEvent.timeline,
|
||||
parentMessageEvent: pangeaMessageEvent.event,
|
||||
event: event,
|
||||
);
|
||||
}
|
||||
|
|
@ -493,6 +483,13 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
PracticeTarget? practiceTargetForToken(PangeaToken token) {
|
||||
if (toolbarMode.associatedActivityType == null) return null;
|
||||
return practiceSelection
|
||||
?.activities(toolbarMode.associatedActivityType!)
|
||||
.firstWhereOrNull((a) => a.tokens.contains(token));
|
||||
}
|
||||
|
||||
void onClickOverlayMessageToken(
|
||||
PangeaToken token,
|
||||
) {
|
||||
|
|
@ -502,17 +499,16 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
/// we don't want to associate the audio with the text in this mode
|
||||
if (pangeaMessageEvent?.messageDisplayLangCode != null &&
|
||||
practiceSelection?.hasActiveActivityByToken(
|
||||
ActivityTypeEnum.wordFocusListening,
|
||||
token,
|
||||
) ==
|
||||
false ||
|
||||
if (practiceSelection?.hasActiveActivityByToken(
|
||||
ActivityTypeEnum.wordFocusListening,
|
||||
token,
|
||||
) ==
|
||||
false ||
|
||||
!hideWordCardContent) {
|
||||
TtsController.tryToSpeak(
|
||||
token.text.content,
|
||||
targetID: null,
|
||||
langCode: pangeaMessageEvent!.messageDisplayLangCode,
|
||||
langCode: pangeaMessageEvent.messageDisplayLangCode,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -521,6 +517,9 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
void onSelectNewToken(PangeaToken token) {
|
||||
if (!isNewToken(token)) return;
|
||||
final future =
|
||||
MatrixState.pangeaController.getAnalytics.analyticsStream.stream.first;
|
||||
|
||||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
eventId: event.eventId,
|
||||
|
|
@ -544,16 +543,12 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
PracticeTarget? practiceTargetForToken(PangeaToken token) {
|
||||
if (toolbarMode.associatedActivityType == null) return null;
|
||||
return practiceSelection
|
||||
?.activities(toolbarMode.associatedActivityType!)
|
||||
.firstWhereOrNull((a) => a.tokens.contains(token));
|
||||
future.then((_) {
|
||||
TokensUtil.clearNewTokenCache();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether the given token is currently selected or highlighted
|
||||
|
|
@ -564,7 +559,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
bool isNewToken(PangeaToken token) =>
|
||||
TokensUtil.isNewToken(token, pangeaMessageEvent!);
|
||||
TokensUtil.isNewToken(token, pangeaMessageEvent);
|
||||
|
||||
bool isTokenHighlighted(PangeaToken token) {
|
||||
if (_highlightedTokens == null) return false;
|
||||
|
|
@ -573,6 +568,12 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
);
|
||||
}
|
||||
|
||||
void setSelectMode(SelectMode? mode) {
|
||||
if (!mounted) return;
|
||||
if (selectedMode == mode) return;
|
||||
setState(() => selectedMode = mode);
|
||||
}
|
||||
|
||||
void setTranslation(String value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
|
@ -636,6 +637,61 @@ 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";
|
||||
|
||||
/////////////////////////////////////
|
||||
/// Build
|
||||
/////////////////////////////////////
|
||||
|
|
@ -647,7 +703,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
event: widget._event,
|
||||
nextEvent: widget._nextEvent,
|
||||
prevEvent: widget._prevEvent,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
initialSelectedToken: widget._initialSelectedToken,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ class MessageSelectionPositioner extends StatefulWidget {
|
|||
final MessageOverlayController overlayController;
|
||||
final ChatController chatController;
|
||||
final Event event;
|
||||
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
final PangeaToken? initialSelectedToken;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
|
|
@ -39,7 +37,6 @@ class MessageSelectionPositioner extends StatefulWidget {
|
|||
required this.overlayController,
|
||||
required this.chatController,
|
||||
required this.event,
|
||||
this.pangeaMessageEvent,
|
||||
this.initialSelectedToken,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
|
|
@ -59,11 +56,14 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
ScrollController? scrollController;
|
||||
|
||||
bool finishedTransition = false;
|
||||
bool startedTransition = false;
|
||||
bool _startedTransition = false;
|
||||
|
||||
ReadingAssistanceMode readingAssistanceMode =
|
||||
ReadingAssistanceMode.selectMode;
|
||||
|
||||
PangeaMessageEvent get pangeaMessageEvent =>
|
||||
widget.overlayController.pangeaMessageEvent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -151,7 +151,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
return reactionsEvents.where((e) => !e.redacted).isNotEmpty;
|
||||
}
|
||||
|
||||
double get reactionsHeight {
|
||||
double get _reactionsHeight {
|
||||
if (_reactionsRenderBox != null) {
|
||||
return _reactionsRenderBox!.size.height;
|
||||
}
|
||||
|
|
@ -277,12 +277,11 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
double get _contentHeight {
|
||||
final messageHeight =
|
||||
_overlayMessageSize?.height ?? originalMessageSize.height;
|
||||
return messageHeight + reactionsHeight + AppConfig.toolbarMenuHeight + 4.0;
|
||||
return messageHeight + _reactionsHeight + AppConfig.toolbarMenuHeight + 4.0;
|
||||
}
|
||||
|
||||
double get overheadContentHeight {
|
||||
return (widget.pangeaMessageEvent != null &&
|
||||
widget.overlayController.selectedToken != null
|
||||
return (widget.overlayController.selectedToken != null
|
||||
? AppConfig.toolbarMaxHeight
|
||||
: 40.0) +
|
||||
4.0;
|
||||
|
|
@ -290,8 +289,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
|
||||
double? get _wordCardLeftOffset {
|
||||
if (ownMessage) return null;
|
||||
if (widget.pangeaMessageEvent != null &&
|
||||
widget.overlayController.selectedToken != null &&
|
||||
if (widget.overlayController.selectedToken != null &&
|
||||
mediaQuery != null &&
|
||||
(mediaQuery!.size.width < _toolbarMaxWidth + messageLeftOffset!)) {
|
||||
return mediaQuery!.size.width - _toolbarMaxWidth - 8.0;
|
||||
|
|
@ -319,7 +317,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
if (_screenHeight == null) return false;
|
||||
final bottomOffset = _originalMessageOffset.dy +
|
||||
originalMessageSize.height +
|
||||
reactionsHeight +
|
||||
_reactionsHeight +
|
||||
AppConfig.toolbarMenuHeight +
|
||||
4.0;
|
||||
|
||||
|
|
@ -332,7 +330,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
|
||||
final messageHeight = originalMessageSize.height;
|
||||
final originalContentHeight =
|
||||
messageHeight + reactionsHeight + AppConfig.toolbarMenuHeight + 8.0;
|
||||
messageHeight + _reactionsHeight + AppConfig.toolbarMenuHeight + 8.0;
|
||||
|
||||
final screenHeight = mediaQuery!.size.height - mediaQuery!.padding.bottom;
|
||||
|
||||
|
|
@ -359,7 +357,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
void onStartedTransition() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
startedTransition = true;
|
||||
_startedTransition = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -399,7 +397,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
alignment:
|
||||
ownMessage ? Alignment.centerRight : Alignment.centerLeft,
|
||||
children: [
|
||||
if (!startedTransition) ...[
|
||||
if (!_startedTransition) ...[
|
||||
OverMessageOverlay(controller: this),
|
||||
if (shouldScroll)
|
||||
Positioned(
|
||||
|
|
@ -426,7 +424,6 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
right: 0,
|
||||
bottom: 20,
|
||||
child: ReadingAssistanceInputBar(
|
||||
widget.chatController,
|
||||
widget.overlayController,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -50,11 +50,19 @@ class OverMessageOverlay extends StatelessWidget {
|
|||
.link,
|
||||
child: OverlayCenterContent(
|
||||
event: controller.widget.event,
|
||||
messageHeight: controller.originalMessageSize.height,
|
||||
messageHeight:
|
||||
controller.widget.overlayController.selectedMode ==
|
||||
SelectMode.practice
|
||||
? controller.originalMessageSize.height
|
||||
: null,
|
||||
messageWidth:
|
||||
controller.widget.overlayController.showingExtraContent
|
||||
? max(controller.originalMessageSize.width, 150)
|
||||
: controller.originalMessageSize.width,
|
||||
controller.widget.overlayController.selectedMode ==
|
||||
SelectMode.practice
|
||||
? controller.widget.overlayController
|
||||
.showingExtraContent
|
||||
? max(controller.originalMessageSize.width, 150)
|
||||
: controller.originalMessageSize.width
|
||||
: null,
|
||||
overlayController: controller.widget.overlayController,
|
||||
chatController: controller.widget.chatController,
|
||||
nextEvent: controller.widget.nextEvent,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
child: OverlayMessage(
|
||||
key: overlayKey,
|
||||
event,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
controller: chatController,
|
||||
overlayController: overlayController,
|
||||
nextEvent: nextEvent,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
final Event? nextEvent;
|
||||
final Event? previousEvent;
|
||||
final Timeline timeline;
|
||||
final bool immersionMode;
|
||||
|
||||
final Animation<Size>? sizeAnimation;
|
||||
final double? messageWidth;
|
||||
|
|
@ -42,7 +41,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
|
||||
const OverlayMessage(
|
||||
this.event, {
|
||||
this.immersionMode = false,
|
||||
required this.overlayController,
|
||||
required this.controller,
|
||||
required this.timeline,
|
||||
|
|
@ -145,7 +143,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
isSubscribed != false;
|
||||
|
||||
final showTranscription =
|
||||
overlayController.pangeaMessageEvent?.isAudioMessage == true &&
|
||||
overlayController.pangeaMessageEvent.isAudioMessage == true &&
|
||||
isSubscribed != false;
|
||||
|
||||
final showSpeechTranslation = overlayController.showSpeechTranslation &&
|
||||
|
|
@ -348,7 +346,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
borderRadius: borderRadius,
|
||||
timeline: timeline,
|
||||
pangeaMessageEvent: overlayController.pangeaMessageEvent,
|
||||
immersionMode: immersionMode,
|
||||
overlayController: overlayController,
|
||||
controller: controller,
|
||||
nextEvent: nextEvent,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import 'dart:developer';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
|
||||
|
|
@ -23,7 +21,6 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
class MultipleChoiceActivity extends StatefulWidget {
|
||||
final PracticeActivityCardState practiceCardController;
|
||||
final PracticeActivityModel currentActivity;
|
||||
final Event event;
|
||||
final VoidCallback? onError;
|
||||
final MessageOverlayController overlayController;
|
||||
final String? initialSelectedChoice;
|
||||
|
|
@ -33,7 +30,6 @@ class MultipleChoiceActivity extends StatefulWidget {
|
|||
super.key,
|
||||
required this.practiceCardController,
|
||||
required this.currentActivity,
|
||||
required this.event,
|
||||
required this.overlayController,
|
||||
this.initialSelectedChoice,
|
||||
this.clearResponsesOnUpdate = false,
|
||||
|
|
@ -119,9 +115,8 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
|||
MatrixState.pangeaController.putAnalytics.setState(
|
||||
AnalyticsStream(
|
||||
// note - this maybe should be the activity event id
|
||||
eventId:
|
||||
widget.practiceCardController.widget.pangeaMessageEvent.eventId,
|
||||
roomId: widget.practiceCardController.widget.pangeaMessageEvent.room.id,
|
||||
eventId: widget.overlayController.event.eventId,
|
||||
roomId: widget.overlayController.event.room.id,
|
||||
constructs: currentRecordModel!.latestResponse!.toUses(
|
||||
widget.practiceCardController.currentActivity!,
|
||||
widget.practiceCardController.metadata,
|
||||
|
|
@ -216,7 +211,7 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
|||
question,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppConfig.messageTextStyle(
|
||||
widget.event,
|
||||
widget.overlayController.event,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
).merge(const TextStyle(fontStyle: FontStyle.italic)),
|
||||
),
|
||||
|
|
@ -226,15 +221,14 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
|||
ActivityTypeEnum.wordFocusListening)
|
||||
WordAudioButton(
|
||||
text: practiceActivity.multipleChoiceContent!.answers.first,
|
||||
uniqueID: "audio-activity-${widget.event.eventId}",
|
||||
uniqueID:
|
||||
"audio-activity-${widget.overlayController.event.eventId}",
|
||||
langCode: widget
|
||||
.overlayController.pangeaMessageEvent!.messageDisplayLangCode,
|
||||
.overlayController.pangeaMessageEvent.messageDisplayLangCode,
|
||||
),
|
||||
if (practiceActivity.activityType ==
|
||||
ActivityTypeEnum.hiddenWordListening)
|
||||
MessageAudioCard(
|
||||
messageEvent:
|
||||
widget.practiceCardController.widget.pangeaMessageEvent,
|
||||
overlayController: widget.overlayController,
|
||||
onError: widget.onError,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -29,13 +29,11 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
/// Handles the activities associated with a message,
|
||||
/// their navigation, and the management of completion records
|
||||
class PracticeActivityCard extends StatefulWidget {
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final PracticeTarget targetTokensAndActivityType;
|
||||
final MessageOverlayController overlayController;
|
||||
|
||||
const PracticeActivityCard({
|
||||
super.key,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.targetTokensAndActivityType,
|
||||
required this.overlayController,
|
||||
});
|
||||
|
|
@ -60,6 +58,9 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
|
||||
PracticeRecord? get currentCompletionRecord => currentActivity?.record;
|
||||
|
||||
PangeaMessageEvent? get pangeaMessageEvent =>
|
||||
widget.overlayController.pangeaMessageEvent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -142,7 +143,7 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
debugPrint(
|
||||
"fetching activity model of type: ${widget.targetTokensAndActivityType.activityType}",
|
||||
);
|
||||
debugger(when: kDebugMode);
|
||||
if (pangeaMessageEvent == null) return null;
|
||||
// check if we already have an activity matching the specs
|
||||
final tokens = widget.targetTokensAndActivityType.tokens;
|
||||
final type = widget.targetTokensAndActivityType.activityType;
|
||||
|
|
@ -167,9 +168,9 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
final req = MessageActivityRequest(
|
||||
userL1: MatrixState.pangeaController.languageController.userL1!.langCode,
|
||||
userL2: MatrixState.pangeaController.languageController.userL2!.langCode,
|
||||
messageText: widget.pangeaMessageEvent.messageDisplayText,
|
||||
messageText: pangeaMessageEvent!.messageDisplayText,
|
||||
messageTokens:
|
||||
widget.pangeaMessageEvent.messageDisplayRepresentation?.tokens ?? [],
|
||||
pangeaMessageEvent?.messageDisplayRepresentation?.tokens ?? [],
|
||||
activityQualityFeedback: activityFeedback,
|
||||
targetTokens: tokens,
|
||||
targetType: type,
|
||||
|
|
@ -179,7 +180,7 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
final PracticeActivityModelResponse activityResponse =
|
||||
await practiceGenerationController.getPracticeActivity(
|
||||
req,
|
||||
widget.pangeaMessageEvent,
|
||||
pangeaMessageEvent,
|
||||
context,
|
||||
);
|
||||
|
||||
|
|
@ -191,8 +192,8 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
}
|
||||
|
||||
ConstructUseMetaData get metadata => ConstructUseMetaData(
|
||||
eventId: widget.pangeaMessageEvent.eventId,
|
||||
roomId: widget.pangeaMessageEvent.room.id,
|
||||
eventId: widget.overlayController.event.eventId,
|
||||
roomId: widget.overlayController.event.room.id,
|
||||
timeStamp: DateTime.now(),
|
||||
);
|
||||
|
||||
|
|
@ -269,16 +270,13 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
return MessageMorphInputBarContent(
|
||||
overlayController: widget.overlayController,
|
||||
activity: currentActivity!,
|
||||
pangeaMessageEvent: widget.overlayController.pangeaMessageEvent!,
|
||||
);
|
||||
}
|
||||
return MultipleChoiceActivity(
|
||||
practiceCardController: this,
|
||||
currentActivity: currentActivity!,
|
||||
event: widget.pangeaMessageEvent.event,
|
||||
onError: _onError,
|
||||
overlayController: widget.overlayController,
|
||||
// initialSelectedChoice: selectedChoice,
|
||||
initialSelectedChoice: null,
|
||||
clearResponsesOnUpdate:
|
||||
currentActivity?.activityType == ActivityTypeEnum.emoji,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ class PracticeModeButtons extends StatelessWidget {
|
|||
child: ToolbarButton(
|
||||
mode: MessageMode.listening,
|
||||
overlayController: overlayController,
|
||||
onPressed: overlayController.updateToolbarMode,
|
||||
buttonSize: buttonSize,
|
||||
),
|
||||
),
|
||||
|
|
@ -44,7 +43,6 @@ class PracticeModeButtons extends StatelessWidget {
|
|||
child: ToolbarButton(
|
||||
mode: MessageMode.wordMorph,
|
||||
overlayController: overlayController,
|
||||
onPressed: overlayController.updateToolbarMode,
|
||||
buttonSize: buttonSize,
|
||||
),
|
||||
),
|
||||
|
|
@ -55,7 +53,6 @@ class PracticeModeButtons extends StatelessWidget {
|
|||
child: ToolbarButton(
|
||||
mode: MessageMode.wordMeaning,
|
||||
overlayController: overlayController,
|
||||
onPressed: overlayController.updateToolbarMode,
|
||||
buttonSize: buttonSize,
|
||||
),
|
||||
),
|
||||
|
|
@ -66,7 +63,6 @@ class PracticeModeButtons extends StatelessWidget {
|
|||
child: ToolbarButton(
|
||||
mode: MessageMode.wordEmoji,
|
||||
overlayController: overlayController,
|
||||
onPressed: overlayController.updateToolbarMode,
|
||||
buttonSize: buttonSize,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:matrix/matrix_api_lite/model/message_types.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
|
@ -20,13 +19,11 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
const double minCardHeight = 70;
|
||||
|
||||
class ReadingAssistanceContent extends StatefulWidget {
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final MessageOverlayController overlayController;
|
||||
final Duration animationDuration;
|
||||
|
||||
const ReadingAssistanceContent({
|
||||
super.key,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.overlayController,
|
||||
this.animationDuration = FluffyThemes.animationDuration,
|
||||
});
|
||||
|
|
@ -48,7 +45,6 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
if (widget.overlayController.practiceSelection?.hasHiddenWordActivity ??
|
||||
false) {
|
||||
return PracticeActivityCard(
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
overlayController: widget.overlayController,
|
||||
targetTokensAndActivityType: widget.overlayController.practiceSelection!
|
||||
.nextActivity(ActivityTypeEnum.hiddenWordListening)!,
|
||||
|
|
@ -58,7 +54,6 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
if (widget.overlayController.practiceSelection?.hasMessageMeaningActivity ??
|
||||
false) {
|
||||
return PracticeActivityCard(
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
overlayController: widget.overlayController,
|
||||
targetTokensAndActivityType: widget.overlayController.practiceSelection!
|
||||
.nextActivity(ActivityTypeEnum.messageMeaning)!,
|
||||
|
|
@ -121,7 +116,6 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
)
|
||||
.key,
|
||||
token: widget.overlayController.selectedToken!,
|
||||
messageEvent: widget.overlayController.pangeaMessageEvent!,
|
||||
overlayController: widget.overlayController,
|
||||
wordIsNew: widget.overlayController
|
||||
.isNewToken(widget.overlayController.selectedToken!),
|
||||
|
|
@ -132,7 +126,7 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (![MessageTypes.Text, MessageTypes.Audio].contains(
|
||||
widget.pangeaMessageEvent.event.messageType,
|
||||
widget.overlayController.pangeaMessageEvent.event.messageType,
|
||||
)) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ enum SelectMode {
|
|||
audio(Icons.volume_up),
|
||||
translate(Icons.translate),
|
||||
practice(Symbols.fitness_center),
|
||||
emoji(Icons.add_reaction_outlined),
|
||||
speechTranslation(Icons.translate);
|
||||
|
||||
final IconData icon;
|
||||
|
|
@ -43,6 +44,8 @@ enum SelectMode {
|
|||
return l10n.translationTooltip;
|
||||
case SelectMode.practice:
|
||||
return l10n.practice;
|
||||
case SelectMode.emoji:
|
||||
return l10n.emojis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,15 +148,13 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
SelectMode.audio,
|
||||
SelectMode.translate,
|
||||
SelectMode.practice,
|
||||
SelectMode.emoji,
|
||||
];
|
||||
|
||||
static List<SelectMode> get audioModes => [
|
||||
SelectMode.speechTranslation,
|
||||
// SelectMode.practice,
|
||||
];
|
||||
|
||||
SelectMode? _selectedMode;
|
||||
|
||||
bool _isLoadingAudio = false;
|
||||
PangeaAudioFile? _audioBytes;
|
||||
File? _audioFile;
|
||||
|
|
@ -172,6 +173,8 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
|
||||
MatrixState? matrix;
|
||||
|
||||
SelectMode? get _selectedMode => widget.overlayController.selectedMode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -218,20 +221,17 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
_clear();
|
||||
|
||||
if (mode == null) {
|
||||
setState(() {
|
||||
matrix?.audioPlayer?.stop();
|
||||
matrix?.audioPlayer?.seek(null);
|
||||
_selectedMode = null;
|
||||
});
|
||||
matrix?.audioPlayer?.stop();
|
||||
matrix?.audioPlayer?.seek(null);
|
||||
widget.overlayController.setSelectMode(mode);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(
|
||||
() => _selectedMode = _selectedMode == mode &&
|
||||
(mode != SelectMode.audio || _audioError != null)
|
||||
? null
|
||||
: mode,
|
||||
);
|
||||
final selectedMode = _selectedMode == mode &&
|
||||
(mode != SelectMode.audio || _audioError != null)
|
||||
? null
|
||||
: mode;
|
||||
widget.overlayController.setSelectMode(selectedMode);
|
||||
|
||||
if (_selectedMode == SelectMode.audio) {
|
||||
_playAudio();
|
||||
|
|
@ -302,7 +302,7 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
|
||||
Future<void> _playAudio() async {
|
||||
final playerID =
|
||||
"${widget.overlayController.pangeaMessageEvent?.eventId}_button";
|
||||
"${widget.overlayController.pangeaMessageEvent.eventId}_button";
|
||||
|
||||
if (matrix?.audioPlayer != null &&
|
||||
matrix?.voiceMessageEventId.value == playerID) {
|
||||
|
|
@ -319,7 +319,7 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
matrix?.audioPlayer?.dispose();
|
||||
matrix?.audioPlayer = AudioPlayer();
|
||||
matrix?.voiceMessageEventId.value =
|
||||
"${widget.overlayController.pangeaMessageEvent?.eventId}_button";
|
||||
"${widget.overlayController.pangeaMessageEvent.eventId}_button";
|
||||
|
||||
_onPlayerStateChanged =
|
||||
matrix?.audioPlayer?.playerStateStream.listen((state) {
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart
|
|||
class ToolbarButton extends StatelessWidget {
|
||||
final MessageMode mode;
|
||||
final MessageOverlayController overlayController;
|
||||
final void Function(MessageMode) onPressed;
|
||||
final double buttonSize;
|
||||
|
||||
const ToolbarButton({
|
||||
required this.mode,
|
||||
required this.overlayController,
|
||||
required this.buttonSize,
|
||||
required this.onPressed,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -36,7 +34,7 @@ class ToolbarButton extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(20),
|
||||
depressed: mode == overlayController.toolbarMode,
|
||||
color: color(context),
|
||||
onPressed: () => onPressed(mode),
|
||||
onPressed: () => overlayController.updateToolbarMode(mode),
|
||||
playSound: true,
|
||||
colorFactor:
|
||||
Theme.of(context).brightness == Brightness.light ? 0.55 : 0.3,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';
|
||||
|
||||
class WordCardSwitcher extends StatelessWidget {
|
||||
final MessageSelectionPositionerState controller;
|
||||
|
|
@ -14,15 +15,16 @@ class WordCardSwitcher extends StatelessWidget {
|
|||
alignment:
|
||||
controller.ownMessage ? Alignment.bottomRight : Alignment.bottomLeft,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: controller.widget.pangeaMessageEvent != null &&
|
||||
controller.widget.overlayController.selectedToken != null
|
||||
? ReadingAssistanceContent(
|
||||
pangeaMessageEvent: controller.widget.pangeaMessageEvent!,
|
||||
overlayController: controller.widget.overlayController,
|
||||
)
|
||||
: MessageReactionPicker(
|
||||
chatController: controller.widget.chatController,
|
||||
),
|
||||
child:
|
||||
controller.widget.overlayController.selectedMode == SelectMode.emoji
|
||||
? const SizedBox()
|
||||
: controller.widget.overlayController.selectedToken != null
|
||||
? ReadingAssistanceContent(
|
||||
overlayController: controller.widget.overlayController,
|
||||
)
|
||||
: MessageReactionPicker(
|
||||
chatController: controller.widget.chatController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,11 +43,7 @@ class _NewWordOverlayState extends State<NewWordOverlay>
|
|||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1850),
|
||||
)..addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
_xpScaleAnim = CurvedAnimation(
|
||||
parent: _controller!,
|
||||
|
|
@ -73,7 +69,6 @@ class _NewWordOverlayState extends State<NewWordOverlay>
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.overlayController.onSelectNewToken(widget.token);
|
||||
_controller?.dispose();
|
||||
MatrixState.pAnyState.closeOverlay(widget.transformTargetId);
|
||||
super.dispose();
|
||||
|
|
|
|||
|
|
@ -22,14 +22,12 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class WordZoomWidget extends StatelessWidget {
|
||||
final PangeaToken token;
|
||||
final PangeaMessageEvent messageEvent;
|
||||
final MessageOverlayController overlayController;
|
||||
final bool wordIsNew;
|
||||
|
||||
const WordZoomWidget({
|
||||
super.key,
|
||||
required this.token,
|
||||
required this.messageEvent,
|
||||
required this.overlayController,
|
||||
required this.wordIsNew,
|
||||
});
|
||||
|
|
@ -49,6 +47,8 @@ class WordZoomWidget extends StatelessWidget {
|
|||
LayerLink get layerLink =>
|
||||
MatrixState.pAnyState.layerLinkAndKey(transformTargetId).link;
|
||||
|
||||
PangeaMessageEvent get messageEvent => overlayController.pangeaMessageEvent;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final GlobalKey cardKey = MatrixState.pAnyState
|
||||
|
|
@ -273,7 +273,6 @@ class WordZoomWidget extends StatelessWidget {
|
|||
overlayColor: overlayColor,
|
||||
overlayController: overlayController,
|
||||
transformTargetId: transformTargetId,
|
||||
//cardKey: cardKey,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue