import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/message_service.dart'; import '../pages/full_screen.dart'; import 'floating_icon_with_wave.dart'; import 'package:basic_intl/intl.dart'; import '../utils/assets_util.dart'; class ChatFloatingIcon extends StatefulWidget { final double? iconSize; final Widget? icon; final VoidCallback? onTap; final VoidCallback? onLongPress; final VoidCallback? onLongPressUp; final ValueChanged? onLongPressEnd; final VoidCallback? onDragStart; final VoidCallback? onDragEnd; final ValueChanged? onPositionChanged; const ChatFloatingIcon({ super.key, this.iconSize, this.icon, this.onTap, this.onLongPress, this.onLongPressUp, this.onLongPressEnd, this.onDragStart, this.onDragEnd, this.onPositionChanged, }); @override State createState() => _ChatFloatingIconState(); } class _ChatFloatingIconState extends State with TickerProviderStateMixin { double iconSize = 0.0; Offset _position = const Offset(10, 120); bool _isDragging = false; bool _isAnimating = false; Offset _targetPosition = const Offset(10, 120); // 水波纹动画控制器 late AnimationController _waveAnimationController; // 图片切换定时器 Timer? _imageTimer; // 添加长按状态标记 bool _isLongPressing = false; // 图片切换相关 int _imageIndex = 0; late final List _iconImages = [ 'ai1_hd.png', 'ai0_hd.png', ]; late final List _iconImagesEn = [ 'ai1_hd_en.png', 'ai0_hd_en.png', ]; @override void initState() { super.initState(); iconSize = widget.iconSize ?? 80.0; _waveAnimationController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); // 如果没有提供自定义图标,则使用默认的图片切换逻辑 if (widget.icon == null) { _imageTimer = Timer.periodic(const Duration(seconds: 3), (timer) { if (mounted && !_isDragging) { setState(() { _imageIndex = (_imageIndex + 1) % _iconImages.length; }); } }); } } @override void dispose() { _waveAnimationController.dispose(); _imageTimer?.cancel(); super.dispose(); } // 更新位置 void _updatePosition(Offset delta) { if (!_isDragging) return; setState(() { final screenSize = MediaQuery.of(context).size; double newX = (_position.dx - delta.dx).clamp(0.0, screenSize.width - iconSize); double newY = (_position.dy - delta.dy).clamp(0.0, screenSize.height - iconSize); _position = Offset(newX, newY); }); // 通知父组件位置变化 widget.onPositionChanged?.call(_position); } // 吸附到屏幕边缘 void _snapToEdge() { final screenSize = MediaQuery.of(context).size; final centerX = screenSize.width / 2; // 判断应该吸到左边还是右边 final shouldSnapToLeft = _position.dx < centerX - iconSize / 2; final targetX = shouldSnapToLeft ? 0.0 : screenSize.width - iconSize; setState(() { _targetPosition = Offset(targetX, _position.dy); _isAnimating = true; }); // 使用定时器在动画完成后更新状态 Timer(const Duration(milliseconds: 100), () { if (mounted) { setState(() { _position = _targetPosition; _isAnimating = false; }); // 通知父组件位置变化 widget.onPositionChanged?.call(_position); } }); } void _onPanStart(DragStartDetails details) { _isDragging = true; _waveAnimationController.stop(); widget.onDragStart?.call(); } void _onPanEnd(DragEndDetails details) { _isDragging = false; _snapToEdge(); widget.onDragEnd?.call(); } // 显示全屏界面 void _showFullScreen() async { final messageService = MessageService.instance; await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChangeNotifierProvider.value( value: messageService, child: const FullScreenPage(), ), ), ); } Widget _buildIconContent(MessageService messageService) { // 如果提供了自定义图标,优先使用 if (widget.icon != null) { return widget.icon!; } // 使用默认的图标逻辑 if (messageService.isRecording) { return FloatingIconWithWave( animationController: _waveAnimationController, iconSize: iconSize, waveColor: Colors.white, ); } else { return AssetsUtil.getImageWidget( Intl.getCurrentLocale().startsWith('zh') ? _iconImages[_imageIndex] : _iconImagesEn[_imageIndex], width: iconSize, height: iconSize, ); } } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: MessageService.instance, child: AnimatedPositioned( duration: const Duration(milliseconds: 250), curve: Curves.easeOutBack, bottom: _isAnimating ? _targetPosition.dy : _position.dy, right: _isAnimating ? _targetPosition.dx : _position.dx, child: Consumer( builder: (context, messageService, child) { return GestureDetector( onTap: widget.onTap ?? _showFullScreen, onLongPress: () async { _isLongPressing = true; _waveAnimationController.repeat(); if (widget.onLongPress != null) { widget.onLongPress!(); } else { await messageService.startVoiceInput(); } }, onLongPressUp: () async { _isLongPressing = false; _waveAnimationController.stop(); if (widget.onLongPressUp != null) { widget.onLongPressUp!(); } else { await messageService.stopAndProcessVoiceInput(); } }, onLongPressEnd: (details) { _isLongPressing = false; widget.onLongPressEnd?.call(details); }, onPanStart: (details) { if (!_isLongPressing) { _onPanStart(details); } }, onPanUpdate: (details) { if (_isDragging && !_isLongPressing) { _updatePosition(details.delta); } }, onPanEnd: (details) { if (!_isLongPressing) { _onPanEnd(details); } }, child: AnimatedScale( scale: _isDragging ? 1.1 : 1.0, duration: const Duration(milliseconds: 150), child: _buildIconContent(messageService), ), ); }, ), ), ); } }