diff --git a/lib/src/models/reaction.dart b/lib/src/models/reaction.dart index 7d44f7e..b9cb187 100644 --- a/lib/src/models/reaction.dart +++ b/lib/src/models/reaction.dart @@ -6,6 +6,7 @@ class Reaction { required this.icon, Widget? previewIcon, this.title, + this.isSelected = false, }) : previewIcon = previewIcon ?? icon; /// Widget showing as button after selecting preview Icon from box appear. @@ -23,6 +24,8 @@ class Reaction { final T? value; + final bool isSelected; + @override bool operator ==(Object? other) { return other is Reaction && diff --git a/lib/src/widgets/reaction_button.dart b/lib/src/widgets/reaction_button.dart index b978ad1..67f561d 100644 --- a/lib/src/widgets/reaction_button.dart +++ b/lib/src/widgets/reaction_button.dart @@ -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'; @@ -88,8 +89,7 @@ class ReactionButton extends StatefulWidget { class _ReactionButtonState extends State> { final GlobalKey _globalKey = GlobalKey(); - late Reaction? _selectedReaction = - _isChecked ? widget.selectedReaction : widget.placeholder; + late Reaction? _selectedReaction = _isChecked ? widget.selectedReaction : widget.placeholder; late bool _isChecked = widget.isChecked; @@ -108,9 +108,7 @@ class _ReactionButtonState extends State> { void _onCheck() { _isChecked = !_isChecked; _updateReaction( - _isChecked - ? widget.selectedReaction ?? widget.reactions.first - : widget.placeholder, + _isChecked ? widget.selectedReaction ?? widget.reactions.first : widget.placeholder, ); } @@ -131,6 +129,7 @@ class _ReactionButtonState extends State> { itemScaleDuration: widget.itemAnimationDuration, animateBox: widget.animateBox, direction: widget.direction, + reactionHighlightNotifier: reactionHighlightNotifier, onReactionSelected: (reaction) { _updateReaction(reaction); _disposeOverlayEntry(); @@ -158,14 +157,48 @@ class _ReactionButtonState extends State> { 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 reactionPositions = []; + + ValueNotifier?> 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(); diff --git a/lib/src/widgets/reactions_box.dart b/lib/src/widgets/reactions_box.dart index dbedac4..dd54d5d 100644 --- a/lib/src/widgets/reactions_box.dart +++ b/lib/src/widgets/reactions_box.dart @@ -19,6 +19,7 @@ class ReactionsBox extends StatefulWidget { required this.itemScale, required this.itemScaleDuration, required this.onReactionSelected, + required this.reactionHighlightNotifier, required this.onClose, required this.animateBox, this.direction = ReactionsBoxAlignment.ltr, @@ -28,6 +29,8 @@ class ReactionsBox extends StatefulWidget { final Size itemSize; + final ValueNotifier?> reactionHighlightNotifier; + final List?> reactions; final Color color; @@ -58,8 +61,7 @@ class ReactionsBox extends StatefulWidget { State> createState() => _ReactionsBoxState(); } -class _ReactionsBoxState extends State> - with SingleTickerProviderStateMixin { +class _ReactionsBoxState extends State> with SingleTickerProviderStateMixin { final PositionNotifier _positionNotifier = PositionNotifier(); late final AnimationController _boxAnimationController = AnimationController( @@ -80,8 +82,7 @@ class _ReactionsBoxState extends State> ? 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); @@ -111,8 +112,7 @@ class _ReactionsBoxState extends State> 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) { @@ -213,6 +213,7 @@ class _ReactionsBoxState extends State> child: ReactionsBoxItem( index: index, fingerPositionNotifier: _positionNotifier, + reactionHighlightNotifier: widget.reactionHighlightNotifier, reaction: widget.reactions[index]!, size: widget.itemSize, scale: widget.itemScale, @@ -230,4 +231,4 @@ class _ReactionsBoxState extends State> ), ); } -} +} \ No newline at end of file diff --git a/lib/src/widgets/reactions_box_item.dart b/lib/src/widgets/reactions_box_item.dart index 40adca2..98409a7 100644 --- a/lib/src/widgets/reactions_box_item.dart +++ b/lib/src/widgets/reactions_box_item.dart @@ -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 extends StatefulWidget { const ReactionsBoxItem({ super.key, required this.reaction, required this.onReactionSelected, + required this.reactionHighlightNotifier, required this.scale, required this.index, required this.size, @@ -19,6 +21,8 @@ class ReactionsBoxItem extends StatefulWidget { final ValueChanged?> onReactionSelected; + final ValueNotifier?> reactionHighlightNotifier; + final Duration animationDuration; final PositionNotifier fingerPositionNotifier; @@ -46,8 +50,7 @@ class _ReactionsBoxItemState extends State> 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, @@ -56,8 +59,7 @@ class _ReactionsBoxItemState extends State> 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); } @@ -71,6 +73,16 @@ class _ReactionsBoxItemState extends State> 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 @@ -85,9 +97,8 @@ class _ReactionsBoxItemState extends State> 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, @@ -102,8 +113,7 @@ class _ReactionsBoxItemState extends State> ), 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,