Skip to content

Commit

Permalink
add reaction vibration and reaction highlight when move the finger le…
Browse files Browse the repository at this point in the history
…ft and right
  • Loading branch information
sherlockvn committed Jun 23, 2024
1 parent dbab28e commit 36a8964
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 24 deletions.
3 changes: 3 additions & 0 deletions lib/src/models/reaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Reaction<T> {
required this.icon,
Widget? previewIcon,
this.title,
this.isSelected = false,
}) : previewIcon = previewIcon ?? icon;

/// Widget showing as button after selecting preview Icon from box appear.
Expand All @@ -23,6 +24,8 @@ class Reaction<T> {

final T? value;

final bool isSelected;

@override
bool operator ==(Object? other) {
return other is Reaction &&
Expand Down
49 changes: 41 additions & 8 deletions lib/src/widgets/reaction_button.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_reaction_button/flutter_reaction_button.dart';
import 'package:flutter_reaction_button/src/enums/reaction.dart';
import 'package:flutter_reaction_button/src/extensions/key.dart';
Expand Down Expand Up @@ -88,8 +89,7 @@ class ReactionButton<T> extends StatefulWidget {
class _ReactionButtonState<T> extends State<ReactionButton<T>> {
final GlobalKey _globalKey = GlobalKey();

late Reaction<T>? _selectedReaction =
_isChecked ? widget.selectedReaction : widget.placeholder;
late Reaction<T>? _selectedReaction = _isChecked ? widget.selectedReaction : widget.placeholder;

late bool _isChecked = widget.isChecked;

Expand All @@ -108,9 +108,7 @@ class _ReactionButtonState<T> extends State<ReactionButton<T>> {
void _onCheck() {
_isChecked = !_isChecked;
_updateReaction(
_isChecked
? widget.selectedReaction ?? widget.reactions.first
: widget.placeholder,
_isChecked ? widget.selectedReaction ?? widget.reactions.first : widget.placeholder,
);
}

Expand All @@ -131,6 +129,7 @@ class _ReactionButtonState<T> extends State<ReactionButton<T>> {
itemScaleDuration: widget.itemAnimationDuration,
animateBox: widget.animateBox,
direction: widget.direction,
reactionHighlightNotifier: reactionHighlightNotifier,
onReactionSelected: (reaction) {
_updateReaction(reaction);
_disposeOverlayEntry();
Expand Down Expand Up @@ -158,14 +157,48 @@ class _ReactionButtonState<T> extends State<ReactionButton<T>> {
super.dispose();
}

@override
void initState() {
super.initState();
reactionWidth = widget.itemSize.width.toInt() + widget.itemsSpacing.toInt();
reactionPositions = List.generate(widget.reactions.length, (index) => index + 1);
}

int reactionWidth = 0;

List<int> reactionPositions = [];

ValueNotifier<Reaction<T>?> reactionHighlightNotifier = ValueNotifier(null);

@override
Widget build(BuildContext context) {
final Widget? child = _isContainer
? widget.child
: (_selectedReaction ?? widget.reactions.first)!.icon;
final Widget? child =
_isContainer ? widget.child : (_selectedReaction ?? widget.reactions.first)!.icon;

return GestureDetector(
key: _globalKey,
onLongPressMoveUpdate: (details) {
debugPrint('onLongPressMoveUpdate global: ${details.globalPosition}');

final reactionPositiondx = details.globalPosition.dx;
final postition = reactionPositiondx ~/ reactionWidth;
if (postition > 0 && postition < widget.reactions.length + 1) {
reactionHighlightNotifier.value = widget.reactions[postition - 1];
debugPrint('reactionHighlight: $postition');
}
},
onLongPressUp: () {
final currentReaction = reactionHighlightNotifier.value;
if (currentReaction != null) {
reactionHighlightNotifier.value = Reaction(
value: currentReaction.value,
icon: currentReaction.icon,
previewIcon: currentReaction.icon,
title: currentReaction.title,
isSelected: true,
);
}
},
onTap: () {
if (widget.toggle) {
_onCheck();
Expand Down
15 changes: 8 additions & 7 deletions lib/src/widgets/reactions_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ReactionsBox<T> extends StatefulWidget {
required this.itemScale,
required this.itemScaleDuration,
required this.onReactionSelected,
required this.reactionHighlightNotifier,
required this.onClose,
required this.animateBox,
this.direction = ReactionsBoxAlignment.ltr,
Expand All @@ -28,6 +29,8 @@ class ReactionsBox<T> extends StatefulWidget {

final Size itemSize;

final ValueNotifier<Reaction<T>?> reactionHighlightNotifier;

final List<Reaction<T>?> reactions;

final Color color;
Expand Down Expand Up @@ -58,8 +61,7 @@ class ReactionsBox<T> extends StatefulWidget {
State<ReactionsBox<T>> createState() => _ReactionsBoxState<T>();
}

class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
with SingleTickerProviderStateMixin {
class _ReactionsBoxState<T> extends State<ReactionsBox<T>> with SingleTickerProviderStateMixin {
final PositionNotifier _positionNotifier = PositionNotifier();

late final AnimationController _boxAnimationController = AnimationController(
Expand All @@ -80,8 +82,7 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
? widget.offset.dx + _boxWidth > MediaQuery.sizeOf(context).width
: widget.offset.dx - _boxWidth < 0;

bool get _shouldStartFromBottom =>
widget.offset.dy - _boxHeight - widget.boxPadding.vertical < 0;
bool get _shouldStartFromBottom => widget.offset.dy - _boxHeight - widget.boxPadding.vertical < 0;

void _checkIsOffsetOutsideBox(Offset offset) {
final Rect boxRect = Rect.fromLTWH(0, 0, _boxWidth, _boxHeight);
Expand Down Expand Up @@ -111,8 +112,7 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
valueListenable: _positionNotifier,
builder: (context, fingerPosition, child) {
final bool isBoxHovered = fingerPosition?.isBoxHovered ?? false;
final double boxScale =
1 - (widget.itemScale / widget.reactions.length);
final double boxScale = 1 - (widget.itemScale / widget.reactions.length);

final double widthOverflow;
if (_isWidthOverflow) {
Expand Down Expand Up @@ -213,6 +213,7 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
child: ReactionsBoxItem<T>(
index: index,
fingerPositionNotifier: _positionNotifier,
reactionHighlightNotifier: widget.reactionHighlightNotifier,
reaction: widget.reactions[index]!,
size: widget.itemSize,
scale: widget.itemScale,
Expand All @@ -230,4 +231,4 @@ class _ReactionsBoxState<T> extends State<ReactionsBox<T>>
),
);
}
}
}
28 changes: 19 additions & 9 deletions lib/src/widgets/reactions_box_item.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_reaction_button/flutter_reaction_button.dart';
import 'package:flutter_reaction_button/src/common/position_notifier.dart';
import 'package:flutter/services.dart';

class ReactionsBoxItem<T> extends StatefulWidget {
const ReactionsBoxItem({
super.key,
required this.reaction,
required this.onReactionSelected,
required this.reactionHighlightNotifier,
required this.scale,
required this.index,
required this.size,
Expand All @@ -19,6 +21,8 @@ class ReactionsBoxItem<T> extends StatefulWidget {

final ValueChanged<Reaction<T>?> onReactionSelected;

final ValueNotifier<Reaction<T>?> reactionHighlightNotifier;

final Duration animationDuration;

final PositionNotifier fingerPositionNotifier;
Expand Down Expand Up @@ -46,8 +50,7 @@ class _ReactionsBoxItemState<T> extends State<ReactionsBoxItem<T>>

void _listener() {
final Offset fingerOffset = widget.fingerPositionNotifier.value.offset;
final Offset topLeft =
Offset((widget.size.width + widget.space) * widget.index, 0);
final Offset topLeft = Offset((widget.size.width + widget.space) * widget.index, 0);
final Offset bottomRight = Offset(
(widget.size.width + widget.space) * (widget.index + 1),
widget.size.height,
Expand All @@ -56,8 +59,7 @@ class _ReactionsBoxItemState<T> extends State<ReactionsBoxItem<T>>
final bool selected = rect.contains(fingerOffset);

if (selected) {
final bool isBoxHovered =
widget.fingerPositionNotifier.value.isBoxHovered;
final bool isBoxHovered = widget.fingerPositionNotifier.value.isBoxHovered;
if (!isBoxHovered) {
widget.onReactionSelected(widget.reaction);
}
Expand All @@ -71,6 +73,16 @@ class _ReactionsBoxItemState<T> extends State<ReactionsBoxItem<T>>
void initState() {
super.initState();
widget.fingerPositionNotifier.addListener(_listener);
widget.reactionHighlightNotifier.addListener(() {
if (widget.reactionHighlightNotifier.value == widget.reaction) {
_animationController.forward();
HapticFeedback.lightImpact();
} else if (widget.reactionHighlightNotifier.value?.isSelected == true) {
widget.onReactionSelected(widget.reactionHighlightNotifier.value);
} else {
_animationController.reverse();
}
});
}

@override
Expand All @@ -85,9 +97,8 @@ class _ReactionsBoxItemState<T> extends State<ReactionsBoxItem<T>>
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
final bool showTitle =
_animationController.value == _animationController.upperBound &&
widget.reaction.title != null;
final bool showTitle = _animationController.value == _animationController.upperBound &&
widget.reaction.title != null;

return Stack(
clipBehavior: Clip.none,
Expand All @@ -102,8 +113,7 @@ class _ReactionsBoxItemState<T> extends State<ReactionsBoxItem<T>>
),
if (widget.reaction.title != null) ...{
Positioned(
bottom:
widget.size.height * (1 + (_animationController.value / 2)),
bottom: widget.size.height * (1 + (_animationController.value / 2)),
child: AnimatedOpacity(
opacity: showTitle ? 1 : 0,
duration: widget.animationDuration,
Expand Down

0 comments on commit 36a8964

Please sign in to comment.