import 'dart:developer'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../utils/bot_style.dart'; import 'it_shimmer.dart'; class ChoicesArray extends StatefulWidget { final bool isLoading; final List? choices; final void Function(int) onPressed; final void Function(int)? onLongPress; final int? selectedChoiceIndex; final String originalSpan; final String Function(int) uniqueKeyForLayerLink; /// some uses of this widget want to disable the choices final bool isActive; const ChoicesArray({ super.key, required this.isLoading, required this.choices, required this.onPressed, required this.originalSpan, required this.uniqueKeyForLayerLink, required this.selectedChoiceIndex, this.isActive = true, this.onLongPress, }); @override ChoicesArrayState createState() => ChoicesArrayState(); } class ChoicesArrayState extends State { bool interactionDisabled = false; void disableInteraction() { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { interactionDisabled = true; }); }); } void enableInteractions() { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { interactionDisabled = false; }); }); } @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); return widget.isLoading && (widget.choices == null || widget.choices!.length <= 1) ? ItShimmer(originalSpan: widget.originalSpan) : Wrap( alignment: WrapAlignment.center, children: widget.choices ?.asMap() .entries .map( (entry) => ChoiceItem( theme: theme, onLongPress: widget.isActive ? widget.onLongPress : null, onPressed: widget.isActive ? widget.onPressed : (_) {}, entry: entry, interactionDisabled: interactionDisabled, enableInteraction: enableInteractions, disableInteraction: disableInteraction, isSelected: widget.selectedChoiceIndex == entry.key, ), ) .toList() ?? [], ); } } class Choice { Choice({ this.color, required this.text, this.isGold = false, }); final Color? color; final String text; final bool isGold; } class ChoiceItem extends StatelessWidget { const ChoiceItem({ super.key, required this.theme, required this.onLongPress, required this.onPressed, required this.entry, required this.isSelected, required this.interactionDisabled, required this.enableInteraction, required this.disableInteraction, }); final MapEntry entry; final ThemeData theme; final void Function(int p1)? onLongPress; final void Function(int p1) onPressed; final bool isSelected; final bool interactionDisabled; final VoidCallback enableInteraction; final VoidCallback disableInteraction; @override Widget build(BuildContext context) { try { return Tooltip( message: onLongPress != null ? L10n.of(context)!.holdForInfo : "", waitDuration: onLongPress != null ? const Duration(milliseconds: 500) : const Duration(days: 1), child: ChoiceAnimationWidget( key: ValueKey(entry.value.text), selected: entry.value.color != null, isGold: entry.value.isGold, enableInteraction: enableInteraction, disableInteraction: disableInteraction, child: Container( margin: const EdgeInsets.all(2), padding: EdgeInsets.zero, decoration: isSelected ? BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), border: Border.all( color: entry.value.color ?? theme.colorScheme.primary, style: BorderStyle.solid, width: 2.0, ), ) : null, child: TextButton( style: ButtonStyle( padding: WidgetStateProperty.all( const EdgeInsets.symmetric(horizontal: 7), ), //if index is selected, then give the background a slight primary color backgroundColor: WidgetStateProperty.all( entry.value.color != null ? entry.value.color!.withOpacity(0.2) : theme.colorScheme.primary.withOpacity(0.1), ), textStyle: WidgetStateProperty.all( BotStyle.text(context), ), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), ), onLongPress: onLongPress != null && !interactionDisabled ? () => onLongPress!(entry.key) : null, onPressed: interactionDisabled ? null : () => onPressed(entry.key), child: Text( entry.value.text, style: BotStyle.text(context), ), ), ), ), ); } catch (e) { debugger(when: kDebugMode); return Container(); } } } class ChoiceAnimationWidget extends StatefulWidget { final Widget child; final bool selected; final bool isGold; final VoidCallback enableInteraction; final VoidCallback disableInteraction; const ChoiceAnimationWidget({ super.key, required this.child, required this.selected, required this.enableInteraction, required this.disableInteraction, this.isGold = false, }); @override ChoiceAnimationWidgetState createState() => ChoiceAnimationWidgetState(); } enum AnimationState { ready, forward, reverse, finished } class ChoiceAnimationWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _animation; AnimationState animationState = AnimationState.ready; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _animation = widget.isGold ? Tween(begin: 1.0, end: 1.2).animate(_controller) : TweenSequence([ TweenSequenceItem( tween: Tween(begin: 0, end: -8 * pi / 180), weight: 1.0, ), TweenSequenceItem( tween: Tween(begin: -8 * pi / 180, end: 16 * pi / 180), weight: 2.0, ), TweenSequenceItem( tween: Tween(begin: 16 * pi / 180, end: 0), weight: 1.0, ), ]).animate(_controller); widget.enableInteraction(); if (widget.selected && animationState == AnimationState.ready) { widget.disableInteraction(); _controller.forward(); setState(() { animationState = AnimationState.forward; }); } _controller.addStatusListener((status) { if (status == AnimationStatus.completed && animationState == AnimationState.forward) { _controller.reverse(); setState(() { animationState = AnimationState.reverse; }); } if (status == AnimationStatus.dismissed && animationState == AnimationState.reverse) { widget.enableInteraction(); setState(() { animationState = AnimationState.finished; }); } }); } @override void didUpdateWidget(ChoiceAnimationWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.selected && animationState == AnimationState.ready) { widget.disableInteraction(); _controller.forward(); setState(() { animationState = AnimationState.forward; }); } } @override Widget build(BuildContext context) { return widget.isGold ? AnimatedBuilder( key: UniqueKey(), animation: _animation, builder: (context, child) { return Transform.scale( scale: _animation.value, child: child, ); }, child: widget.child, ) : AnimatedBuilder( key: UniqueKey(), animation: _animation, builder: (context, child) { return Transform.rotate( angle: _animation.value, child: child, ); }, child: widget.child, ); } @override void dispose() { _controller.dispose(); super.dispose(); } }