Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add reaction box positioning strategy #47

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/src/widgets/reaction_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ReactionButton<T> extends StatefulWidget {
this.itemAnimationDuration = const Duration(milliseconds: 100),
this.hoverDuration = const Duration(milliseconds: 400),
this.child,
this.positioningStrategy = const TryStayInBoundariesStrategy(),
}) : _type = child != null ? ReactionType.container : ReactionType.button;

/// This triggers when reaction button value changed.
Expand Down Expand Up @@ -66,6 +67,9 @@ class ReactionButton<T> extends StatefulWidget {
/// Animation duration while moving [default = const Duration(milliseconds: 100)]
final Duration itemAnimationDuration;

/// Reaction box positioning strategy [default = const TryStayInBoundariesStrategy()]
final ReactionBoxPositioningStrategy positioningStrategy;

final Size itemSize;

final bool animateBox;
Expand Down Expand Up @@ -127,6 +131,7 @@ class _ReactionButtonState<T> extends State<ReactionButton<T>> {
itemScale: widget.itemScale,
itemScaleDuration: widget.itemAnimationDuration,
animateBox: widget.animateBox,
positioningStrategy: widget.positioningStrategy,
onReactionSelected: (reaction) {
_updateReaction(reaction);
_disposeOverlayEntry();
Expand Down
78 changes: 65 additions & 13 deletions lib/src/widgets/reactions_box.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_reaction_button/flutter_reaction_button.dart';
import 'package:flutter_reaction_button/src/common/position.dart';
Expand All @@ -21,10 +23,13 @@ class ReactionsBox<T> extends StatefulWidget {
required this.onReactionSelected,
required this.onClose,
required this.animateBox,
this.positioningStrategy = const TryStayInBoundariesStrategy(),
}) : assert(itemScale > 0.0 && itemScale < 1);

final Offset offset;

final ReactionBoxPositioningStrategy positioningStrategy;

final Size itemSize;

final List<Reaction<T>?> reactions;
Expand Down Expand Up @@ -73,11 +78,7 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
(widget.itemSpace * (widget.reactions.length - 1)) +
widget.boxPadding.horizontal;

bool get shouldStartFromEnd =>
MediaQuery.sizeOf(context).width - boxWidth < widget.offset.dx;

bool get shouldStartFromBottom =>
widget.offset.dy < boxHeight + widget.boxPadding.vertical;
double get screenWidth => MediaQuery.sizeOf(context).width;

bool _isOffsetOutsideBox(Offset offset) {
final Rect boxRect = Rect.fromLTWH(0, 0, boxWidth, boxHeight);
Expand Down Expand Up @@ -129,14 +130,16 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
),
),
PositionedDirectional(
start: shouldStartFromEnd
? widget.offset.dx - boxWidth
: widget.offset.dx,
top: shouldStartFromBottom
? widget.offset.dy + widget.itemSize.height
: widget.offset.dy -
widget.itemSize.height -
widget.boxPadding.vertical,
start: widget.positioningStrategy.getHorizontalOffset(context,
boxWidth: boxWidth,
screenWidth: screenWidth,
offset: widget.offset,
boxPadding: widget.boxPadding),
top: widget.positioningStrategy.getVerticalOffset(context,
boxHeight: boxHeight,
screenWidth: screenWidth,
offset: widget.offset,
boxPadding: widget.boxPadding),
child: Listener(
onPointerDown: (point) {
_positionNotifier.value = PositionData(
Expand Down Expand Up @@ -222,3 +225,52 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
);
}
}

abstract class ReactionBoxPositioningStrategy {
const ReactionBoxPositioningStrategy();
double getHorizontalOffset(BuildContext context,
{required double boxWidth,
required double screenWidth,
required Offset offset,
required EdgeInsetsGeometry boxPadding});

double getVerticalOffset(BuildContext context,
{required double boxHeight,
required double screenWidth,
required Offset offset,
required EdgeInsetsGeometry boxPadding}) {
final shouldStartFromBottom = offset.dy < boxHeight + boxPadding.vertical;
return shouldStartFromBottom
? offset.dy + boxHeight
: offset.dy - boxHeight - boxPadding.vertical;
}
}

class TryStayInBoundariesStrategy extends ReactionBoxPositioningStrategy {
const TryStayInBoundariesStrategy();
@override
double getHorizontalOffset(BuildContext context,
{required double boxWidth,
required double screenWidth,
required Offset offset,
required EdgeInsetsGeometry boxPadding}) {
final shouldStartFromEnd = offset.dx + boxWidth > screenWidth;
if (shouldStartFromEnd) {
return max(0, min(screenWidth - boxWidth, offset.dx - boxWidth));
} else {
return max(0, offset.dx);
}
}
}

class CenterHorizontallyStrategy extends ReactionBoxPositioningStrategy {
const CenterHorizontallyStrategy();
@override
double getHorizontalOffset(BuildContext context,
{required double boxWidth,
required double screenWidth,
required Offset offset,
required EdgeInsetsGeometry boxPadding}) {
return (screenWidth - boxWidth) / 2;
}
}