// ignore_for_file: deprecated_member_use import 'package:didvan/services/ai_rag_service.dart'; import 'package:didvan/services/ai_voice_service.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:record/record.dart'; import 'package:path_provider/path_provider.dart'; import 'package:just_audio/just_audio.dart'; import 'dart:ui'; import 'dart:io'; import 'dart:math' as math; class AiVoiceChatDialog extends StatefulWidget { const AiVoiceChatDialog({super.key}); @override State createState() => _AiVoiceChatDialogState(); } class _AiVoiceChatDialogState extends State with TickerProviderStateMixin { bool _isRecording = false; bool _isPreparing = false; bool _isProcessing = false; bool _isAiSpeaking = false; String _statusText = 'برای شروع مکالمه، دکمه میکروفون را نگه دارید'; late AnimationController _waveController; late AnimationController _pulseController; late AnimationController _glowController; late AnimationController _particleController; late AnimationController _preparingController; final AudioRecorder _audioRecorder = AudioRecorder(); final AudioPlayer _audioPlayer = AudioPlayer(); String? _recordingPath; final List _audioWaveHeights = List.generate(40, (_) => 0.3); int _currentWaveIndex = 0; final List _thinkingMessages = [ 'در حال فکر کردن...', 'در حال بررسی اطلاعات...', 'در حال تحلیل سوال شما...', 'در حال جستجو در دانش...', 'در حال پردازش درخواست...', 'در حال یافتن بهترین پاسخ...', 'در حال بررسی جزئیات...', 'در حال تحلیل داده‌های مرتبط...', 'در حال مقایسه‌ی نتایج ممکن...', 'در حال جمع‌آوری اطلاعات موردنیاز...', 'در حال تشخیص الگوها...' ]; final List _analyzingMessages = [ 'در حال تجزیه و تحلیل...', 'در حال پردازش داده‌ها...', 'در حال بررسی محتوا...', 'در حال ترکیب اطلاعات...', 'در حال درک منظور شما...', 'در حال آماده‌سازی پاسخ...', 'در حال محاسبه‌ی بهترین گزینه...', 'در حال درک مفهوم پرسش شما...', 'در حال به‌روزرسانی اطلاعات...', 'در حال استخراج پاسخ مناسب...', 'در حال هماهنگ‌سازی با پایگاه دانش...' ]; int _currentThinkingIndex = 0; int _currentAnalyzingIndex = 0; @override void initState() { super.initState(); _waveController = AnimationController( vsync: this, duration: const Duration(milliseconds: 100), )..addListener(() { if (_isRecording || _isAiSpeaking) { setState(() { _currentWaveIndex = (_currentWaveIndex + 1) % _audioWaveHeights.length; for (int i = 0; i < _audioWaveHeights.length; i++) { _audioWaveHeights[i] = 0.3 + (math.Random().nextDouble() * 0.7); } }); } }); _pulseController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1500), )..repeat(reverse: true); _glowController = AnimationController( vsync: this, duration: const Duration(milliseconds: 2000), )..repeat(reverse: true); _particleController = AnimationController( vsync: this, duration: const Duration(seconds: 3), )..repeat(); _preparingController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), )..repeat(reverse: true); _audioPlayer.playerStateStream.listen((state) { if (mounted) { setState(() { _isAiSpeaking = state.playing; if (state.processingState == ProcessingState.completed) { _isAiSpeaking = false; _statusText = 'برای ادامه مکالمه، دکمه میکروفون را نگه دارید'; _waveController.stop(); } else if (state.playing) { _statusText = 'دستیار در حال پاسخ دادن است...'; _waveController.repeat(); } }); } }); } @override void dispose() { _waveController.dispose(); _pulseController.dispose(); _glowController.dispose(); _particleController.dispose(); _preparingController.dispose(); _audioRecorder.dispose(); _audioPlayer.dispose(); super.dispose(); } Future _startRecording() async { try { if (await _audioRecorder.hasPermission()) { if (_isAiSpeaking) { await _audioPlayer.stop(); setState(() { _isAiSpeaking = false; }); } setState(() { _isPreparing = true; _statusText = '⏳ در حال آماده سازی...'; }); _preparingController.repeat(reverse: true); final directory = await getTemporaryDirectory(); final timestamp = DateTime.now().millisecondsSinceEpoch; _recordingPath = '${directory.path}/voice_$timestamp.m4a'; await _audioRecorder.start( const RecordConfig( encoder: AudioEncoder.aacLc, ), path: _recordingPath!, ); await Future.delayed(const Duration(seconds: 2)); if (mounted && _isPreparing) { setState(() { _isPreparing = false; _isRecording = true; _statusText = '🎙️ در حال گوش دادن...'; }); _preparingController.stop(); _waveController.repeat(); } } } catch (e) { setState(() { _isPreparing = false; _isRecording = false; _statusText = 'خطا در شروع ضبط صدا'; }); _preparingController.stop(); _waveController.stop(); debugPrint('Error starting recording: $e'); } } Future _stopRecording() async { if (!_isRecording) return; try { setState(() { _isRecording = false; _isProcessing = true; _currentThinkingIndex = 0; _statusText = _thinkingMessages[_currentThinkingIndex]; }); _waveController.stop(); _startThinkingMessageRotation(); await Future.delayed(const Duration(seconds: 2)); if (!mounted) return; final path = await _audioRecorder.stop(); if (path != null) { final response = await AiVoiceService.uploadVoice(path); if (response.isSuccess && response.text.isNotEmpty) { setState(() { _currentAnalyzingIndex = 0; _statusText = _analyzingMessages[_currentAnalyzingIndex]; }); _startAnalyzingMessageRotation(); final ragResponse = await AiRagService.sendMessage(response.text); if (ragResponse.audioUrl != null && ragResponse.audioUrl!.isNotEmpty) { setState(() { _statusText = '🔊 دستیار در حال پاسخ دادن است...'; _isProcessing = false; _isAiSpeaking = true; }); await _audioPlayer.setUrl(ragResponse.audioUrl!); await _audioPlayer.play(); _waveController.repeat(); } else { setState(() { _statusText = 'خطا در پردازش'; _isProcessing = false; }); } } else { setState(() { _statusText = 'خطا در پردازش پیام صوتی'; _isProcessing = false; }); } try { await File(path).delete(); } catch (e) { debugPrint('Error deleting temp file: $e'); } } else { setState(() { _isProcessing = false; _statusText = 'خطا در ضبط صدا'; }); } } catch (e) { setState(() { _isRecording = false; _isProcessing = false; _statusText = 'خطا در پردازش: ${e.toString()}'; }); _waveController.stop(); debugPrint('Error stopping recording: $e'); } } void _startThinkingMessageRotation() { Future.delayed(const Duration(seconds: 10), () { if (mounted && _isProcessing && !_isAiSpeaking) { setState(() { _currentThinkingIndex = (_currentThinkingIndex + 1) % _thinkingMessages.length; _statusText = _thinkingMessages[_currentThinkingIndex]; }); _startThinkingMessageRotation(); } }); } void _startAnalyzingMessageRotation() { Future.delayed(const Duration(seconds: 10), () { if (mounted && _isProcessing && !_isAiSpeaking) { setState(() { _currentAnalyzingIndex = (_currentAnalyzingIndex + 1) % _analyzingMessages.length; _statusText = _analyzingMessages[_currentAnalyzingIndex]; }); _startAnalyzingMessageRotation(); } }); } Color _getMainColor() { if (_isPreparing) return const Color(0xFFFFA500); if (_isRecording) return const Color.fromARGB(255, 178, 4, 54); if (_isAiSpeaking) return const Color(0xFF00AAFF); if (_isProcessing) return const Color(0xFFFFAA00); return const Color(0xFF6B7280); } @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ const Color(0xFF0A0E27).withOpacity(0.95), const Color(0xFF1A1F3A).withOpacity(0.95), ], ), borderRadius: BorderRadius.circular(32), border: Border.all( color: Colors.white.withOpacity(0.1), width: 1.5, ), boxShadow: [ BoxShadow( color: _getMainColor().withOpacity(0.3), blurRadius: 40, spreadRadius: 0, ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(32), child: Stack( children: [ _buildAnimatedBackground(), Column( children: [ _buildHeader(), Expanded( child: _buildMainContent(), ), _buildControls(), ], ), ], ), ), ), ), ); } Widget _buildAnimatedBackground() { return AnimatedBuilder( animation: _particleController, builder: (context, child) { return CustomPaint( painter: ParticlePainter( animation: _particleController, color: _getMainColor(), ), size: Size.infinite, ); }, ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.white.withOpacity(0.05), Colors.transparent, ], ), ), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( gradient: LinearGradient( colors: [_getMainColor(), _getMainColor().withOpacity(0.6)], ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: _getMainColor().withOpacity(0.5), blurRadius: 20, spreadRadius: 2, ), ], ), child: Icon( _isRecording ? Icons.mic_rounded : _isAiSpeaking ? Icons.volume_up_rounded : Icons.headset_mic_rounded, color: Colors.white, size: 24, ), ), const SizedBox(width: 16), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanText( 'گفتگوی صوتی', fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), SizedBox(height: 4), DidvanText( 'دستیار هوشمند دیدوان', fontSize: 12, color: Colors.white60, ), ], ), ), IconButton( icon: const Icon(Icons.close_rounded, color: Colors.white70), onPressed: () => Navigator.pop(context), ), ], ), ); } Widget _buildMainContent() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildVisualization(), const SizedBox(height: 40), _buildStatusText(), ], ), ); } Widget _buildVisualization() { return AnimatedBuilder( animation: Listenable.merge( [_pulseController, _glowController, _preparingController]), builder: (context, child) { final pulseValue = _pulseController.value; final glowValue = _glowController.value; final preparingValue = _preparingController.value; return Stack( alignment: Alignment.center, children: [ if (_isPreparing) ...[ for (int i = 0; i < 4; i++) Transform.scale( scale: 1.0 + (preparingValue * 0.3) + (i * 0.15), child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: const Color(0xFFFFA500) .withOpacity(0.4 - (i * 0.1)), width: 3, ), ), ), ), ], if (!_isPreparing) for (int i = 0; i < 3; i++) Container( width: 280 + (i * 40) + (pulseValue * 20), height: 280 + (i * 40) + (pulseValue * 20), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: _getMainColor().withOpacity(0.1 - (i * 0.03)), width: 2, ), ), ), Container( width: 260 + (glowValue * 20), height: 260 + (glowValue * 20), decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ _getMainColor().withOpacity(0.3), _getMainColor().withOpacity(0.1), Colors.transparent, ], ), ), ), if (_isPreparing) SizedBox( width: 240, height: 240, child: CustomPaint( painter: PreparingSpinnerPainter( animation: preparingValue, color: const Color(0xFFFFA500), ), ), ) else SizedBox( width: 240, height: 240, child: CustomPaint( painter: WaveVisualizerPainter( waveHeights: _audioWaveHeights, color: _getMainColor(), isActive: _isRecording || _isAiSpeaking, ), ), ), Container( width: 120, height: 120, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ _getMainColor(), _getMainColor().withOpacity(0.7), ], ), boxShadow: [ BoxShadow( color: _getMainColor().withOpacity(0.6), blurRadius: 30, spreadRadius: 5, ), ], ), child: Icon( _isPreparing ? Icons.settings_rounded : _isRecording ? Icons.mic_rounded : _isAiSpeaking ? Icons.graphic_eq_rounded : _isProcessing ? Icons.hourglass_empty_rounded : Icons.headphones_rounded, color: Colors.white, size: 50, ), ), ], ); }, ); } Widget _buildStatusText() { return Container( margin: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(20), border: Border.all( color: _getMainColor().withOpacity(0.3), width: 1, ), ), child: DidvanText( _statusText, fontSize: 14, color: Colors.white, textAlign: TextAlign.center, ), ); } Widget _buildControls() { return Container( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (_isAiSpeaking) const Padding( padding: EdgeInsets.only(bottom: 16), child: DidvanText( '', fontSize: 12, color: Colors.white70, textAlign: TextAlign.center, ), ), GestureDetector( onLongPressStart: (_) { if (!_isProcessing && !_isRecording && !_isPreparing) { _startRecording(); } }, onLongPressEnd: (_) { if (_isRecording) { _stopRecording(); } }, child: AnimatedBuilder( animation: _pulseController, builder: (context, child) { return Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: _isPreparing ? [const Color(0xFFFFA500), const Color(0xFFFFCC00)] : _isRecording ? [ const Color.fromARGB(255, 178, 4, 54), const Color.fromARGB(255, 255, 200, 215) ] : _isProcessing ? [Colors.grey.shade600, Colors.grey.shade700] : [ const Color(0xFF0066AA), const Color(0xFF00AAFF) ], ), boxShadow: [ BoxShadow( color: _getMainColor().withOpacity(0.5), blurRadius: 20 + (_pulseController.value * 10), spreadRadius: 5 + (_pulseController.value * 5), ), ], ), child: Icon( _isPreparing || _isRecording ? Icons.stop_rounded : Icons.mic_rounded, color: Colors.white, size: 36, ), ); }, ), ), ], ), ); } } class WaveVisualizerPainter extends CustomPainter { final List waveHeights; final Color color; final bool isActive; WaveVisualizerPainter({ required this.waveHeights, required this.color, required this.isActive, }); @override void paint(Canvas canvas, Size size) { if (!isActive) return; final paint = Paint() ..color = color ..style = PaintingStyle.fill; final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2; final barCount = waveHeights.length; final angleStep = (2 * math.pi) / barCount; for (int i = 0; i < barCount; i++) { final angle = i * angleStep; final height = waveHeights[i] * 40; final startX = center.dx + (radius - 10) * math.cos(angle); final startY = center.dy + (radius - 10) * math.sin(angle); final endX = center.dx + (radius - 10 + height) * math.cos(angle); final endY = center.dy + (radius - 10 + height) * math.sin(angle); paint.strokeWidth = 3; paint.strokeCap = StrokeCap.round; paint.color = color.withOpacity(0.6 + (waveHeights[i] * 0.4)); canvas.drawLine( Offset(startX, startY), Offset(endX, endY), paint, ); } } @override bool shouldRepaint(covariant WaveVisualizerPainter oldDelegate) { return true; } } class ParticlePainter extends CustomPainter { final Animation animation; final Color color; ParticlePainter({required this.animation, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color.withOpacity(0.1) ..style = PaintingStyle.fill; final random = math.Random(42); for (int i = 0; i < 30; i++) { final x = random.nextDouble() * size.width; final baseY = random.nextDouble() * size.height; final y = baseY + (animation.value * 100) % size.height; final radius = 1 + random.nextDouble() * 2; canvas.drawCircle(Offset(x, y), radius, paint); } } @override bool shouldRepaint(covariant ParticlePainter oldDelegate) { return true; } } class PreparingSpinnerPainter extends CustomPainter { final double animation; final Color color; PreparingSpinnerPainter({required this.animation, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 4 ..strokeCap = StrokeCap.round; final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 20; const dotCount = 12; const angleStep = (2 * math.pi) / dotCount; for (int i = 0; i < dotCount; i++) { final angle = (i * angleStep) + (animation * 2 * math.pi); final opacity = (1.0 - (i / dotCount)) * 0.8; final x = center.dx + radius * math.cos(angle); final y = center.dy + radius * math.sin(angle); paint.color = color.withOpacity(opacity); canvas.drawCircle( Offset(x, y), 3 + (animation * 2), paint..style = PaintingStyle.fill, ); } for (int i = 0; i < 3; i++) { final arcRadius = radius - (i * 25); final startAngle = (animation * 2 * math.pi) + (i * math.pi / 3); final sweepAngle = math.pi / 2 + (animation * math.pi / 4); paint ..style = PaintingStyle.stroke ..strokeWidth = 3.0 - i ..color = color.withOpacity(0.5 - (i * 0.1)); canvas.drawArc( Rect.fromCircle(center: center, radius: arcRadius), startAngle, sweepAngle, false, paint, ); } } @override bool shouldRepaint(covariant PreparingSpinnerPainter oldDelegate) { return true; } }