Merge pull request #716 from pangeachat/message-overlay-positioning
Message overlay positioning
This commit is contained in:
commit
b7eaab52da
4 changed files with 195 additions and 67 deletions
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class ToolbarButtons extends StatelessWidget {
|
|||
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: 50,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
|
|
|
|||
116
lib/pangea/widgets/chat/overlay_message.dart
Normal file
116
lib/pangea/widgets/chat/overlay_message.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue