@@ -21,6 +21,7 @@ class ReactionsDialogWidget extends StatefulWidget {
2121 this .reactions = DefaultData .reactions,
2222 this .widgetAlignment = Alignment .centerRight,
2323 this .menuItemsWidth = 0.50 ,
24+ this .messagePosition,
2425 });
2526
2627 // Id for the hero widget
@@ -47,6 +48,9 @@ class ReactionsDialogWidget extends StatefulWidget {
4748 // The width of the menu items
4849 final double menuItemsWidth;
4950
51+ // The position of the message on screen (optional)
52+ final Offset ? messagePosition;
53+
5054 @override
5155 State <ReactionsDialogWidget > createState () => _ReactionsDialogWidgetState ();
5256}
@@ -69,19 +73,17 @@ class _ReactionsDialogWidgetState extends State<ReactionsDialogWidget> {
6973 child: BackdropFilter (
7074 filter: ImageFilter .blur (sigmaX: 40 , sigmaY: 40 ),
7175 child: SafeArea (
72- child: Column (
73- children: [
74- const Spacer (),
75- buildReactions (context),
76- Gap (16. h),
77- Padding (
78- padding: EdgeInsets .symmetric (horizontal: 16. w),
79- child: buildMessage (),
80- ),
81- Gap (16. h),
82- buildMenuItems (context),
83- Gap (32. h),
84- ],
76+ child: LayoutBuilder (
77+ builder: (context, constraints) {
78+ return _PositionedContent (
79+ messagePosition: widget.messagePosition,
80+ menuItemsCount: widget.menuItems.length,
81+ buildReactions: () => buildReactions (context),
82+ buildMessage: buildMessage,
83+ buildMenuItems: () => buildMenuItems (context),
84+ constraints: constraints,
85+ );
86+ },
8587 ),
8688 ),
8789 ),
@@ -94,7 +96,7 @@ class _ReactionsDialogWidgetState extends State<ReactionsDialogWidget> {
9496 alignment: widget.widgetAlignment,
9597 child: Container (
9698 width: MediaQuery .of (context).size.width * widget.menuItemsWidth,
97- margin: EdgeInsets .symmetric (horizontal: 48 . w),
99+ margin: EdgeInsets .symmetric (horizontal: 18 . w),
98100 decoration: BoxDecoration (
99101 color: context.colors.primaryForeground,
100102 ),
@@ -164,7 +166,7 @@ class _ReactionsDialogWidgetState extends State<ReactionsDialogWidget> {
164166 return Align (
165167 alignment: widget.widgetAlignment,
166168 child: Container (
167- margin: EdgeInsets .symmetric (horizontal: 16 . w),
169+ margin: EdgeInsets .symmetric (horizontal: 8 . w),
168170 child: widget.messageWidget,
169171 ),
170172 );
@@ -240,3 +242,124 @@ class _ReactionsDialogWidgetState extends State<ReactionsDialogWidget> {
240242 );
241243 }
242244}
245+
246+ class _PositionedContent extends StatelessWidget {
247+ const _PositionedContent ({
248+ required this .messagePosition,
249+ required this .menuItemsCount,
250+ required this .buildReactions,
251+ required this .buildMessage,
252+ required this .buildMenuItems,
253+ required this .constraints,
254+ });
255+
256+ final Offset ? messagePosition;
257+ final int menuItemsCount;
258+ final Widget Function () buildReactions;
259+ final Widget Function () buildMessage;
260+ final Widget Function () buildMenuItems;
261+ final BoxConstraints constraints;
262+
263+ @override
264+ Widget build (BuildContext context) {
265+ final reactionBarHeight = 56. h; // Approximate height of reaction bar
266+ final menuItemsHeight = (menuItemsCount * 48. h) + 32. h; // Menu items + bottom gap
267+ final gapBetweenReactionsAndMessage = 16. h;
268+ final gapBetweenMessageAndMenu = 16. h;
269+
270+ final heightBelowMessage = gapBetweenMessageAndMenu + menuItemsHeight;
271+
272+ if (messagePosition != null ) {
273+ final topSpacing = _calculateTopSpacing (
274+ context: context,
275+ reactionBarHeight: reactionBarHeight,
276+ heightBelowMessage: heightBelowMessage,
277+ gapBetweenReactionsAndMessage: gapBetweenReactionsAndMessage,
278+ );
279+
280+ return _buildPositionedLayout (
281+ topSpacing: topSpacing,
282+ gapBetweenReactionsAndMessage: gapBetweenReactionsAndMessage,
283+ gapBetweenMessageAndMenu: gapBetweenMessageAndMenu,
284+ );
285+ }
286+
287+ return _buildCenteredLayout ();
288+ }
289+
290+ double _calculateTopSpacing ({
291+ required BuildContext context,
292+ required double reactionBarHeight,
293+ required double heightBelowMessage,
294+ required double gapBetweenReactionsAndMessage,
295+ }) {
296+ // Get the safe area insets to adjust for status bar, notch, etc.
297+ final mediaQuery = MediaQuery .of (context);
298+ final topInset = mediaQuery.padding.top;
299+
300+ // The position passed is the top of the message widget in global coordinates
301+ // Adjust by subtracting the top safe area inset because dialog is inside SafeArea
302+ final messageTopPosition = messagePosition! .dy - topInset;
303+ final screenHeight = constraints.maxHeight;
304+
305+ // Calculate where the reactions bar should be positioned (above the message)
306+ // This offset accounts for typical message padding and visual spacing
307+ final reactionBarOffset = 40. h;
308+ // Additional upward adjustment for better visual alignment
309+ final visualAlignmentOffset = 16. h;
310+ final messageTopY = messageTopPosition - reactionBarOffset - visualAlignmentOffset;
311+
312+ final spaceBelow = screenHeight - messageTopPosition - 40. h;
313+
314+ // Check if we need to move the message up to fit the menu
315+ final adjustedMessageTopY =
316+ spaceBelow < heightBelowMessage
317+ ? (messageTopY - (heightBelowMessage - spaceBelow)).clamp (
318+ reactionBarHeight + gapBetweenReactionsAndMessage + 20. h, // Minimum top position
319+ messageTopY, // Don't move down, only up
320+ )
321+ : messageTopY;
322+
323+ // Position with reactions above the message position
324+ return (adjustedMessageTopY - reactionBarHeight - gapBetweenReactionsAndMessage).clamp (
325+ 0.0 ,
326+ screenHeight,
327+ );
328+ }
329+
330+ Widget _buildPositionedLayout ({
331+ required double topSpacing,
332+ required double gapBetweenReactionsAndMessage,
333+ required double gapBetweenMessageAndMenu,
334+ }) {
335+ return Column (
336+ crossAxisAlignment: CrossAxisAlignment .stretch,
337+ children: [
338+ Gap (topSpacing),
339+ buildReactions (),
340+ Gap (gapBetweenReactionsAndMessage),
341+ buildMessage (),
342+ Gap (gapBetweenMessageAndMenu),
343+ buildMenuItems (),
344+ const Spacer (),
345+ ],
346+ );
347+ }
348+
349+ Widget _buildCenteredLayout () {
350+ return Column (
351+ children: [
352+ const Spacer (),
353+ buildReactions (),
354+ Gap (16. h),
355+ Padding (
356+ padding: EdgeInsets .symmetric (horizontal: 16. w),
357+ child: buildMessage (),
358+ ),
359+ Gap (16. h),
360+ buildMenuItems (),
361+ Gap (32. h),
362+ ],
363+ );
364+ }
365+ }
0 commit comments