// ignore_for_file: deprecated_member_use import 'package:didvan/services/ai_rag_service.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/ai_voice_chat_dialog.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/views/widgets/glass/liquid_glass_container.dart'; import 'package:didvan/views/widgets/glass/glass_logic.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:provider/provider.dart'; import 'package:record/record.dart'; import 'package:just_audio/just_audio.dart'; import 'dart:ui'; class AiChatDialog extends StatefulWidget { const AiChatDialog({super.key}); @override State createState() => _AiChatDialogState(); } class _AiChatDialogState extends State with TickerProviderStateMixin { final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); final List _messages = []; bool _isLoading = false; bool _isRecording = false; late AnimationController _animationController; late AnimationController _pulseController; late AnimationController _shimmerController; final AudioRecorder _audioRecorder = AudioRecorder(); final AudioPlayer _audioPlayer = AudioPlayer(); String? _recordingPath; String? _currentPlayingUrl; bool _isPlaying = false; final GlassShader _headerGlassShader = GlassShader(); final GlassShader _inputGlassShader = GlassShader(); final GlassShader _loadingGlassShader = GlassShader(); final GlobalKey _backgroundKey = GlobalKey(); @override void initState() { super.initState(); Future.wait([ _headerGlassShader.initialize(), _inputGlassShader.initialize(), _loadingGlassShader.initialize(), ] as Iterable).then((_) { if (mounted) setState(() {}); }); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), ); _audioPlayer.playerStateStream.listen((state) { if (mounted) { setState(() { _isPlaying = state.playing; if (state.processingState == ProcessingState.completed) { _isPlaying = false; _currentPlayingUrl = null; } }); } }); _pulseController = AnimationController( vsync: this, duration: const Duration(milliseconds: 3000), )..repeat(reverse: true); _shimmerController = AnimationController( vsync: this, duration: const Duration(milliseconds: 4000), )..repeat(); _animationController.forward(); Future.delayed(const Duration(milliseconds: 600), () { if (mounted) { setState(() { _messages.add(ChatMessage( text: 'سلام! 👋\n\nمن دستیار هوشمند دیدوان هستم. می‌تونم در مورد اخبار، تحلیل‌ها و محتوای دیدوان بهتون کمک کنم.\n\nچه سوالی دارید؟ 😊', isUser: false, timestamp: DateTime.now(), )); }); _scrollToBottom(); } }); } @override void dispose() { _messageController.dispose(); _scrollController.dispose(); _animationController.dispose(); _pulseController.dispose(); _shimmerController.dispose(); _audioRecorder.dispose(); _audioPlayer.dispose(); super.dispose(); } void _scrollToBottom() { Future.delayed(const Duration(milliseconds: 100), () { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 600), curve: Curves.easeOutCubic, ); } }); } Future _toggleAudioPlayback(String audioUrl) async { try { if (_isPlaying && _currentPlayingUrl == audioUrl) { await _audioPlayer.pause(); setState(() { _isPlaying = false; }); } else { if (_currentPlayingUrl != audioUrl) { final token = RequestService.token; AudioSource? audioSource; try { audioSource = AudioSource.uri( Uri.parse(audioUrl), headers: {'Authorization': 'Bearer $token'}, ); await _audioPlayer.setAudioSource(audioSource); setState(() { _currentPlayingUrl = audioUrl; }); } catch (headerError) { await _audioPlayer.setUrl(audioUrl); setState(() { _currentPlayingUrl = audioUrl; }); } } await _audioPlayer.play(); setState(() { _isPlaying = true; }); } } catch (e) { debugPrint('❌ Error playing audio: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('خطا در پخش صوت')), ); } } } Future _sendMessage() async { final message = _messageController.text.trim(); if (message.isEmpty) return; setState(() { _messages.add(ChatMessage( text: message, isUser: true, timestamp: DateTime.now(), )); _isLoading = true; _messageController.clear(); }); _scrollToBottom(); final response = await AiRagService.sendMessage(message); setState(() { _messages.add(ChatMessage( text: response.output, isUser: false, timestamp: DateTime.now(), sources: response.sources, audioUrl: response.audioUrl, )); _isLoading = false; }); _scrollToBottom(); } @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, insetPadding: EdgeInsets.zero, child: Stack( alignment: Alignment.center, children: [ Positioned.fill( child: GestureDetector( onTap: () => Navigator.pop(context), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( color: Colors.black.withOpacity(0.01), ), ), ), ), Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: ScaleTransition( scale: CurvedAnimation( parent: _animationController, curve: Curves.elasticOut, ), child: Container( constraints: const BoxConstraints(maxWidth: 500, maxHeight: 680), decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color: Colors.white.withOpacity(0.05), boxShadow: [ BoxShadow( color: const Color(0xFF0066AA).withOpacity(0.15), blurRadius: 40, spreadRadius: 0, offset: const Offset(0, 20), ), BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), padding: const EdgeInsets.all(1.5), child: ClipRRect( borderRadius: BorderRadius.circular(39), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Stack( children: [ RepaintBoundary( key: _backgroundKey, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(39), gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color.fromARGB(55, 255, 255, 255), Color.fromARGB(50, 255, 255, 255), ], ), ), child: Stack( children: [ Positioned.fill( child: AnimatedBuilder( animation: _shimmerController, builder: (context, child) { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment( -1.5 + (_shimmerController.value * 2.5), -0.5), end: Alignment( 0.5 + (_shimmerController.value * 2.5), 1.5), colors: [ Colors.transparent, Colors.white.withOpacity(0.1), Colors.transparent, ], stops: const [0.0, 0.5, 1.0], ), ), ); }, ), ), Positioned.fill( child: Stack( alignment: Alignment.bottomCenter, children: [ _buildMessageList(), ], ), ), ], ), ), ), Positioned( top: 0, left: 0, right: 0, child: LiquidGlassContainer( backgroundKey: _backgroundKey, shader: _headerGlassShader, distortion: 6.0, blur: 30.0, dispersion: 10.0, borderRadius: const BorderRadius.only( topLeft: Radius.circular(39), topRight: Radius.circular(39), ), child: _buildHeaderContent(context), ), ), Positioned( bottom: 0, left: 0, right: 0, child: LiquidGlassContainer( backgroundKey: _backgroundKey, shader: _inputGlassShader, distortion: 6.0, blur: 30.0, dispersion: 10.0, borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(39), bottomRight: Radius.circular(39), ), child: _buildInputFieldContent(), ), ), ], ), ), ), ), ), ), ), ], ), ); } Widget _buildHeaderContent(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), decoration: BoxDecoration( color: const Color.fromARGB(150, 255, 255, 255), border: Border( bottom: BorderSide( color: Colors.white.withOpacity(0.2), width: 1, ), ), ), child: Row( children: [ Container( height: 48, width: 48, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.white.withOpacity(0.9), const Color(0xFFE0F7FF), const Color(0xFFF0FDFF), ], stops: const [0.0, 0.3, 1.0], ), border: Border.all( color: Colors.white, width: 1.5, ), boxShadow: [ BoxShadow( color: const Color(0xFF0066AA).withOpacity(0.2), blurRadius: 15, offset: const Offset(0, 8), ), BoxShadow( color: Colors.white.withOpacity(0.5), blurRadius: 10, offset: const Offset(-2, -2), ), ], ), child: Padding( padding: const EdgeInsets.all(10), child: SvgPicture.asset( 'lib/assets/icons/live ai.svg', colorFilter: const ColorFilter.mode( Color(0xFF0066AA), BlendMode.srcIn, ), ), ), ), const SizedBox(width: 14), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanText( 'دستیار هوشمند دیدوان', fontSize: 13, fontWeight: FontWeight.w800, color: Color(0xFF1A2B3C), ), ], ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), shape: BoxShape.circle, border: Border.all( color: Colors.white.withOpacity(0.4), ), ), child: Icon( Icons.close_rounded, color: Colors.grey.shade800, size: 22, ), ), ), ], ), ); } Widget _buildInputFieldContent() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color.fromARGB(50, 255, 255, 255), border: Border( top: BorderSide( color: Colors.white.withOpacity(0.3), width: 1, ), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ GestureDetector( onTap: _isLoading || _isRecording ? null : _sendMessage, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 52, height: 52, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: (_isLoading || _isRecording) ? [Colors.grey.shade300, Colors.grey.shade400] : [const Color(0xFF0066AA), const Color(0xFF0099FF)], ), shape: BoxShape.circle, border: Border.all( color: Colors.white.withOpacity(0.6), width: 2, ), boxShadow: [ BoxShadow( color: ((_isLoading || _isRecording) ? Colors.grey : const Color(0xFF0066AA)) .withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 6), ), ], ), child: _isLoading ? const Padding( padding: EdgeInsets.all(14), child: CircularProgressIndicator( strokeWidth: 2.5, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Padding( padding: const EdgeInsets.all(13.0), child: SvgPicture.asset( 'lib/assets/icons/send.svg', color: Colors.white, ), ), ), ), const SizedBox(width: 12), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(30), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( constraints: const BoxConstraints(maxHeight: 120), decoration: BoxDecoration( color: Colors.white.withOpacity(0.75), borderRadius: BorderRadius.circular(30), border: Border.all( color: Colors.white.withOpacity(0.5), width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: TextField( controller: _messageController, maxLines: null, textInputAction: TextInputAction.send, onSubmitted: (_) => _sendMessage(), onChanged: (_) => setState(() {}), style: const TextStyle( fontSize: 14, color: Color(0xFF1A2B3C), height: 1.5, ), decoration: InputDecoration( hintText: 'چیزی بنویسید...', hintStyle: TextStyle( color: Colors.grey.shade700, fontSize: 11, fontWeight: FontWeight.w500, ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 16, ), suffixIcon: _messageController.text.isEmpty ? GestureDetector( onTap: () { Navigator.pop(context); showDialog( context: context, barrierDismissible: false, builder: (context) => const AiVoiceChatDialog(), ); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Container( decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFF0066AA), Color(0xFF00AAFF) ], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.all(5.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ const SizedBox( width: 5, ), const DidvanText( 'چت صوتی', fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), const SizedBox(width: 6), SvgPicture.asset( 'lib/assets/icons/voice-square.svg', colorFilter: const ColorFilter.mode( Colors.white, BlendMode.srcIn, ), height: 24, ), ], ), ), ), ), ) : null, suffixIconConstraints: const BoxConstraints( minWidth: 0, minHeight: 0, ), ), ), ), ), ), ), ], ), ); } Widget _buildMessageList() { return ListView.builder( controller: _scrollController, physics: const BouncingScrollPhysics(), padding: const EdgeInsets.fromLTRB(20, 100, 20, 120), itemCount: _messages.length + (_isLoading ? 1 : 0), itemBuilder: (context, index) { if (index < _messages.length) { final message = _messages[index]; return _buildMessageBubble(message); } else { return Padding( padding: const EdgeInsets.only(bottom: 18), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ _buildLiquidLoadingIndicator(), ], ), ); } }, ); } Widget _buildMessageBubble(ChatMessage message) { return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 600), curve: Curves.easeOutBack, builder: (context, value, child) { return Transform.translate( offset: Offset(0, 30 * (1 - value)), child: Transform.scale( scale: 0.9 + (0.1 * value), child: Opacity( opacity: value, child: child, ), ), ); }, child: Padding( padding: const EdgeInsets.only(bottom: 18), child: Row( mainAxisAlignment: message.isUser ? MainAxisAlignment.start : MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (message.isUser) ...[ _buildUserAvatar(), const SizedBox(width: 10), ], Flexible( child: Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), decoration: BoxDecoration( gradient: message.isUser ? null : const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFF0066AA), Color(0xFF0099FF), ], ), color: message.isUser ? Colors.white.withOpacity(0.6) : null, borderRadius: BorderRadius.only( topLeft: const Radius.circular(24), topRight: const Radius.circular(24), bottomRight: Radius.circular(message.isUser ? 4 : 24), bottomLeft: Radius.circular(message.isUser ? 24 : 4), ), border: Border.all( color: message.isUser ? Colors.white.withOpacity(0.5) : Colors.white.withOpacity(0.2), width: 1, ), boxShadow: [ BoxShadow( color: message.isUser ? Colors.black.withOpacity(0.03) : const Color(0xFF0066AA).withOpacity(0.25), blurRadius: 15, offset: const Offset(0, 6), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanText( message.text, color: message.isUser ? const Color(0xFF1A2B3C) : Colors.white, fontSize: 14, fontWeight: FontWeight.w500, ), const SizedBox(height: 4), Align( alignment: Alignment.centerLeft, child: DidvanText( _formatTime(message.timestamp), fontSize: 10, color: message.isUser ? Colors.grey.shade600 : Colors.white.withOpacity(0.7), ), ), if (message.sources.isNotEmpty) ...[ const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(14), border: Border.all( color: Colors.white.withOpacity(0.2), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.bookmark_rounded, size: 12, color: Colors.white, ), const SizedBox(width: 6), DidvanText( 'منابع: ${message.sources.join(", ")}', fontSize: 11, color: Colors.white, fontWeight: FontWeight.w600, ), ], ), ), ], ], ), ), ), if (!message.isUser) ...[ const SizedBox(width: 10), _buildAiAvatar(), ], ], ), ), ); } Widget _buildAiAvatar() { return Container( width: 38, height: 38, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF0066AA), Color(0xFF00AAFF)], ), shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.8), width: 1.5), boxShadow: [ BoxShadow( color: const Color(0xFF0066AA).withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), padding: const EdgeInsets.all(9), child: SvgPicture.asset( 'lib/assets/icons/live ai.svg', colorFilter: const ColorFilter.mode( Colors.white, BlendMode.srcIn, ), ), ); } Widget _buildUserAvatar() { return Consumer( builder: (context, userProvider, _) { return Container( width: 38, height: 38, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white.withOpacity(0.8), width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(19), child: (userProvider.user.photo != null && userProvider.user.photo!.isNotEmpty) ? Image.network( userProvider.user.photo!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildDefaultUserAvatar(), ) : _buildDefaultUserAvatar(), ), ); }, ); } Widget _buildDefaultUserAvatar() { return Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.grey.shade300, Colors.grey.shade400], ), shape: BoxShape.circle, ), child: const Icon( Icons.person_rounded, size: 20, color: Colors.white, ), ); } Widget _buildLiquidLoadingIndicator() { return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ LiquidGlassContainer( backgroundKey: _backgroundKey, shader: _loadingGlassShader, distortion: 3.0, blur: 20.0, dispersion: 2.2, borderRadius: const BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), bottomRight: Radius.circular(24), bottomLeft: Radius.circular(4), ), child: AnimatedBuilder( animation: _shimmerController, builder: (context, child) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: const Color.fromARGB(167, 255, 255, 255), borderRadius: const BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), bottomRight: Radius.circular(24), bottomLeft: Radius.circular(4), ), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ DidvanText( 'در حال فکر کردن...', color: const Color(0xFF1A2B3C).withOpacity(0.8), fontSize: 12, fontWeight: FontWeight.w700, ), const SizedBox(width: 10), Stack( alignment: Alignment.center, children: [ SpinKitPulse( color: const Color(0xFF0066AA).withOpacity(0.5), size: 24, ), const Icon( Icons.auto_awesome, size: 14, color: Color(0xFF0066AA), ) ], ), ], ), ); }, ), ), const SizedBox(width: 10), _buildAiAvatar(), ], ); } String _formatTime(DateTime time) { final hour = time.hour.toString().padLeft(2, '0'); final minute = time.minute.toString().padLeft(2, '0'); return '$hour:$minute'; } } class ChatMessage { final String text; final bool isUser; final DateTime timestamp; final List sources; final String? audioUrl; ChatMessage({ required this.text, required this.isUser, required this.timestamp, this.sources = const [], this.audioUrl, }); }