refactor: separate token and message reading assistance modes (#2416)
* refactor: separate token and message reading assistance modes * chore: apply same token styling to HTML formatted messages * chore: don't wait for lemma responses before showing reading assistance content
This commit is contained in:
parent
184442a5ff
commit
b7a6ee6fe2
17 changed files with 766 additions and 453 deletions
|
|
@ -27,10 +27,10 @@ abstract class AppConfig {
|
|||
static const double toolbarMinHeight = 200.0;
|
||||
static const double toolbarMinWidth = 350.0;
|
||||
static const double defaultHeaderHeight = 56.0;
|
||||
static const double readingAssistanceInputBarHeight = 170;
|
||||
static const double toolbarButtonsHeight = 50.0;
|
||||
static const double toolbarSpacing = 8.0;
|
||||
static const double toolbarIconSize = 24.0;
|
||||
static const double readingAssistanceInputBarHeight = 140.0;
|
||||
|
||||
static TextStyle messageTextStyle(
|
||||
Event? event,
|
||||
|
|
|
|||
|
|
@ -10,10 +10,16 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/any_state_holder.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/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/message_token_button.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.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/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import '../../../utils/url_launcher.dart';
|
||||
|
||||
|
|
@ -25,12 +31,14 @@ class HtmlMessage extends StatelessWidget {
|
|||
final TextStyle linkStyle;
|
||||
final void Function(LinkableElement) onOpen;
|
||||
// #Pangea
|
||||
final bool isOverlay;
|
||||
final MessageOverlayController? overlayController;
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
final ChatController controller;
|
||||
final Event event;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
final bool isTransitionAnimation;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
final bool Function(PangeaToken)? isSelected;
|
||||
final void Function(PangeaToken)? onClick;
|
||||
|
|
@ -45,7 +53,7 @@ class HtmlMessage extends StatelessWidget {
|
|||
this.textColor = Colors.black,
|
||||
required this.onOpen,
|
||||
// #Pangea
|
||||
required this.isOverlay,
|
||||
this.overlayController,
|
||||
required this.event,
|
||||
this.pangeaMessageEvent,
|
||||
required this.controller,
|
||||
|
|
@ -53,6 +61,8 @@ class HtmlMessage extends StatelessWidget {
|
|||
this.prevEvent,
|
||||
this.isSelected,
|
||||
this.onClick,
|
||||
this.isTransitionAnimation = false,
|
||||
this.readingAssistanceMode,
|
||||
// Pangea#
|
||||
});
|
||||
|
||||
|
|
@ -262,53 +272,91 @@ class HtmlMessage extends StatelessWidget {
|
|||
? isSelected!.call(token)
|
||||
: false;
|
||||
|
||||
final shouldDo = pangeaMessageEvent?.shouldDoActivity(
|
||||
token: token,
|
||||
a: ActivityTypeEnum.wordMeaning,
|
||||
feature: null,
|
||||
tag: null,
|
||||
) ??
|
||||
false;
|
||||
final renderer = TokenRenderingUtil(
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
existingStyle: AppConfig.messageTextStyle(
|
||||
pangeaMessageEvent!.event,
|
||||
textColor,
|
||||
),
|
||||
overlayController: overlayController,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
);
|
||||
|
||||
// @ggurdin: probably changing this, not sure when it shows up
|
||||
final didMeaningActivity = token?.didActivitySuccessfully(
|
||||
ActivityTypeEnum.wordMeaning,
|
||||
) ??
|
||||
true;
|
||||
|
||||
Color backgroundColor = Colors.transparent;
|
||||
if (selected) {
|
||||
backgroundColor = Theme.of(context).colorScheme.primary.withAlpha(80);
|
||||
} else if (isSelected != null && shouldDo) {
|
||||
backgroundColor = !didMeaningActivity
|
||||
? AppConfig.success.withAlpha(60)
|
||||
: AppConfig.gold.withAlpha(60);
|
||||
}
|
||||
final tokenWidth = renderer.tokenTextWidthForContainer(
|
||||
context,
|
||||
node.innerHtml,
|
||||
);
|
||||
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null && token != null
|
||||
? () => onClick?.call(token)
|
||||
child: CompositedTransformTarget(
|
||||
link: token != null && renderer.assignTokenKey
|
||||
? MatrixState.pAnyState
|
||||
.layerLinkAndKey(token.text.uniqueKey)
|
||||
.link
|
||||
: LayerLinkAndKey(token.hashCode.toString()).link,
|
||||
child: Column(
|
||||
key: token != null && renderer.assignTokenKey
|
||||
? MatrixState.pAnyState
|
||||
.layerLinkAndKey(token.text.uniqueKey)
|
||||
.key
|
||||
: null,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
LinkifySpan(
|
||||
text: node.innerHtml,
|
||||
style: AppConfig.messageTextStyle(
|
||||
pangeaMessageEvent!.event,
|
||||
textColor,
|
||||
).merge(TextStyle(backgroundColor: backgroundColor)),
|
||||
linkStyle: linkStyle,
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
children: [
|
||||
if (renderer.showCenterStyling && token != null)
|
||||
MessageTokenButton(
|
||||
token: token,
|
||||
overlayController: overlayController,
|
||||
textStyle: renderer.style(
|
||||
context,
|
||||
color: renderer.backgroundColor(
|
||||
context,
|
||||
selected,
|
||||
),
|
||||
),
|
||||
],
|
||||
width: tokenWidth,
|
||||
animate: isTransitionAnimation,
|
||||
practiceTarget:
|
||||
overlayController?.toolbarMode.associatedActivityType !=
|
||||
null
|
||||
? overlayController?.practiceSelection
|
||||
?.activities(
|
||||
overlayController!
|
||||
.toolbarMode.associatedActivityType!,
|
||||
)
|
||||
.firstWhereOrNull(
|
||||
(a) => a.tokens.contains(token),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null && token != null
|
||||
? () => onClick?.call(token)
|
||||
: null,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
LinkifySpan(
|
||||
text: node.innerHtml,
|
||||
style: renderer.style(
|
||||
context,
|
||||
color: renderer.backgroundColor(
|
||||
context,
|
||||
selected,
|
||||
),
|
||||
),
|
||||
linkStyle: linkStyle,
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -611,7 +659,7 @@ class HtmlMessage extends StatelessWidget {
|
|||
return SelectionArea(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isOverlay) {
|
||||
if (overlayController == null) {
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent?.event ?? event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar
|
|||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_token_text.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_toolbar_selection_area.dart';
|
||||
|
|
@ -44,6 +45,7 @@ class MessageContent extends StatelessWidget {
|
|||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
final bool isTransitionAnimation;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
// Pangea#
|
||||
final Timeline timeline;
|
||||
|
||||
|
|
@ -61,6 +63,7 @@ class MessageContent extends StatelessWidget {
|
|||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
this.isTransitionAnimation = false,
|
||||
this.readingAssistanceMode,
|
||||
// Pangea#
|
||||
required this.linkColor,
|
||||
required this.borderRadius,
|
||||
|
|
@ -256,13 +259,15 @@ class MessageContent extends StatelessWidget {
|
|||
room: event.room,
|
||||
// #Pangea
|
||||
event: event,
|
||||
isOverlay: overlayController != null,
|
||||
overlayController: overlayController,
|
||||
controller: controller,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
isSelected: overlayController != null ? isSelected : null,
|
||||
onClick: event.isActivityMessage ? null : onClick,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
// Pangea#
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
linkStyle: TextStyle(
|
||||
|
|
@ -394,6 +399,7 @@ class MessageContent extends StatelessWidget {
|
|||
true,
|
||||
overlayController: overlayController,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,11 @@ class ReactionsPicker extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
decoration: const BoxDecoration(
|
||||
// #Pangea
|
||||
// color: theme.colorScheme.onInverseSurface,
|
||||
// Pangea#
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ class PangeaAnyState {
|
|||
|
||||
RenderBox? getRenderBox(String key) =>
|
||||
layerLinkAndKey(key).key.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
bool isOverlayOpen(String overlayKey) {
|
||||
return entries.any((element) => element.key == overlayKey);
|
||||
}
|
||||
}
|
||||
|
||||
class LayerLinkAndKey {
|
||||
|
|
|
|||
|
|
@ -95,10 +95,12 @@ class _CustomizedSvgState extends State<CustomizedSvg> {
|
|||
_hasError = modifiedSvg == null;
|
||||
});
|
||||
} catch (_) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_hasError = true;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_hasError = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ class TokenPosition {
|
|||
/// End index of the token in the message
|
||||
final int tokenEnd;
|
||||
|
||||
final bool selected;
|
||||
final bool hideContent;
|
||||
final PangeaToken? token;
|
||||
final bool isHighlighted;
|
||||
final bool selected;
|
||||
|
||||
const TokenPosition({
|
||||
required this.start,
|
||||
|
|
@ -36,6 +36,8 @@ class TokenPosition {
|
|||
}
|
||||
|
||||
class MessageTextUtil {
|
||||
static final Map<String, List<TokenPosition>> _tokenPositionsCache = {};
|
||||
|
||||
static List<TokenPosition>? getTokenPositions(
|
||||
PangeaMessageEvent pangeaMessageEvent, {
|
||||
PracticeSelection? messageAnalyticsEntry,
|
||||
|
|
@ -47,6 +49,27 @@ class MessageTextUtil {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (_tokenPositionsCache.containsKey(pangeaMessageEvent.eventId)) {
|
||||
return _tokenPositionsCache[pangeaMessageEvent.eventId]!
|
||||
.map(
|
||||
(t) => TokenPosition(
|
||||
start: t.start,
|
||||
end: t.end,
|
||||
tokenStart: t.tokenStart,
|
||||
tokenEnd: t.tokenEnd,
|
||||
hideContent: t.hideContent,
|
||||
selected: t.token != null
|
||||
? isSelected?.call(t.token!) ?? false
|
||||
: false,
|
||||
isHighlighted: t.token != null
|
||||
? isHighlighted?.call(t.token!) ?? false
|
||||
: false,
|
||||
token: t.token,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Convert the entire message into a list of characters
|
||||
final Characters messageCharacters =
|
||||
pangeaMessageEvent.messageDisplayText.characters;
|
||||
|
|
@ -131,6 +154,7 @@ class MessageTextUtil {
|
|||
continue;
|
||||
}
|
||||
|
||||
_tokenPositionsCache[pangeaMessageEvent.eventId] = tokenPositions;
|
||||
return tokenPositions;
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
|
|
|
|||
|
|
@ -41,6 +41,18 @@ enum MorphFeaturesEnum {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
class MorphFeatureUtil {
|
||||
static final Map<String, MorphFeaturesEnum> _morphFeatureCache = {};
|
||||
|
||||
static void set(String key, MorphFeaturesEnum value) {
|
||||
_morphFeatureCache[key] = value;
|
||||
}
|
||||
|
||||
static MorphFeaturesEnum? get(String key) {
|
||||
return _morphFeatureCache[key];
|
||||
}
|
||||
}
|
||||
|
||||
extension MorphFeaturesEnumExtension on MorphFeaturesEnum {
|
||||
/// Convert enum to string
|
||||
String toShortString() {
|
||||
|
|
@ -49,6 +61,12 @@ extension MorphFeaturesEnumExtension on MorphFeaturesEnum {
|
|||
|
||||
/// Convert string to enum
|
||||
static MorphFeaturesEnum fromString(String category) {
|
||||
// Repeated regex operations are causing performance issues,
|
||||
// so we cache the results in a static map
|
||||
if (MorphFeatureUtil.get(category) != null) {
|
||||
return MorphFeatureUtil.get(category)!;
|
||||
}
|
||||
|
||||
final morph = MorphFeaturesEnum.values.firstWhereOrNull(
|
||||
(e) =>
|
||||
e.toShortString() ==
|
||||
|
|
@ -62,6 +80,8 @@ extension MorphFeaturesEnumExtension on MorphFeaturesEnum {
|
|||
);
|
||||
return MorphFeaturesEnum.Unknown;
|
||||
}
|
||||
|
||||
MorphFeatureUtil.set(category, morph);
|
||||
return morph;
|
||||
}
|
||||
|
||||
|
|
|
|||
10
lib/pangea/toolbar/enums/reading_assistance_mode_enum.dart
Normal file
10
lib/pangea/toolbar/enums/reading_assistance_mode_enum.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
enum ReadingAssistanceMode {
|
||||
/// Overlay message is directly over the original message
|
||||
tokenMode,
|
||||
|
||||
/// Overlay message is centered and larger than the original message
|
||||
messageMode,
|
||||
|
||||
/// Overlay message is moving to the center of the screen
|
||||
transitionMode,
|
||||
}
|
||||
|
|
@ -33,8 +33,12 @@ class OverlayFooter extends StatelessWidget {
|
|||
// constraints: const BoxConstraints(
|
||||
// maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
// ),
|
||||
height: AppConfig.readingAssistanceInputBarHeight +
|
||||
AppConfig.toolbarButtonsHeight +
|
||||
20.0,
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (showToolbarButtons)
|
||||
ToolbarButtonRow(overlayController: overlayController),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/reactions_picker.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';
|
||||
|
|
@ -48,7 +49,7 @@ class ReadingAssistanceInputBar extends StatelessWidget {
|
|||
case MessageMode.noneSelected:
|
||||
case MessageMode.messageMeaning:
|
||||
//TODO: show all emojis for the lemmas and allow sending normal reactions
|
||||
break;
|
||||
return ReactionsPicker(controller);
|
||||
|
||||
case MessageMode.messageTranslation:
|
||||
if (overlayController.isTranslationUnlocked) {
|
||||
|
|
@ -97,10 +98,6 @@ class ReadingAssistanceInputBar extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
if (content == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: minContentHeight,
|
||||
|
|
@ -114,8 +111,7 @@ class ReadingAssistanceInputBar extends StatelessWidget {
|
|||
return Expanded(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: (MediaQuery.of(context).size.height / 2) -
|
||||
AppConfig.toolbarButtonsHeight,
|
||||
maxHeight: AppConfig.readingAssistanceInputBarHeight,
|
||||
maxWidth: overlayController.maxWidth,
|
||||
),
|
||||
child: AnimatedSize(
|
||||
|
|
|
|||
93
lib/pangea/toolbar/utils/token_rendering_util.dart
Normal file
93
lib/pangea/toolbar/utils/token_rendering_util.dart
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
||||
class TokenRenderingUtil {
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
final MessageOverlayController? overlayController;
|
||||
final bool isTransitionAnimation;
|
||||
final TextStyle existingStyle;
|
||||
|
||||
static final Map<String, double> _tokensWidthCache = {};
|
||||
|
||||
TokenRenderingUtil({
|
||||
required this.pangeaMessageEvent,
|
||||
required this.readingAssistanceMode,
|
||||
required this.existingStyle,
|
||||
this.overlayController,
|
||||
this.isTransitionAnimation = false,
|
||||
});
|
||||
|
||||
bool get showCenterStyling {
|
||||
if (overlayController == null) return false;
|
||||
if (!isTransitionAnimation) return true;
|
||||
return readingAssistanceMode == ReadingAssistanceMode.transitionMode;
|
||||
}
|
||||
|
||||
double? _fontSize(BuildContext context) => showCenterStyling
|
||||
? overlayController != null && overlayController!.maxWidth > 600
|
||||
? Theme.of(context).textTheme.titleLarge?.fontSize
|
||||
: Theme.of(context).textTheme.bodyLarge?.fontSize
|
||||
: null;
|
||||
|
||||
TextStyle style(
|
||||
BuildContext context, {
|
||||
Color? color,
|
||||
}) =>
|
||||
existingStyle.copyWith(
|
||||
fontSize: _fontSize(context),
|
||||
decoration: TextDecoration.underline,
|
||||
decorationThickness: 4,
|
||||
decorationColor: color ?? Colors.white.withAlpha(0),
|
||||
);
|
||||
|
||||
double tokenTextWidthForContainer(BuildContext context, String text) {
|
||||
final tokenSizeKey = "$text-${_fontSize(context)}";
|
||||
if (_tokensWidthCache.containsKey(tokenSizeKey)) {
|
||||
return _tokensWidthCache[tokenSizeKey]!;
|
||||
}
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: style(context),
|
||||
),
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
final width = textPainter.width;
|
||||
textPainter.dispose();
|
||||
|
||||
_tokensWidthCache[tokenSizeKey] = width;
|
||||
return width;
|
||||
}
|
||||
|
||||
// Only one token on the screen can have the token's unique key at a time.
|
||||
// When readingAssistanceMode is not null, there are two messages - the centered message and the transition message.
|
||||
// When in word mode, the key goes to the transition message.
|
||||
// If actively transitioning, neither gets the keys.
|
||||
// If in message mode, the key goes to the centered message (isTransitionAnimation == false).
|
||||
bool get assignTokenKey {
|
||||
if (readingAssistanceMode == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (readingAssistanceMode!) {
|
||||
case ReadingAssistanceMode.tokenMode:
|
||||
return isTransitionAnimation;
|
||||
case ReadingAssistanceMode.transitionMode:
|
||||
return false;
|
||||
case ReadingAssistanceMode.messageMode:
|
||||
return !isTransitionAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
Color backgroundColor(BuildContext context, bool selected) {
|
||||
return selected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white.withAlpha(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -142,16 +142,22 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
.messageDisplayRepresentation!.tokensToSave
|
||||
.map((e) => e.vocabConstructID)
|
||||
.toList();
|
||||
|
||||
final List<Future<LemmaInfoResponse>> lemmaInfoFutures =
|
||||
messageVocabConstructIds
|
||||
.map((token) => token.getLemmaInfo())
|
||||
.toList();
|
||||
final List<LemmaInfoResponse> lemmaInfos =
|
||||
await Future.wait(lemmaInfoFutures);
|
||||
messageLemmaInfos = Map.fromIterables(
|
||||
messageVocabConstructIds,
|
||||
lemmaInfos,
|
||||
);
|
||||
|
||||
Future.wait(lemmaInfoFutures).then((resp) {
|
||||
if (mounted) {
|
||||
setState(
|
||||
() => messageLemmaInfos = Map.fromIterables(
|
||||
messageVocabConstructIds,
|
||||
resp,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -285,28 +291,38 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
_selectedSpan = selectedSpan;
|
||||
}
|
||||
|
||||
if (selectedToken != null) {
|
||||
final entry = ReadingAssistanceContent(
|
||||
key: wordZoomKey,
|
||||
pangeaMessageEvent: pangeaMessageEvent!,
|
||||
overlayController: this,
|
||||
);
|
||||
if (mounted) {
|
||||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: entry,
|
||||
transformTargetId: selectedToken!.text.uniqueKey,
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
addBorder: false,
|
||||
overlayKey: selectedToken!.text.uniqueKey,
|
||||
maxHeight: AppConfig.toolbarMaxHeight,
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
Future.delayed(const Duration(milliseconds: 10), () {
|
||||
_showReadingAssistanceContent();
|
||||
});
|
||||
}
|
||||
|
||||
void _showReadingAssistanceContent() {
|
||||
if (selectedToken == null) return;
|
||||
if (MatrixState.pAnyState.isOverlayOpen(
|
||||
selectedToken!.text.uniqueKey,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mounted) setState(() {});
|
||||
final entry = ReadingAssistanceContent(
|
||||
key: wordZoomKey,
|
||||
pangeaMessageEvent: pangeaMessageEvent!,
|
||||
overlayController: this,
|
||||
);
|
||||
if (mounted) {
|
||||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: entry,
|
||||
transformTargetId: selectedToken!.text.uniqueKey,
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
addBorder: false,
|
||||
overlayKey: selectedToken!.text.uniqueKey,
|
||||
maxHeight: AppConfig.toolbarMaxHeight,
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void updateToolbarMode(MessageMode mode) => setState(() {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
|||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/overlay_footer.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
|
@ -53,31 +54,36 @@ class MessageSelectionPositioner extends StatefulWidget {
|
|||
class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
Animation<Offset>? _overlayOffsetAnimation;
|
||||
Animation<Size>? _messageSizeAnimation;
|
||||
|
||||
StreamSubscription? _reactionSubscription;
|
||||
|
||||
Offset? _centeredMessageOffset;
|
||||
Size? _centeredMessageSize;
|
||||
|
||||
Size? _tooltipSize;
|
||||
Size? _inputBarSize;
|
||||
|
||||
final Completer _centeredMessageCompleter = Completer();
|
||||
final Completer _tooltipCompleter = Completer();
|
||||
|
||||
bool _finishedAnimation = false;
|
||||
MessageMode _currentMode = MessageMode.noneSelected;
|
||||
ReadingAssistanceMode? _readingAssistanceMode;
|
||||
|
||||
Animation<Offset>? _overlayOffsetAnimation;
|
||||
Animation<Size>? _messageSizeAnimation;
|
||||
Offset? _currentOffset;
|
||||
|
||||
StreamSubscription? _reactionSubscription;
|
||||
|
||||
final _animationDuration = const Duration(
|
||||
milliseconds: AppConfig.overlayAnimationDuration,
|
||||
// seconds: 5,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentMode = widget.overlayController.toolbarMode;
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
milliseconds: AppConfig.overlayAnimationDuration,
|
||||
// seconds: 5,
|
||||
),
|
||||
duration: _animationDuration,
|
||||
);
|
||||
|
||||
_reactionSubscription =
|
||||
|
|
@ -100,26 +106,37 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
},
|
||||
).listen((_) => setState(() {}));
|
||||
|
||||
Future.wait([
|
||||
_centeredMessageCompleter.future,
|
||||
if (showToolbarButtons) _tooltipCompleter.future,
|
||||
]).then((_) => _startAnimation());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _centeredMessageCompleter.future;
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_currentOffset = Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_originalMessageBottomOffset - _reactionsHeight,
|
||||
);
|
||||
});
|
||||
|
||||
_setReadingAssistanceMode(
|
||||
widget.initialSelectedToken == null
|
||||
? ReadingAssistanceMode.messageMode
|
||||
: ReadingAssistanceMode.tokenMode,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MessageSelectionPositioner oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.overlayController.toolbarMode !=
|
||||
widget.overlayController.toolbarMode) {
|
||||
setState(() {});
|
||||
final mode = widget.overlayController.toolbarMode;
|
||||
if (mode != _currentMode) {
|
||||
if (_currentMode == MessageMode.noneSelected) {
|
||||
_setReadingAssistanceMode(ReadingAssistanceMode.messageMode);
|
||||
}
|
||||
setState(() => _currentMode = mode);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
|
|
@ -154,46 +171,68 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
}
|
||||
|
||||
void _setInputBarSize(RenderBox renderBox) {
|
||||
setState(() => _inputBarSize = renderBox.size);
|
||||
}
|
||||
|
||||
void _startAnimation() {
|
||||
if (_mediaQuery == null) {
|
||||
Future<void> _setReadingAssistanceMode(ReadingAssistanceMode mode) async {
|
||||
if (mode == _readingAssistanceMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_messageBottomOffset - _reactionsHeight,
|
||||
),
|
||||
// For own messages, dx is the right offset. For other's messages, dx is the left offset.
|
||||
end: _adjustedCenteredMessageOffset,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
);
|
||||
await _centeredMessageCompleter.future;
|
||||
|
||||
_messageSizeAnimation = Tween<Size>(
|
||||
begin: Size(
|
||||
_messageSize.width,
|
||||
_originalMessageHeight,
|
||||
),
|
||||
end: _adjustedCenteredMessageSize,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
);
|
||||
if (mode == ReadingAssistanceMode.messageMode) {
|
||||
setState(
|
||||
() => _readingAssistanceMode = ReadingAssistanceMode.transitionMode,
|
||||
);
|
||||
} else if (mode == ReadingAssistanceMode.tokenMode) {
|
||||
setState(
|
||||
() => _readingAssistanceMode = ReadingAssistanceMode.tokenMode,
|
||||
);
|
||||
}
|
||||
|
||||
_animationController.forward().then((_) {
|
||||
_finishedAnimation = true;
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
if (mode == ReadingAssistanceMode.tokenMode) {
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: _currentOffset,
|
||||
end: _adjustedOriginalMessageOffset,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
)..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() => _currentOffset = _overlayOffsetAnimation?.value);
|
||||
}
|
||||
});
|
||||
} else if (mode == ReadingAssistanceMode.messageMode) {
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: _currentOffset,
|
||||
end: _centeredMessageOffset!,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
)..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() => _currentOffset = _overlayOffsetAnimation?.value);
|
||||
}
|
||||
});
|
||||
|
||||
_messageSizeAnimation = Tween<Size>(
|
||||
begin: Size(
|
||||
_originalMessageSize.width,
|
||||
_originalMessageSize.height,
|
||||
),
|
||||
end: _adjustedCenteredMessageSize,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await _animationController.forward(from: 0);
|
||||
if (mounted) setState(() => _readingAssistanceMode = mode);
|
||||
}
|
||||
|
||||
T _runWithLogging<T>(
|
||||
|
|
@ -215,6 +254,10 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
}
|
||||
|
||||
final double _inputBarSize = AppConfig.readingAssistanceInputBarHeight +
|
||||
AppConfig.toolbarButtonsHeight +
|
||||
20.0;
|
||||
|
||||
bool get _showDetails =>
|
||||
(Matrix.of(context).store.getBool(SettingKeys.displayChatDetailsColumn) ??
|
||||
false) &&
|
||||
|
|
@ -233,70 +276,12 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth)
|
||||
: 0;
|
||||
|
||||
// message size
|
||||
|
||||
RenderBox? get _messageRenderBox => _runWithLogging<RenderBox?>(
|
||||
() => MatrixState.pAnyState.getRenderBox(
|
||||
widget.event.eventId,
|
||||
),
|
||||
"Error getting message render box",
|
||||
null,
|
||||
);
|
||||
|
||||
Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100);
|
||||
Size get _messageSize {
|
||||
if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
|
||||
return _defaultMessageSize;
|
||||
}
|
||||
|
||||
return _runWithLogging(
|
||||
() => _messageRenderBox?.size,
|
||||
"Error getting message size",
|
||||
_defaultMessageSize,
|
||||
);
|
||||
}
|
||||
|
||||
double get _originalMessageHeight => _messageSize.height;
|
||||
|
||||
double? get _centerSpace {
|
||||
/// Available vertical space not taken up by the header and footer
|
||||
double? get _verticalSpace {
|
||||
if (_mediaQuery == null) return null;
|
||||
return _mediaQuery!.size.height - _headerHeight - _footerHeight;
|
||||
}
|
||||
|
||||
bool get _centeredMessageHasOverflow {
|
||||
if (_centerSpace == null ||
|
||||
_centeredMessageSize == null ||
|
||||
_centeredMessageOffset == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final finalMessageHeight = _centeredMessageSize!.height + _reactionsHeight;
|
||||
return finalMessageHeight > _centerSpace!;
|
||||
}
|
||||
|
||||
Size? get _adjustedCenteredMessageSize {
|
||||
if (_centeredMessageHasOverflow) {
|
||||
return Size(
|
||||
_centeredMessageSize!.width,
|
||||
_centerSpace! - (AppConfig.toolbarSpacing * 2),
|
||||
);
|
||||
}
|
||||
return _centeredMessageSize;
|
||||
}
|
||||
|
||||
Offset? get _adjustedCenteredMessageOffset {
|
||||
if (_centeredMessageHasOverflow) {
|
||||
return Offset(
|
||||
_centeredMessageOffset!.dx,
|
||||
_footerHeight + AppConfig.toolbarSpacing,
|
||||
);
|
||||
}
|
||||
return _centeredMessageOffset;
|
||||
}
|
||||
|
||||
//TODO: figure out where the 16 and 8 come from and use references instead of hard-coded values
|
||||
static const _messageDefaultLeftMargin = Avatar.defaultSize + 16 + 8;
|
||||
|
||||
double get _toolbarMaxWidth {
|
||||
const double messageMargin = 16.0;
|
||||
// widget.event.isActivityMessage ? 0 : Avatar.defaultSize + 16 + 8;
|
||||
|
|
@ -318,11 +303,73 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
return maxWidth;
|
||||
}
|
||||
|
||||
// original message size and offset
|
||||
|
||||
RenderBox? get _messageRenderBox => _runWithLogging<RenderBox?>(
|
||||
() => MatrixState.pAnyState.getRenderBox(
|
||||
widget.event.eventId,
|
||||
),
|
||||
"Error getting message render box",
|
||||
null,
|
||||
);
|
||||
|
||||
Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100);
|
||||
|
||||
/// The size of the message in the chat list (as opposed to the expanded size in the center overlay)
|
||||
Size get _originalMessageSize {
|
||||
if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
|
||||
return _defaultMessageSize;
|
||||
}
|
||||
|
||||
return _runWithLogging(
|
||||
() => _messageRenderBox?.size,
|
||||
"Error getting message size",
|
||||
_defaultMessageSize,
|
||||
);
|
||||
}
|
||||
|
||||
static const _messageDefaultLeftMargin = Avatar.defaultSize + 16 + 8;
|
||||
|
||||
// Centered message size and offset
|
||||
|
||||
bool get _centeredMessageHasOverflow {
|
||||
if (_verticalSpace == null ||
|
||||
_centeredMessageSize == null ||
|
||||
_centeredMessageOffset == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final finalMessageHeight = _centeredMessageSize!.height + _reactionsHeight;
|
||||
return finalMessageHeight > _verticalSpace!;
|
||||
}
|
||||
|
||||
/// Size of the centered overlay message adjusted for overflow
|
||||
Size? get _adjustedCenteredMessageSize {
|
||||
if (_centeredMessageHasOverflow) {
|
||||
return Size(
|
||||
_centeredMessageSize!.width,
|
||||
_verticalSpace! - (AppConfig.toolbarSpacing * 2),
|
||||
);
|
||||
}
|
||||
return _centeredMessageSize;
|
||||
}
|
||||
|
||||
Offset? get _adjustedCenteredMessageOffset {
|
||||
if (_centeredMessageHasOverflow) {
|
||||
return Offset(
|
||||
_centeredMessageOffset!.dx,
|
||||
_footerHeight + AppConfig.toolbarSpacing,
|
||||
);
|
||||
}
|
||||
return _centeredMessageOffset;
|
||||
}
|
||||
|
||||
// message offset
|
||||
|
||||
static const Offset _defaultMessageOffset =
|
||||
Offset(_messageDefaultLeftMargin, 300);
|
||||
Offset get _messageOffset {
|
||||
|
||||
Offset get _originalMessageOffset {
|
||||
if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
|
||||
return _defaultMessageOffset;
|
||||
}
|
||||
|
|
@ -333,8 +380,50 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
);
|
||||
}
|
||||
|
||||
double get _messageBottomOffset =>
|
||||
_mediaQuery!.size.height - _messageOffset.dy - _originalMessageHeight;
|
||||
Offset get _adjustedOriginalMessageOffset {
|
||||
if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
|
||||
return _defaultMessageOffset;
|
||||
}
|
||||
|
||||
final topOffset = _originalMessageOffset.dy;
|
||||
final bottomOffset = _originalMessageBottomOffset;
|
||||
final hasHeaderOverflow =
|
||||
topOffset < (_headerHeight + AppConfig.toolbarSpacing);
|
||||
final hasFooterOverflow =
|
||||
bottomOffset < (_footerHeight + AppConfig.toolbarSpacing);
|
||||
|
||||
if (!hasHeaderOverflow && !hasFooterOverflow) {
|
||||
return Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_originalMessageBottomOffset - _reactionsHeight,
|
||||
);
|
||||
}
|
||||
|
||||
if (hasHeaderOverflow) {
|
||||
final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing);
|
||||
return Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_mediaQuery!.size.height -
|
||||
_originalMessageOffset.dy +
|
||||
difference -
|
||||
_originalMessageSize.height,
|
||||
);
|
||||
} else {
|
||||
final difference =
|
||||
bottomOffset - (_footerHeight + AppConfig.toolbarSpacing);
|
||||
return Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_mediaQuery!.size.height -
|
||||
(_originalMessageOffset.dy + difference) -
|
||||
_originalMessageSize.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double get _originalMessageBottomOffset =>
|
||||
_mediaQuery!.size.height -
|
||||
_originalMessageOffset.dy -
|
||||
_originalMessageSize.height;
|
||||
|
||||
double? get _centeredMessageTopOffset {
|
||||
if (_mediaQuery == null ||
|
||||
|
|
@ -349,7 +438,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
double get _messageLeftOffset => max(
|
||||
_messageOffset.dx - _columnWidth - _horizontalPadding,
|
||||
_originalMessageOffset.dx - _columnWidth - _horizontalPadding,
|
||||
0,
|
||||
);
|
||||
|
||||
|
|
@ -358,8 +447,8 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
return 0;
|
||||
}
|
||||
return _mediaQuery!.size.width -
|
||||
_messageOffset.dx -
|
||||
_messageSize.width -
|
||||
_originalMessageOffset.dx -
|
||||
_originalMessageSize.width -
|
||||
_horizontalPadding;
|
||||
}
|
||||
|
||||
|
|
@ -375,9 +464,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
double get _footerHeight {
|
||||
return (_inputBarSize?.height ??
|
||||
(showToolbarButtons ? AppConfig.toolbarButtonsHeight : 0)) +
|
||||
(_mediaQuery?.padding.bottom ?? 0);
|
||||
return _inputBarSize + (_mediaQuery?.padding.bottom ?? 0);
|
||||
}
|
||||
|
||||
// measurement for items in the toolbar
|
||||
|
|
@ -432,13 +519,14 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
child: SizedBox.shrink(),
|
||||
),
|
||||
Opacity(
|
||||
opacity: _finishedAnimation ? 1.0 : 0.0,
|
||||
opacity:
|
||||
_readingAssistanceMode == ReadingAssistanceMode.messageMode
|
||||
? 1.0
|
||||
: 0.0,
|
||||
child: OverlayCenterContent(
|
||||
event: widget.event,
|
||||
messageHeight: null,
|
||||
messageWidth: null,
|
||||
// messageHeight: _adjustedCenteredMessageSize?.height,
|
||||
// messageWidth: _adjustedCenteredMessageSize?.width,
|
||||
maxWidth: widget.overlayController.maxWidth,
|
||||
overlayController: widget.overlayController,
|
||||
chatController: widget.chatController,
|
||||
|
|
@ -448,11 +536,11 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
hasReactions: _hasReactions,
|
||||
onChangeMessageSize: _setCenteredMessageSize,
|
||||
isTransitionAnimation: false,
|
||||
transitionAnimationFinished: _finishedAnimation,
|
||||
maxHeight: _mediaQuery!.size.height -
|
||||
_headerHeight -
|
||||
_footerHeight -
|
||||
AppConfig.toolbarSpacing * 2,
|
||||
readingAssistanceMode: _readingAssistanceMode,
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
|
|
@ -466,13 +554,10 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MeasureRenderBox(
|
||||
onChange: _setInputBarSize,
|
||||
child: OverlayFooter(
|
||||
controller: widget.chatController,
|
||||
overlayController: widget.overlayController,
|
||||
showToolbarButtons: showToolbarButtons,
|
||||
),
|
||||
OverlayFooter(
|
||||
controller: widget.chatController,
|
||||
overlayController: widget.overlayController,
|
||||
showToolbarButtons: showToolbarButtons,
|
||||
),
|
||||
SizedBox(height: _mediaQuery?.padding.bottom ?? 0),
|
||||
],
|
||||
|
|
@ -486,7 +571,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
),
|
||||
],
|
||||
),
|
||||
if (!_finishedAnimation)
|
||||
if (_readingAssistanceMode != ReadingAssistanceMode.messageMode)
|
||||
AnimatedBuilder(
|
||||
animation: _overlayOffsetAnimation ?? _animationController,
|
||||
builder: (context, child) {
|
||||
|
|
@ -500,11 +585,11 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
_messageRightOffset
|
||||
: null,
|
||||
bottom: (_overlayOffsetAnimation?.value)?.dy ??
|
||||
_messageBottomOffset - _reactionsHeight,
|
||||
_originalMessageBottomOffset - _reactionsHeight,
|
||||
child: OverlayCenterContent(
|
||||
event: widget.event,
|
||||
messageHeight: _originalMessageHeight,
|
||||
messageWidth: _messageSize.width,
|
||||
messageHeight: _originalMessageSize.height,
|
||||
messageWidth: _originalMessageSize.width,
|
||||
maxWidth: widget.overlayController.maxWidth,
|
||||
overlayController: widget.overlayController,
|
||||
chatController: widget.chatController,
|
||||
|
|
@ -514,11 +599,11 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
hasReactions: _hasReactions,
|
||||
sizeAnimation: _messageSizeAnimation,
|
||||
isTransitionAnimation: true,
|
||||
transitionAnimationFinished: _finishedAnimation,
|
||||
maxHeight: _mediaQuery!.size.height -
|
||||
_headerHeight -
|
||||
_footerHeight -
|
||||
AppConfig.toolbarSpacing * 2,
|
||||
readingAssistanceMode: _readingAssistanceMode,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -547,7 +632,11 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_centeredMessageTopOffset != null && _tooltipSize != null)
|
||||
if (_centeredMessageTopOffset != null &&
|
||||
_tooltipSize != null &&
|
||||
widget.overlayController.toolbarMode !=
|
||||
MessageMode.noneSelected &&
|
||||
widget.overlayController.selectedToken == null)
|
||||
Positioned(
|
||||
top: max(
|
||||
((_headerHeight + _centeredMessageTopOffset!) / 2) -
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
|
|
@ -12,6 +11,8 @@ import 'package:fluffychat/pangea/message_token_text/message_token_button.dart';
|
|||
import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.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/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -27,9 +28,9 @@ class MessageTokenText extends StatelessWidget {
|
|||
final bool Function(PangeaToken)? _isSelected;
|
||||
final void Function(PangeaToken)? _onClick;
|
||||
final bool Function(PangeaToken)? _isHighlighted;
|
||||
final MessageMode? _messageMode;
|
||||
final MessageOverlayController? _overlayController;
|
||||
final bool _isTransitionAnimation;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
const MessageTokenText({
|
||||
super.key,
|
||||
|
|
@ -42,11 +43,11 @@ class MessageTokenText extends StatelessWidget {
|
|||
MessageMode? messageMode,
|
||||
MessageOverlayController? overlayController,
|
||||
bool isTransitionAnimation = false,
|
||||
this.readingAssistanceMode,
|
||||
}) : _onClick = onClick,
|
||||
_isSelected = isSelected,
|
||||
_style = style,
|
||||
_pangeaMessageEvent = pangeaMessageEvent,
|
||||
_messageMode = messageMode,
|
||||
_isHighlighted = isHighlighted,
|
||||
_overlayController = overlayController,
|
||||
_isTransitionAnimation = isTransitionAnimation;
|
||||
|
|
@ -82,55 +83,10 @@ class MessageTokenText extends StatelessWidget {
|
|||
messageAnalyticsEntry: messageAnalyticsEntry,
|
||||
isSelected: _isSelected,
|
||||
onClick: callOnClick,
|
||||
messageMode: _messageMode,
|
||||
isHighlighted: _isHighlighted,
|
||||
overlayController: _overlayController,
|
||||
isTransitionAnimation: _isTransitionAnimation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HiddenText extends StatelessWidget {
|
||||
final String text;
|
||||
final TextStyle style;
|
||||
|
||||
const HiddenText({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final TextPainter textPainter = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
final textWidth = textPainter.size.width;
|
||||
final textHeight = textPainter.size.height;
|
||||
|
||||
textPainter.dispose();
|
||||
|
||||
return SizedBox(
|
||||
height: textHeight,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: textWidth,
|
||||
height: textHeight,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: textWidth,
|
||||
height: 1,
|
||||
color: style.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -146,12 +102,11 @@ class MessageTextWidget extends StatelessWidget {
|
|||
final bool? softWrap;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final MessageMode? messageMode;
|
||||
|
||||
final Animation<double>? contentSizeAnimation;
|
||||
final MessageOverlayController? overlayController;
|
||||
final bool isTransitionAnimation;
|
||||
final bool isMessage;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
const MessageTextWidget({
|
||||
super.key,
|
||||
|
|
@ -163,56 +118,23 @@ class MessageTextWidget extends StatelessWidget {
|
|||
this.softWrap,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.messageMode,
|
||||
this.isHighlighted,
|
||||
this.contentSizeAnimation,
|
||||
this.overlayController,
|
||||
this.isTransitionAnimation = false,
|
||||
this.isMessage = true,
|
||||
this.readingAssistanceMode,
|
||||
});
|
||||
|
||||
TextStyle style(BuildContext context) => !isMessage
|
||||
? existingStyle
|
||||
: overlayController != null && overlayController!.maxWidth > 600
|
||||
? existingStyle.copyWith(
|
||||
fontSize: Theme.of(context).textTheme.titleLarge?.fontSize,
|
||||
)
|
||||
: existingStyle.copyWith(
|
||||
fontSize: Theme.of(context).textTheme.bodyLarge?.fontSize,
|
||||
);
|
||||
|
||||
/// for some reason, this isn't the same as tokenTextWidth
|
||||
double tokenTextWidthForContainer(BuildContext context, PangeaToken token) {
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(text: token.text.content, style: style(context)),
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
final width = textPainter.width;
|
||||
textPainter.dispose();
|
||||
return width;
|
||||
}
|
||||
|
||||
Color backgroundColor(BuildContext context, TokenPosition tokenPosition) {
|
||||
final hideTokenHighlights = messageAnalyticsEntry != null &&
|
||||
(messageAnalyticsEntry!.hasHiddenWordActivity ||
|
||||
messageAnalyticsEntry!.hasMessageMeaningActivity);
|
||||
|
||||
Color backgroundColor = Colors.transparent;
|
||||
|
||||
if (!hideTokenHighlights) {
|
||||
if (tokenPosition.selected) {
|
||||
backgroundColor = Theme.of(context).colorScheme.primary;
|
||||
}
|
||||
// else if (tokenPosition.isHighlighted) {
|
||||
// backgroundColor = AppConfig.success.withAlpha(80);
|
||||
// }
|
||||
}
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final renderer = TokenRenderingUtil(
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
existingStyle: existingStyle,
|
||||
overlayController: overlayController,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
);
|
||||
|
||||
final Characters messageCharacters =
|
||||
pangeaMessageEvent.messageDisplayText.characters;
|
||||
|
||||
|
|
@ -226,7 +148,7 @@ class MessageTextWidget extends StatelessWidget {
|
|||
if (tokenPositions == null) {
|
||||
return Text(
|
||||
pangeaMessageEvent.messageDisplayText,
|
||||
style: style(context),
|
||||
style: renderer.style(context),
|
||||
softWrap: softWrap,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
|
|
@ -278,39 +200,45 @@ class MessageTextWidget extends StatelessWidget {
|
|||
|
||||
final token = tokenPosition.token!;
|
||||
|
||||
final tokenWidth = tokenTextWidthForContainer(context, token);
|
||||
final tokenWidth = renderer.tokenTextWidthForContainer(
|
||||
context,
|
||||
token.text.content,
|
||||
);
|
||||
|
||||
return WidgetSpan(
|
||||
child: CompositedTransformTarget(
|
||||
link: overlayController == null || isTransitionAnimation
|
||||
? LayerLinkAndKey(token.hashCode.toString()).link
|
||||
: MatrixState.pAnyState
|
||||
link: renderer.assignTokenKey
|
||||
? MatrixState.pAnyState
|
||||
.layerLinkAndKey(token.text.uniqueKey)
|
||||
.link,
|
||||
.link
|
||||
: LayerLinkAndKey(token.hashCode.toString()).link,
|
||||
child: Column(
|
||||
key: overlayController == null || isTransitionAnimation
|
||||
? null
|
||||
: MatrixState.pAnyState
|
||||
key: renderer.assignTokenKey
|
||||
? MatrixState.pAnyState
|
||||
.layerLinkAndKey(token.text.uniqueKey)
|
||||
.key,
|
||||
.key
|
||||
: null,
|
||||
children: [
|
||||
MessageTokenButton(
|
||||
token: token,
|
||||
overlayController: overlayController,
|
||||
textStyle: style(context),
|
||||
width: tokenWidth,
|
||||
animate: isTransitionAnimation,
|
||||
practiceTarget: overlayController
|
||||
?.toolbarMode.associatedActivityType !=
|
||||
null
|
||||
? overlayController?.practiceSelection
|
||||
?.activities(
|
||||
overlayController!
|
||||
.toolbarMode.associatedActivityType!,
|
||||
)
|
||||
.firstWhereOrNull((a) => a.tokens.contains(token))
|
||||
: null,
|
||||
),
|
||||
if (renderer.showCenterStyling)
|
||||
MessageTokenButton(
|
||||
token: token,
|
||||
overlayController: overlayController,
|
||||
textStyle: renderer.style(context),
|
||||
width: tokenWidth,
|
||||
animate: isTransitionAnimation,
|
||||
practiceTarget: overlayController
|
||||
?.toolbarMode.associatedActivityType !=
|
||||
null
|
||||
? overlayController?.practiceSelection
|
||||
?.activities(
|
||||
overlayController!
|
||||
.toolbarMode.associatedActivityType!,
|
||||
)
|
||||
.firstWhereOrNull(
|
||||
(a) => a.tokens.contains(token),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
|
|
@ -324,7 +252,13 @@ class MessageTextWidget extends StatelessWidget {
|
|||
if (start.isNotEmpty)
|
||||
LinkifySpan(
|
||||
text: start,
|
||||
style: style(context),
|
||||
style: renderer.style(
|
||||
context,
|
||||
color: renderer.backgroundColor(
|
||||
context,
|
||||
tokenPosition.selected,
|
||||
),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: linkColor,
|
||||
|
|
@ -332,39 +266,46 @@ class MessageTextWidget extends StatelessWidget {
|
|||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
tokenPosition.hideContent
|
||||
? WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null
|
||||
? () => onClick?.call(tokenPosition)
|
||||
: null,
|
||||
child: HiddenText(
|
||||
text: middle,
|
||||
style: style(context),
|
||||
),
|
||||
),
|
||||
)
|
||||
: LinkifySpan(
|
||||
text: middle,
|
||||
// style: style.merge(
|
||||
// TextStyle(
|
||||
// backgroundColor: backgroundColor(tokenPosition)
|
||||
// ),
|
||||
// ),
|
||||
style: style(context),
|
||||
linkStyle: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: linkColor,
|
||||
),
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url)
|
||||
.launchUrl(),
|
||||
),
|
||||
// tokenPosition.hideContent
|
||||
// ? WidgetSpan(
|
||||
// alignment: PlaceholderAlignment.middle,
|
||||
// child: GestureDetector(
|
||||
// onTap: onClick != null
|
||||
// ? () => onClick?.call(tokenPosition)
|
||||
// : null,
|
||||
// child: HiddenText(
|
||||
// text: middle,
|
||||
// style: style(context),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// :
|
||||
LinkifySpan(
|
||||
text: middle,
|
||||
style: renderer.style(
|
||||
context,
|
||||
color: renderer.backgroundColor(
|
||||
context,
|
||||
tokenPosition.selected,
|
||||
),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: linkColor,
|
||||
),
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
if (end.isNotEmpty)
|
||||
LinkifySpan(
|
||||
text: end,
|
||||
style: style(context),
|
||||
style: renderer.style(
|
||||
context,
|
||||
color: renderer.backgroundColor(
|
||||
context,
|
||||
tokenPosition.selected,
|
||||
),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: linkColor,
|
||||
|
|
@ -377,39 +318,41 @@ class MessageTextWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(
|
||||
milliseconds: AppConfig.overlayAnimationDuration,
|
||||
),
|
||||
height:
|
||||
overlayController != null && !isTransitionAnimation
|
||||
? 4
|
||||
: 0,
|
||||
width: tokenWidth,
|
||||
child: Container(
|
||||
color: backgroundColor(context, tokenPosition),
|
||||
),
|
||||
),
|
||||
// AnimatedContainer(
|
||||
// duration: const Duration(
|
||||
// milliseconds: AppConfig.overlayAnimationDuration,
|
||||
// ),
|
||||
// height: overlayController != null && isTransitionAnimation
|
||||
// ? 4
|
||||
// : 0,
|
||||
// width: tokenWidth,
|
||||
// child: Container(
|
||||
// color: backgroundColor(context, tokenPosition),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if ((i > 0 || i < tokenPositions.length - 1) &&
|
||||
tokenPositions[i + 1].hideContent &&
|
||||
tokenPositions[i - 1].hideContent) {
|
||||
return WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null
|
||||
? () => onClick?.call(tokenPosition)
|
||||
: null,
|
||||
child: HiddenText(text: substring, style: style(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
// if ((i > 0 || i < tokenPositions.length - 1) &&
|
||||
// tokenPositions[i + 1].hideContent &&
|
||||
// tokenPositions[i - 1].hideContent) {
|
||||
// return WidgetSpan(
|
||||
// child: GestureDetector(
|
||||
// onTap: onClick != null
|
||||
// ? () => onClick?.call(tokenPosition)
|
||||
// : null,
|
||||
// child: HiddenText(
|
||||
// text: substring,
|
||||
// style: style(context),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
return LinkifySpan(
|
||||
text: substring,
|
||||
style: style(context),
|
||||
style: renderer.style(context),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
|
@ -423,3 +366,48 @@ class MessageTextWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// class HiddenText extends StatelessWidget {
|
||||
// final String text;
|
||||
// final TextStyle style;
|
||||
|
||||
// const HiddenText({
|
||||
// super.key,
|
||||
// required this.text,
|
||||
// required this.style,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final TextPainter textPainter = TextPainter(
|
||||
// text: TextSpan(text: text, style: style),
|
||||
// textDirection: TextDirection.ltr,
|
||||
// )..layout();
|
||||
|
||||
// final textWidth = textPainter.size.width;
|
||||
// final textHeight = textPainter.size.height;
|
||||
|
||||
// textPainter.dispose();
|
||||
|
||||
// return SizedBox(
|
||||
// height: textHeight,
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// Container(
|
||||
// width: textWidth,
|
||||
// height: textHeight,
|
||||
// color: Colors.transparent,
|
||||
// ),
|
||||
// Positioned(
|
||||
// bottom: 0,
|
||||
// child: Container(
|
||||
// width: textWidth,
|
||||
// height: 1,
|
||||
// color: style.color,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/message_reactions.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/overlay_message.dart';
|
||||
|
|
@ -29,7 +30,7 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
final bool hasReactions;
|
||||
|
||||
final bool isTransitionAnimation;
|
||||
final bool transitionAnimationFinished;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
const OverlayCenterContent({
|
||||
required this.event,
|
||||
|
|
@ -46,58 +47,64 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
this.onChangeMessageSize,
|
||||
this.sizeAnimation,
|
||||
this.isTransitionAnimation = false,
|
||||
this.transitionAnimationFinished = false,
|
||||
this.readingAssistanceMode,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: event.senderId == event.room.client.userID
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MeasureRenderBox(
|
||||
onChange: onChangeMessageSize,
|
||||
child: OverlayMessage(
|
||||
event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
controller: chatController,
|
||||
overlayController: overlayController,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
timeline: chatController.timeline!,
|
||||
sizeAnimation: sizeAnimation,
|
||||
// there's a split seconds between when the transition animation starts and
|
||||
// when the sizeAnimation is set when the original dimensions need to be enforced
|
||||
messageWidth: (sizeAnimation == null && isTransitionAnimation)
|
||||
? messageWidth
|
||||
: null,
|
||||
messageHeight: (sizeAnimation == null && isTransitionAnimation)
|
||||
? messageHeight
|
||||
: null,
|
||||
maxHeight: maxHeight,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
),
|
||||
),
|
||||
if (hasReactions)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: MessageReactions(
|
||||
event,
|
||||
chatController.timeline!,
|
||||
),
|
||||
return IgnorePointer(
|
||||
ignoring: !isTransitionAnimation &&
|
||||
readingAssistanceMode != ReadingAssistanceMode.messageMode,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: event.senderId == event.room.client.userID
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MeasureRenderBox(
|
||||
onChange: onChangeMessageSize,
|
||||
child: OverlayMessage(
|
||||
event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
controller: chatController,
|
||||
overlayController: overlayController,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
timeline: chatController.timeline!,
|
||||
sizeAnimation: sizeAnimation,
|
||||
// there's a split seconds between when the transition animation starts and
|
||||
// when the sizeAnimation is set when the original dimensions need to be enforced
|
||||
messageWidth: (sizeAnimation == null && isTransitionAnimation)
|
||||
? messageWidth
|
||||
: null,
|
||||
messageHeight:
|
||||
(sizeAnimation == null && isTransitionAnimation)
|
||||
? messageHeight
|
||||
: null,
|
||||
maxHeight: maxHeight,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (hasReactions)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: MessageReactions(
|
||||
event,
|
||||
chatController.timeline!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:fluffychat/pages/chat/events/message_content.dart';
|
|||
import 'package:fluffychat/pages/chat/events/reply_content.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -30,6 +31,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
final double maxHeight;
|
||||
|
||||
final bool isTransitionAnimation;
|
||||
final ReadingAssistanceMode? readingAssistanceMode;
|
||||
|
||||
const OverlayMessage(
|
||||
this.event, {
|
||||
|
|
@ -45,6 +47,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
this.prevEvent,
|
||||
this.sizeAnimation,
|
||||
this.isTransitionAnimation = false,
|
||||
this.readingAssistanceMode,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -204,6 +207,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
? theme.colorScheme.onPrimary
|
||||
: theme.colorScheme.onSurface,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue