Merge pull request #716 from pangeachat/message-overlay-positioning

Message overlay positioning
This commit is contained in:
ggurdin 2024-10-08 09:18:46 -04:00 committed by GitHub
commit b7eaab52da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 195 additions and 67 deletions

View file

@ -4,14 +4,15 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/pangea/enum/message_mode_enum.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
@ -214,6 +215,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
PangeaTokenText? get selectedSpan => _selectedSpan;
final int toolbarButtonsHeight = 50;
@override
void didChangeDependencies() {
super.didChangeDependencies();
@ -224,11 +227,13 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
// position the overlay directly over the underlying message
final headerBottomOffset = screenHeight - headerHeight;
final footerBottomOffset = footerHeight;
final currentBottomOffset =
screenHeight - messageOffset!.dy - messageSize!.height;
final currentBottomOffset = screenHeight -
messageOffset!.dy -
messageSize!.height -
toolbarButtonsHeight;
final bool hasHeaderOverflow =
messageOffset!.dy < (AppConfig.toolbarMaxHeight + headerHeight);
final bool hasHeaderOverflow = (messageOffset!.dy - toolbarButtonsHeight) <
(AppConfig.toolbarMaxHeight + headerHeight);
final bool hasFooterOverflow = footerHeight > currentBottomOffset;
if (!hasHeaderOverflow && !hasFooterOverflow) return;
@ -241,7 +246,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
// if the overlay would have a footer overflow for this message,
// check if shifting the overlay up could cause a header overflow
final bottomOffsetDifference = footerHeight - currentBottomOffset;
final newTopOffset = messageOffset!.dy - bottomOffsetDifference;
final newTopOffset =
messageOffset!.dy - bottomOffsetDifference - toolbarButtonsHeight;
final bool upshiftCausesHeaderOverflow = hasFooterOverflow &&
newTopOffset < (headerHeight + AppConfig.toolbarMaxHeight);
@ -301,6 +307,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
double get screenHeight => MediaQuery.of(context).size.height;
double get screenWidth => MediaQuery.of(context).size.width;
@override
Widget build(BuildContext context) {
final bool showDetails = (Matrix.of(context)
@ -310,7 +318,25 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
FluffyThemes.isThreeColumnMode(context) &&
widget.chatController.room.membership == Membership.join;
final overlayMessage = ConstrainedBox(
// the default spacing between the side of the screen and the message bubble
final double messageMargin =
pangeaMessageEvent.ownMessage ? Avatar.defaultSize + 16 : 8;
// the actual spacing between the side of the screen and
// the message bubble, accounts for wide screen
double extraChatSpace = FluffyThemes.isColumnMode(context)
? ((screenWidth -
(FluffyThemes.columnWidth * 3.5) -
FluffyThemes.navRailWidth) /
2) +
messageMargin
: messageMargin;
if (extraChatSpace < messageMargin) {
extraChatSpace = messageMargin;
}
final overlayMessage = Container(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 2.5,
),
@ -318,76 +344,75 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: widget._pangeaMessageEvent.ownMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: widget._pangeaMessageEvent.ownMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
MessagePadding(
pangeaMessageEvent: pangeaMessageEvent,
child: MessageToolbar(
pangeaMessageEvent: widget._pangeaMessageEvent,
overLayController: this,
),
),
],
MessageToolbar(
pangeaMessageEvent: widget._pangeaMessageEvent,
overLayController: this,
),
Message(
widget._event,
onSwipe: () => {},
onInfoTab: (_) => {},
onAvatarTab: (_) => {},
scrollToEventId: (_) => {},
onSelect: (_) => {},
OverlayMessage(
pangeaMessageEvent,
immersionMode: widget.chatController.choreographer.immersionMode,
controller: widget.chatController,
timeline: widget.chatController.timeline!,
overlayController: this,
animateIn: false,
nextEvent: widget._nextEvent,
previousEvent: widget._prevEvent,
prevEvent: widget._prevEvent,
timeline: widget.chatController.timeline!,
messageWidth: messageSize!.width,
),
ToolbarButtons(
overlayController: this,
width: 250,
),
// MessageReactions(widget._event, widget.chatController.timeline!),
// const SizedBox(height: 6),
// MessagePadding(
// pangeaMessageEvent: pangeaMessageEvent,
// child: ToolbarButtons(overlayController: this, width: 250),
// ),
],
),
),
);
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
final columnOffset = FluffyThemes.isColumnMode(context)
? FluffyThemes.columnWidth + FluffyThemes.navRailWidth
: 0;
final double leftPadding = widget._pangeaMessageEvent.ownMessage
? extraChatSpace
: messageOffset!.dx - horizontalPadding - columnOffset;
final double rightPadding = widget._pangeaMessageEvent.ownMessage
? screenWidth -
messageOffset!.dx -
messageSize!.width -
horizontalPadding
: extraChatSpace;
final positionedOverlayMessage = _overlayPositionAnimation == null
? Positioned(
left: 0,
right: showDetails ? FluffyThemes.columnWidth : 0,
bottom: screenHeight - messageOffset!.dy - messageSize!.height,
child: Align(
alignment: Alignment.center,
child: overlayMessage,
),
left: leftPadding,
right: rightPadding,
bottom: screenHeight -
messageOffset!.dy -
messageSize!.height -
toolbarButtonsHeight,
child: overlayMessage,
)
: AnimatedBuilder(
animation: _overlayPositionAnimation!,
builder: (context, child) {
return Positioned(
left: 0,
right: showDetails ? FluffyThemes.columnWidth : 0,
left: leftPadding,
right: rightPadding,
bottom: _overlayPositionAnimation!.value,
child: Align(
alignment: Alignment.center,
child: overlayMessage,
),
child: overlayMessage,
);
},
);
return Padding(
padding: EdgeInsets.only(
left: FluffyThemes.isColumnMode(context) ? 8.0 : 0.0,
right: FluffyThemes.isColumnMode(context) ? 8.0 : 0.0,
left: horizontalPadding,
right: horizontalPadding,
),
child: Stack(
children: [

View file

@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
@ -124,12 +123,6 @@ class MessageToolbarState extends State<MessageToolbar> {
child: Column(
children: [
Container(
constraints: const BoxConstraints(
maxHeight: AppConfig.toolbarMaxHeight,
maxWidth: 350,
minWidth: 350,
),
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
@ -140,10 +133,9 @@ class MessageToolbarState extends State<MessageToolbar> {
Radius.circular(AppConfig.borderRadius),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
child: Row(
children: [
Flexible(
Expanded(
child: SingleChildScrollView(
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
@ -154,12 +146,6 @@ class MessageToolbarState extends State<MessageToolbar> {
],
),
),
const SizedBox(height: 6),
ToolbarButtons(
overlayController: widget.overLayController,
width: 250,
),
const SizedBox(height: 6),
],
),
);

View file

@ -37,6 +37,7 @@ class ToolbarButtons extends StatelessWidget {
return SizedBox(
width: width,
height: 50,
child: Stack(
alignment: Alignment.center,
children: [

View file

@ -0,0 +1,116 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message_content.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class OverlayMessage extends StatelessWidget {
final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overlayController;
final ChatController controller;
final Event? nextEvent;
final Event? prevEvent;
final Timeline timeline;
final bool immersionMode;
final double messageWidth;
const OverlayMessage(
this.pangeaMessageEvent, {
this.immersionMode = false,
required this.overlayController,
required this.controller,
required this.timeline,
required this.messageWidth,
this.nextEvent,
this.prevEvent,
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final bool ownMessage =
pangeaMessageEvent.event.senderId == Matrix.of(context).client.userID;
final displayTime =
pangeaMessageEvent.event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!pangeaMessageEvent.event.originServerTs
.sameEnvironment(nextEvent!.originServerTs);
final nextEventSameSender = nextEvent != null &&
{
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted,
}.contains(nextEvent!.type) &&
nextEvent!.senderId == pangeaMessageEvent.event.senderId &&
!displayTime;
final previousEventSameSender = prevEvent != null &&
{
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted,
}.contains(prevEvent!.type) &&
prevEvent!.senderId == pangeaMessageEvent.event.senderId &&
prevEvent!.originServerTs
.sameEnvironment(pangeaMessageEvent.event.originServerTs);
const hardCorner = Radius.circular(4);
const roundedCorner = Radius.circular(AppConfig.borderRadius);
final borderRadius = BorderRadius.only(
topLeft: !ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
topRight: ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
bottomLeft:
!ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
bottomRight:
ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
);
final displayEvent = pangeaMessageEvent.event.getDisplayEvent(timeline);
var color = theme.colorScheme.surfaceContainerHighest;
if (ownMessage) {
color = displayEvent.status.isError
? Colors.redAccent
: theme.colorScheme.primary;
}
return Material(
color: color,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: borderRadius,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
width: messageWidth,
child: MessageContent(
pangeaMessageEvent.event,
textColor: ownMessage
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode: immersionMode,
overlayController: overlayController,
controller: controller,
nextEvent: nextEvent,
prevEvent: prevEvent,
borderRadius: borderRadius,
),
),
);
}
}