import 'package:flutter/material.dart'; import '../utils/Colors.dart'; import 'FloatingNav.dart'; import 'GlassSurface.dart'; class FloatingNavState extends State { static const double _size = 60; /// Neighbours overlap by this much, as in the reference. static const double _overlap = 6; int _pressed = -1; void _onPressDown(int index) { setState(() { _pressed = index; }); } void _onPressEnd() { setState(() { _pressed = -1; }); } @override Widget build(BuildContext context) { final int count = widget.entries.length; const double step = _size - _overlap; // The active disc is painted last so it sits in front of both neighbours. final List order = [ for (int index = 0; index < count; index++) if (index != widget.selected) index, if (widget.selected >= 0 && widget.selected < count) widget.selected, ]; return SafeArea( top: false, child: Padding( padding: const EdgeInsets.only(bottom: 18), child: Center( child: SizedBox( width: step * (count - 1) + _size, height: _size + 8, child: Stack( clipBehavior: Clip.none, children: [ for (int index in order) AnimatedPositioned( duration: const Duration(milliseconds: 260), curve: Curves.easeOutCubic, left: step * index, top: index == widget.selected ? 0 : 4, child: _button(index), ), ], ), ), ), ), ); } Widget _button(int index) { final NavEntry entry = widget.entries[index]; final bool active = index == widget.selected; final double scale = _pressed == index ? 0.9 : (active ? 1.06 : 1.0); return Semantics( button: true, selected: active, label: entry.label, child: GestureDetector( onTap: entry.onTap, onTapDown: (_) => _onPressDown(index), onTapUp: (_) => _onPressEnd(), onTapCancel: _onPressEnd, behavior: HitTestBehavior.opaque, child: AnimatedScale( scale: scale, duration: Duration(milliseconds: _pressed == index ? 90 : 260), curve: active ? Curves.easeOutBack : Curves.easeOutCubic, child: AnimatedContainer( duration: const Duration(milliseconds: 260), curve: Curves.easeOutCubic, width: _size, height: _size, decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: active ? const [ BoxShadow( color: Color(0x2E000000), blurRadius: 20, offset: Offset(0, 8), ), ] : const [ BoxShadow( color: Color(0x14000000), blurRadius: 14, offset: Offset(0, 6), ), ], ), child: GlassSurface( radius: const BorderRadius.all(Radius.circular(_size)), tint: active ? colorWhite : colorGlass, child: Container( alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: colorWhite.withValues(alpha: active ? 1 : 0.85), width: 1.2), ), child: TweenAnimationBuilder( tween: ColorTween(end: active ? colorInk : colorInkSoft), duration: const Duration(milliseconds: 260), builder: (context, colour, child) => Icon(entry.icon, size: 23, color: colour), ), ), ), ), ), ), ); } }