Merge pull request #1024 from pangeachat/initializing-overlay

Initializing-overlay
pull/1490/head
ggurdin 1 year ago committed by GitHub
commit f26c7e76e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -62,24 +62,23 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
MessageMode toolbarMode = MessageMode.translation; MessageMode toolbarMode = MessageMode.translation;
PangeaTokenText? _selectedSpan; PangeaTokenText? _selectedSpan;
/// The number of activities that need to be completed before the toolbar is unlocked List<PangeaToken>? tokens;
/// If we don't have any good activities for them, we'll decrease this number bool initialized = false;
static const int neededActivities = 3;
bool get messageInUserL2 =>
pangeaMessageEvent.messageDisplayLangCode ==
MatrixState.pangeaController.languageController.userL2?.langCode;
PangeaMessageEvent get pangeaMessageEvent => widget._pangeaMessageEvent; PangeaMessageEvent get pangeaMessageEvent => widget._pangeaMessageEvent;
final TtsController tts = TtsController(); final TtsController tts = TtsController();
bool isPlayingAudio = false; bool _isPlayingAudio = false;
bool get showToolbarButtons => !widget._pangeaMessageEvent.isAudioMessage; bool get showToolbarButtons => !widget._pangeaMessageEvent.isAudioMessage;
int get activitiesLeftToComplete => messageAnalyticsEntry?.numActivities ?? 0;
bool get isPracticeComplete => activitiesLeftToComplete <= 0;
/// Decides whether an _initialSelectedToken should be used /// Decides whether an _initialSelectedToken should be used
/// for a first practice activity on the word meaning /// for a first practice activity on the word meaning
PangeaToken? get selectedTargetTokenForWordMeaning { PangeaToken? get _selectedTargetTokenForWordMeaning {
// if there is no initial selected token, then we don't need to do anything // if there is no initial selected token, then we don't need to do anything
if (widget._initialSelectedToken == null || messageAnalyticsEntry == null) { if (widget._initialSelectedToken == null || messageAnalyticsEntry == null) {
return null; return null;
@ -100,25 +99,33 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
: null; : null;
} }
List<PangeaToken>? tokens;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_animationController = AnimationController(
vsync: this, _getTokens();
duration: _setupSubscriptions();
const Duration(milliseconds: AppConfig.overlayAnimationDuration),
); if (_selectedTargetTokenForWordMeaning != null) {
messageAnalyticsEntry?.addForWordMeaning(
_selectedTargetTokenForWordMeaning!,
);
}
debugPrint( debugPrint(
"selected token: ${widget._initialSelectedToken?.toJson()} total_xp:${widget._initialSelectedToken?.xp} vocab_construct_xp: ${widget._initialSelectedToken?.vocabConstruct.points} daysSincelastUseInWordMeaning ${widget._initialSelectedToken?.daysSinceLastUseByType(ActivityTypeEnum.wordMeaning)}", "selected token: ${widget._initialSelectedToken?.text.content} total_xp:${widget._initialSelectedToken?.xp} vocab_construct_xp: ${widget._initialSelectedToken?.vocabConstruct.points} daysSincelastUseInWordMeaning ${widget._initialSelectedToken?.daysSinceLastUseByType(ActivityTypeEnum.wordMeaning)}",
); );
debugPrint( debugPrint(
"${widget._initialSelectedToken?.vocabConstruct.uses.map((u) => "${u.useType} ${u.timeStamp}").join(", ")}", "${widget._initialSelectedToken?.vocabConstruct.uses.map((u) => "${u.useType} ${u.timeStamp}").join(", ")}",
); );
}
_getTokens(); void _setupSubscriptions() {
_animationController = AnimationController(
vsync: this,
duration:
const Duration(milliseconds: AppConfig.overlayAnimationDuration),
);
_reactionSubscription = _reactionSubscription =
widget.chatController.room.client.onSync.stream.where( widget.chatController.room.client.onSync.stream.where(
@ -141,13 +148,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
).listen((_) => setState(() {})); ).listen((_) => setState(() {}));
tts.setupTTS(); tts.setupTTS();
if (selectedTargetTokenForWordMeaning != null) {
messageAnalyticsEntry
?.addForWordMeaning(selectedTargetTokenForWordMeaning!);
}
_setInitialToolbarMode();
} }
MessageAnalyticsEntry? get messageAnalyticsEntry => tokens != null MessageAnalyticsEntry? get messageAnalyticsEntry => tokens != null
@ -160,23 +160,47 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
: null; : null;
Future<void> _getTokens() async { Future<void> _getTokens() async {
tokens = pangeaMessageEvent.originalSent?.tokens; try {
final repEvent = pangeaMessageEvent.messageDisplayRepresentation;
if (pangeaMessageEvent.originalSent != null && tokens == null) { if (repEvent != null) {
debugPrint("fetching tokens"); tokens = await repEvent.tokensGlobal(
pangeaMessageEvent.originalSent! pangeaMessageEvent.senderId,
.tokensGlobal( pangeaMessageEvent.originServerTs,
pangeaMessageEvent.senderId, );
pangeaMessageEvent.originServerTs, }
) } catch (e, s) {
.then((tokens) { ErrorHandler.logError(e: e, s: s);
// this isn't currently working because originalSent's _event is null } finally {
this.tokens = tokens; _setInitialToolbarMode();
_setInitialToolbarMode(); initialized = true;
}); if (mounted) setState(() {});
} }
} }
Future<void> _setInitialToolbarMode() async {
if (widget._pangeaMessageEvent.isAudioMessage) {
toolbarMode = MessageMode.speechToText;
return setState(() {});
}
// 1) we're only going to do activities if we have tokens for the message
// 2) if the user selects a span on initialization, then we want to give
// them a practice activity on that word
// 3) if the user has activities left to complete, then we want to give them
if (tokens != null && activitiesLeftToComplete > 0) {
return setState(() => toolbarMode = MessageMode.practiceActivity);
}
// Note: this setting is now hidden so this will always be false
// leaving this here in case we want to bring it back
if (MatrixState.pangeaController.userController.profile.userSettings
.autoPlayMessages) {
return setState(() => toolbarMode = MessageMode.textToSpeech);
}
setState(() => toolbarMode = MessageMode.translation);
}
/// We need to check if the setState call is safe to call immediately /// We need to check if the setState call is safe to call immediately
/// Kept getting the error: setState() or markNeedsBuild() called during build. /// Kept getting the error: setState() or markNeedsBuild() called during build.
/// This is a workaround to prevent that error /// This is a workaround to prevent that error
@ -210,14 +234,11 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
} }
} }
int get activitiesLeftToComplete => messageAnalyticsEntry?.numActivities ?? 0;
bool get isPracticeComplete => activitiesLeftToComplete <= 0;
/// When an activity is completed, we need to update the state /// When an activity is completed, we need to update the state
/// and check if the toolbar should be unlocked /// and check if the toolbar should be unlocked
void onActivityFinish() { void onActivityFinish() {
if (!mounted) return; if (!mounted) return;
clearSelection(); _clearSelection();
setState(() {}); setState(() {});
} }
@ -225,42 +246,21 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
/// interact with the toolbar without completing activities /// interact with the toolbar without completing activities
void exitPracticeFlow() { void exitPracticeFlow() {
messageAnalyticsEntry?.clearActivityQueue(); messageAnalyticsEntry?.clearActivityQueue();
clearSelection(); _clearSelection();
setState(() {}); setState(() {});
} }
Future<void> _setInitialToolbarMode() async { void updateToolbarMode(MessageMode mode) {
if (widget._pangeaMessageEvent.isAudioMessage) {
toolbarMode = MessageMode.speechToText;
return setState(() => toolbarMode = MessageMode.practiceActivity);
}
// 1) we're only going to do activities if we have tokens for the message
// 2) if the user selects a span on initialization, then we want to give
// them a practice activity on that word
// 3) if the user has activities left to complete, then we want to give them
if (tokens != null &&
(selectedTargetTokenForWordMeaning != null ||
activitiesLeftToComplete > 0)) {
return setState(() => toolbarMode = MessageMode.practiceActivity);
}
// Note: this setting is now hidden so this will always be false
// leaving this here in case we want to bring it back
if (MatrixState.pangeaController.userController.profile.userSettings
.autoPlayMessages) {
return setState(() => toolbarMode = MessageMode.textToSpeech);
}
setState(() => toolbarMode = MessageMode.translation);
}
updateToolbarMode(MessageMode mode) {
setState(() { setState(() {
toolbarMode = mode; toolbarMode = mode;
}); });
} }
void _clearSelection() {
_selectedSpan = null;
setState(() {});
}
/// The text that the toolbar should target /// The text that the toolbar should target
/// If there is no selectedSpan, then the whole message is the target /// If there is no selectedSpan, then the whole message is the target
/// If there is a selectedSpan, then the target is the selected text /// If there is a selectedSpan, then the target is the selected text
@ -282,7 +282,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
MessageMode.practiceActivity, MessageMode.practiceActivity,
// MessageMode.textToSpeech // MessageMode.textToSpeech
].contains(toolbarMode) || ].contains(toolbarMode) ||
isPlayingAudio) { _isPlayingAudio) {
return; return;
} }
@ -302,11 +302,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
setState(() {}); setState(() {});
} }
void clearSelection() {
_selectedSpan = null;
setState(() {});
}
void setSelectedSpan(PracticeActivityModel activity) { void setSelectedSpan(PracticeActivityModel activity) {
final RelevantSpanDisplayDetails? span = final RelevantSpanDisplayDetails? span =
activity.content.spanDisplayDetails; activity.content.spanDisplayDetails;
@ -341,7 +336,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
PangeaTokenText? get selectedSpan => _selectedSpan; PangeaTokenText? get selectedSpan => _selectedSpan;
bool get hasReactions { bool get _hasReactions {
final reactionsEvents = widget._pangeaMessageEvent.event.aggregatedEvents( final reactionsEvents = widget._pangeaMessageEvent.event.aggregatedEvents(
widget.chatController.timeline!, widget.chatController.timeline!,
RelationshipTypes.reaction, RelationshipTypes.reaction,
@ -349,35 +344,37 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
return reactionsEvents.where((e) => !e.redacted).isNotEmpty; return reactionsEvents.where((e) => !e.redacted).isNotEmpty;
} }
double get toolbarButtonsHeight => double get _toolbarButtonsHeight =>
showToolbarButtons ? AppConfig.toolbarButtonsHeight : 0; showToolbarButtons ? AppConfig.toolbarButtonsHeight : 0;
double get reactionsHeight => hasReactions ? 28 : 0; double get _reactionsHeight => _hasReactions ? 28 : 0;
double get belowMessageHeight => toolbarButtonsHeight + reactionsHeight; double get _belowMessageHeight => _toolbarButtonsHeight + _reactionsHeight;
void setIsPlayingAudio(bool isPlaying) { void setIsPlayingAudio(bool isPlaying) {
if (mounted) { if (mounted) {
setState(() => isPlayingAudio = isPlaying); setState(() => _isPlayingAudio = isPlaying);
} }
} }
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
if (messageSize == null || messageOffset == null || screenHeight == null) { if (_messageSize == null ||
_messageOffset == null ||
_screenHeight == null) {
return; return;
} }
// position the overlay directly over the underlying message // position the overlay directly over the underlying message
final headerBottomOffset = screenHeight! - headerHeight; final headerBottomOffset = _screenHeight! - _headerHeight;
final footerBottomOffset = footerHeight; final footerBottomOffset = _footerHeight;
final currentBottomOffset = screenHeight! - final currentBottomOffset = _screenHeight! -
messageOffset!.dy - _messageOffset!.dy -
messageSize!.height - _messageSize!.height -
belowMessageHeight; _belowMessageHeight;
final bool hasHeaderOverflow = final bool hasHeaderOverflow =
messageOffset!.dy < (AppConfig.toolbarMaxHeight + headerHeight); _messageOffset!.dy < (AppConfig.toolbarMaxHeight + _headerHeight);
final bool hasFooterOverflow = footerHeight > currentBottomOffset; final bool hasFooterOverflow = _footerHeight > currentBottomOffset;
if (!hasHeaderOverflow && !hasFooterOverflow) return; if (!hasHeaderOverflow && !hasFooterOverflow) return;
@ -388,42 +385,44 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
// if the overlay would have a footer overflow for this message, // if the overlay would have a footer overflow for this message,
// check if shifting the overlay up could cause a header overflow // check if shifting the overlay up could cause a header overflow
final bottomOffsetDifference = footerHeight - currentBottomOffset; final bottomOffsetDifference = _footerHeight - currentBottomOffset;
final newTopOffset = final newTopOffset =
messageOffset!.dy - bottomOffsetDifference - belowMessageHeight; _messageOffset!.dy - bottomOffsetDifference - _belowMessageHeight;
final bool upshiftCausesHeaderOverflow = hasFooterOverflow && final bool upshiftCausesHeaderOverflow = hasFooterOverflow &&
newTopOffset < (headerHeight + AppConfig.toolbarMaxHeight); newTopOffset < (_headerHeight + AppConfig.toolbarMaxHeight);
if (hasHeaderOverflow || upshiftCausesHeaderOverflow) { if (hasHeaderOverflow || upshiftCausesHeaderOverflow) {
animationEndOffset = midpoint - messageSize!.height - belowMessageHeight; animationEndOffset =
final totalTopOffset = midpoint - _messageSize!.height - _belowMessageHeight;
animationEndOffset + messageSize!.height + AppConfig.toolbarMaxHeight; final totalTopOffset = animationEndOffset +
final remainingSpace = screenHeight! - totalTopOffset; _messageSize!.height +
if (remainingSpace < headerHeight) { AppConfig.toolbarMaxHeight;
final remainingSpace = _screenHeight! - totalTopOffset;
if (remainingSpace < _headerHeight) {
// the overlay could run over the header, so it needs to be shifted down // the overlay could run over the header, so it needs to be shifted down
animationEndOffset -= (headerHeight - remainingSpace); animationEndOffset -= (_headerHeight - remainingSpace);
} }
scrollOffset = animationEndOffset - currentBottomOffset; scrollOffset = animationEndOffset - currentBottomOffset;
} else if (hasFooterOverflow) { } else if (hasFooterOverflow) {
scrollOffset = footerHeight - currentBottomOffset; scrollOffset = _footerHeight - currentBottomOffset;
animationEndOffset = footerHeight; animationEndOffset = _footerHeight;
} }
// If, after ajusting the overlay position, the message still overflows the footer, // If, after ajusting the overlay position, the message still overflows the footer,
// update the message height to fit the screen. The message is scrollable, so // update the message height to fit the screen. The message is scrollable, so
// this will make the both the toolbar box and the toolbar buttons visible. // this will make the both the toolbar box and the toolbar buttons visible.
if (animationEndOffset < footerHeight + belowMessageHeight) { if (animationEndOffset < _footerHeight + _belowMessageHeight) {
final double remainingSpace = screenHeight! - final double remainingSpace = _screenHeight! -
AppConfig.toolbarMaxHeight - AppConfig.toolbarMaxHeight -
headerHeight - _headerHeight -
footerHeight - _footerHeight -
belowMessageHeight; _belowMessageHeight;
if (remainingSpace < messageSize!.height) { if (remainingSpace < _messageSize!.height) {
adjustedMessageHeight = remainingSpace; _adjustedMessageHeight = remainingSpace;
} }
animationEndOffset = footerHeight; animationEndOffset = _footerHeight;
} }
_overlayPositionAnimation = Tween<double>( _overlayPositionAnimation = Tween<double>(
@ -453,7 +452,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
super.dispose(); super.dispose();
} }
RenderBox? get messageRenderBox { RenderBox? get _messageRenderBox {
try { try {
return MatrixState.pAnyState.getRenderBox( return MatrixState.pAnyState.getRenderBox(
widget._event.eventId, widget._event.eventId,
@ -464,39 +463,39 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
} }
} }
Size? get messageSize { Size? get _messageSize {
if (messageRenderBox == null || !messageRenderBox!.hasSize) { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
return null; return null;
} }
try { try {
return messageRenderBox?.size; return _messageRenderBox?.size;
} catch (e, s) { } catch (e, s) {
ErrorHandler.logError(e: "Error getting message size: $e", s: s); ErrorHandler.logError(e: "Error getting message size: $e", s: s);
return null; return null;
} }
} }
Offset? get messageOffset { Offset? get _messageOffset {
if (messageRenderBox == null || !messageRenderBox!.hasSize) { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
return null; return null;
} }
try { try {
return messageRenderBox?.localToGlobal(Offset.zero); return _messageRenderBox?.localToGlobal(Offset.zero);
} catch (e, s) { } catch (e, s) {
ErrorHandler.logError(e: "Error getting message offset: $e", s: s); ErrorHandler.logError(e: "Error getting message offset: $e", s: s);
return null; return null;
} }
} }
double? adjustedMessageHeight; double? _adjustedMessageHeight;
// height of the reply/forward bar + the reaction picker + contextual padding // height of the reply/forward bar + the reaction picker + contextual padding
double get footerHeight => double get _footerHeight =>
48 + 56 + (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0); 48 + 56 + (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0);
MediaQueryData? get mediaQuery { MediaQueryData? get _mediaQuery {
try { try {
return MediaQuery.of(context); return MediaQuery.of(context);
} catch (e, s) { } catch (e, s) {
@ -505,17 +504,17 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
} }
} }
double get headerHeight => double get _headerHeight =>
(Theme.of(context).appBarTheme.toolbarHeight ?? 56) + (Theme.of(context).appBarTheme.toolbarHeight ?? 56) +
(mediaQuery?.padding.top ?? 0); (_mediaQuery?.padding.top ?? 0);
double? get screenHeight => mediaQuery?.size.height; double? get _screenHeight => _mediaQuery?.size.height;
double? get screenWidth => mediaQuery?.size.width; double? get _screenWidth => _mediaQuery?.size.width;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (messageSize == null) return const SizedBox.shrink(); if (_messageSize == null) return const SizedBox.shrink();
final bool showDetails = (Matrix.of(context) final bool showDetails = (Matrix.of(context)
.store .store
@ -530,8 +529,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
const totalMaxWidth = (FluffyThemes.columnWidth * 2.5) - messageMargin; const totalMaxWidth = (FluffyThemes.columnWidth * 2.5) - messageMargin;
double? maxWidth; double? maxWidth;
if (screenWidth != null) { if (_screenWidth != null) {
final chatViewWidth = screenWidth! - final chatViewWidth = _screenWidth! -
(FluffyThemes.isColumnMode(context) (FluffyThemes.isColumnMode(context)
? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth) ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth)
: 0); : 0);
@ -558,7 +557,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
SizedBox( SizedBox(
height: adjustedMessageHeight, height: _adjustedMessageHeight,
child: OverlayMessage( child: OverlayMessage(
pangeaMessageEvent, pangeaMessageEvent,
immersionMode: immersionMode:
@ -568,15 +567,15 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
nextEvent: widget._nextEvent, nextEvent: widget._nextEvent,
prevEvent: widget._prevEvent, prevEvent: widget._prevEvent,
timeline: widget.chatController.timeline!, timeline: widget.chatController.timeline!,
messageWidth: messageSize!.width, messageWidth: _messageSize!.width,
messageHeight: messageSize!.height, messageHeight: _messageSize!.height,
), ),
), ),
if (hasReactions) if (_hasReactions)
Padding( Padding(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: SizedBox( child: SizedBox(
height: reactionsHeight - 8, height: _reactionsHeight - 8,
child: MessageReactions( child: MessageReactions(
widget._pangeaMessageEvent.event, widget._pangeaMessageEvent.event,
widget.chatController.timeline!, widget.chatController.timeline!,
@ -597,30 +596,32 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
: 0; : 0;
final double? leftPadding = final double? leftPadding =
(widget._pangeaMessageEvent.ownMessage || messageOffset == null) (widget._pangeaMessageEvent.ownMessage || _messageOffset == null)
? null ? null
: messageOffset!.dx - horizontalPadding - columnOffset; : _messageOffset!.dx - horizontalPadding - columnOffset;
final double? rightPadding = (widget._pangeaMessageEvent.ownMessage && final double? rightPadding = (widget._pangeaMessageEvent.ownMessage &&
screenWidth != null && _screenWidth != null &&
messageOffset != null && _messageOffset != null &&
messageSize != null) _messageSize != null)
? screenWidth! - ? _screenWidth! -
messageOffset!.dx - _messageOffset!.dx -
messageSize!.width - _messageSize!.width -
horizontalPadding horizontalPadding
: null; : null;
final positionedOverlayMessage = (_overlayPositionAnimation == null) final positionedOverlayMessage = (_overlayPositionAnimation == null)
? (screenHeight == null || messageSize == null || messageOffset == null) ? (_screenHeight == null ||
_messageSize == null ||
_messageOffset == null)
? const SizedBox.shrink() ? const SizedBox.shrink()
: Positioned( : Positioned(
left: leftPadding, left: leftPadding,
right: rightPadding, right: rightPadding,
bottom: screenHeight! - bottom: _screenHeight! -
messageOffset!.dy - _messageOffset!.dy -
messageSize!.height - _messageSize!.height -
belowMessageHeight, _belowMessageHeight,
child: overlayMessage, child: overlayMessage,
) )
: AnimatedBuilder( : AnimatedBuilder(
@ -672,25 +673,3 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
); );
} }
} }
class MessagePadding extends StatelessWidget {
const MessagePadding({
super.key,
required this.child,
required this.pangeaMessageEvent,
});
final Widget child;
final PangeaMessageEvent pangeaMessageEvent;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: pangeaMessageEvent.ownMessage ? 0 : Avatar.defaultSize + 16,
right: pangeaMessageEvent.ownMessage ? 8 : 0,
),
child: child,
);
}
}

@ -45,6 +45,10 @@ class MessageToolbar extends StatelessWidget {
); );
} }
if (!overLayController.initialized) {
return const ToolbarContentLoadingIndicator();
}
// Check if the message is in the user's second language // Check if the message is in the user's second language
final bool messageInUserL2 = pangeaMessageEvent.messageDisplayLangCode == final bool messageInUserL2 = pangeaMessageEvent.messageDisplayLangCode ==
MatrixState.pangeaController.languageController.userL2?.langCode; MatrixState.pangeaController.languageController.userL2?.langCode;

@ -144,7 +144,7 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
MessageActivityRequest( MessageActivityRequest(
userL1: pangeaController.languageController.userL1!.langCode, userL1: pangeaController.languageController.userL1!.langCode,
userL2: pangeaController.languageController.userL2!.langCode, userL2: pangeaController.languageController.userL2!.langCode,
messageText: widget.pangeaMessageEvent.originalSent!.text, messageText: widget.pangeaMessageEvent.messageDisplayText,
messageTokens: widget.overlayController.tokens!, messageTokens: widget.overlayController.tokens!,
activityQualityFeedback: activityFeedback, activityQualityFeedback: activityFeedback,
targetTokens: nextActivitySpecs.tokens, targetTokens: nextActivitySpecs.tokens,

Loading…
Cancel
Save