You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/widgets/chat/message_selection_overlay.dart

230 lines
7.2 KiB
Dart

import 'dart:async';
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/widgets/chat/message_text_selection.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class MessageSelectionOverlay extends StatefulWidget {
final ChatController controller;
final Event event;
final PangeaMessageEvent pangeaMessageEvent;
final MessageMode? initialMode;
final MessageTextSelection textSelection;
const MessageSelectionOverlay({
required this.controller,
required this.event,
required this.pangeaMessageEvent,
required this.textSelection,
this.initialMode,
super.key,
});
@override
MessageSelectionOverlayState createState() => MessageSelectionOverlayState();
}
class MessageSelectionOverlayState extends State<MessageSelectionOverlay> {
double overlayBottomOffset = -1;
double adjustedOverlayBottomOffset = -1;
Size? messageSize;
Offset? messageOffset;
final StreamController _completeAnimationStream =
StreamController.broadcast();
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// position the overlay directly over the underlying message
setOverlayBottomOffset();
// wait for the toolbar to animate to full height
_completeAnimationStream.stream.first.then((_) {
if (toolbarHeight == null ||
messageSize == null ||
messageOffset == null) {
return;
}
// Once the toolbar has fully expanded, adjust
// the overlay's position if there's an overflow
final overlayTopOffset = messageOffset!.dy - toolbarHeight!;
final bool hasHeaderOverflow = overlayTopOffset < headerHeight;
final bool hasFooterOverflow = overlayBottomOffset < footerHeight;
if (hasHeaderOverflow) {
final overlayHeight = toolbarHeight! + messageSize!.height;
adjustedOverlayBottomOffset = screenHeight -
overlayHeight -
footerHeight -
MediaQuery.of(context).padding.bottom;
} else if (hasFooterOverflow) {
adjustedOverlayBottomOffset = footerHeight;
}
setState(() {});
});
}
@override
void dispose() {
_completeAnimationStream.close();
super.dispose();
}
void setOverlayBottomOffset() {
// Try to get the offset and size of the original message bubble.
// If it fails, return an empty SizedBox. For instance, this can fail if
// you change the screen size while the overlay is open.
try {
final messageRenderBox = MatrixState.pAnyState.getRenderBox(
widget.event.eventId,
);
if (messageRenderBox != null && messageRenderBox.hasSize) {
messageSize = messageRenderBox.size;
messageOffset = messageRenderBox.localToGlobal(Offset.zero);
final messageTopOffset = messageOffset!.dy;
overlayBottomOffset =
screenHeight - messageTopOffset - messageSize!.height;
}
} catch (err) {
overlayBottomOffset = adjustedOverlayBottomOffset = -1;
} finally {
setState(() {});
}
}
// height of the reply/forward bar + the reaction picker + contextual padding
double get footerHeight =>
48 + 56 + (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0);
double get headerHeight =>
(Theme.of(context).appBarTheme.toolbarHeight ?? 56) +
MediaQuery.of(context).padding.top;
double get screenHeight => MediaQuery.of(context).size.height;
double? get toolbarHeight {
try {
final toolbarRenderBox = MatrixState.pAnyState.getRenderBox(
'${widget.pangeaMessageEvent.eventId}-toolbar',
);
return toolbarRenderBox?.size.height;
} catch (e) {
return null;
}
}
@override
Widget build(BuildContext context) {
if (overlayBottomOffset == -1) {
return const SizedBox.shrink();
}
final overlayMessage = ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 2.5,
),
child: Material(
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: widget.pangeaMessageEvent.ownMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(
left: widget.pangeaMessageEvent.ownMessage
? 0
: Avatar.defaultSize + 16,
right: widget.pangeaMessageEvent.ownMessage ? 8 : 0,
),
child: MessageToolbar(
pangeaMessageEvent: widget.pangeaMessageEvent,
controller: widget.controller,
textSelection: widget.textSelection,
completeAnimationStream: _completeAnimationStream,
initialMode: widget.initialMode,
),
),
],
),
Message(
widget.event,
onSwipe: () => {},
onInfoTab: (_) => {},
onAvatarTab: (_) => {},
scrollToEventId: (_) => {},
onSelect: (_) => {},
immersionMode: widget.controller.choreographer.immersionMode,
controller: widget.controller,
timeline: widget.controller.timeline!,
isOverlay: true,
animateIn: false,
),
],
),
),
);
final bool showDetails = Matrix.of(context)
.store
.getBool(SettingKeys.displayChatDetailsColumn) ??
false;
return Expanded(
child: Stack(
children: [
AnimatedPositioned(
duration: FluffyThemes.animationDuration,
left: 0,
right: showDetails ? FluffyThemes.columnWidth : 0,
bottom: adjustedOverlayBottomOffset == -1
? overlayBottomOffset
: adjustedOverlayBottomOffset,
child: Align(
alignment: Alignment.center,
child: overlayMessage,
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
OverlayFooter(controller: widget.controller),
],
),
),
Material(
child: OverlayHeader(controller: widget.controller),
),
],
),
);
}
}