import 'package:didvan/models/didvan_voice_model.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/media/voice.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:just_audio/just_audio.dart'; class DidvanVoiceSection extends StatefulWidget { final DidvanVoiceModel didvanVoice; const DidvanVoiceSection({ super.key, required this.didvanVoice, }); @override State createState() => _DidvanVoiceSectionState(); } class _DidvanVoiceSectionState extends State { late AudioPlayer _audioPlayer; @override void initState() { super.initState(); _initializeAudio(); } void _initializeAudio() { _audioPlayer = VoiceService.audioPlayer; } String _formatDuration(Duration? duration) { if (duration == null) return '00:00'; final minutes = duration.inMinutes; final seconds = duration.inSeconds % 60; return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } @override Widget build(BuildContext context) { debugPrint('🎙️ Original File: ${widget.didvanVoice.file}'); debugPrint('🎙️ Original Image: ${widget.didvanVoice.image}'); final bool fileHasHttp = widget.didvanVoice.file.startsWith('http'); final bool fileHasBaseUrl = widget.didvanVoice.file.contains(RequestHelper.baseUrl); debugPrint('🎙️ File has HTTP: $fileHasHttp, has BaseURL: $fileHasBaseUrl'); final imageUrl = widget.didvanVoice.image.startsWith('http') || widget.didvanVoice.image.contains(RequestHelper.baseUrl) ? widget.didvanVoice.image : '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; // برای یک لقمه استراتژی، لینک مستقیم از سرور استفاده می‌شود final audioFileUrl = widget.didvanVoice.file.startsWith('http') || widget.didvanVoice.file.contains(RequestHelper.baseUrl) ? widget.didvanVoice.file : '${RequestHelper.baseUrl}${widget.didvanVoice.file}'; debugPrint('🎙️ Audio URL before token: $audioFileUrl'); // فقط به لینک‌های baseUrl سرور accessToken اضافه می‌شود، نه لینک‌های external مثل S3 final bool needsToken = audioFileUrl.contains(RequestHelper.baseUrl); debugPrint('🎙️ Needs access token: $needsToken'); final String audioUrl; if (needsToken) { final separator = audioFileUrl.contains('?') ? '&' : '?'; audioUrl = '$audioFileUrl${separator}accessToken=${RequestService.token}'; } else { audioUrl = audioFileUrl; // لینک‌های S3 نیازی به token ندارند } debugPrint('🎙️ یک لقمه استراتژی - Image URL: $imageUrl'); debugPrint('🎙️ یک لقمه استراتژی - Audio URL: $audioUrl'); return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color.fromRGBO(0, 69, 92, 1), borderRadius: BorderRadius.circular(12), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), child: Image.network( imageUrl, width: 110, height: 150, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 110, height: 150, color: Colors.grey.shade800, child: const Icon(Icons.music_note, color: Colors.white), ); }, ), ), const SizedBox(width: 16), Expanded( child: StreamBuilder( stream: _audioPlayer.playerStateStream, builder: (context, snapshot) { final isMyVoice = VoiceService.src == audioUrl; final playerState = snapshot.data; final processingState = playerState?.processingState ?? ProcessingState.idle; final playing = isMyVoice && (playerState?.playing ?? false); final isLoading = isMyVoice && (processingState == ProcessingState.loading || processingState == ProcessingState.buffering); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanText( widget.didvanVoice.title, fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, maxLines: 2, overflow: TextOverflow.ellipsis, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () async { if (!isMyVoice) { if (_audioPlayer.playing) await _audioPlayer.stop(); VoiceService.src = audioUrl; await _audioPlayer.setUrl(audioUrl); _audioPlayer.play(); } else { final newPosition = _audioPlayer.position + const Duration(seconds: 10); _audioPlayer.seek(newPosition); } }, icon: const Icon(Icons.forward_10, color: Colors.white), iconSize: 28, ), if (isLoading) Container( margin: const EdgeInsets.symmetric(horizontal: 8), width: 48, height: 48, child: const CircularProgressIndicator( color: Colors.white), ) else IconButton( onPressed: () async { if (playing) { _audioPlayer.pause(); } else { if (!isMyVoice) { // لود کردن صدای جدید اگر سورس فرق دارد VoiceService.src = audioUrl; await _audioPlayer.setUrl(audioUrl); } _audioPlayer.play(); } }, icon: Icon( playing ? Icons.pause_circle_filled : Icons.play_circle_filled, color: Colors.white, ), iconSize: 48, ), IconButton( onPressed: () { if (isMyVoice) { final newPosition = _audioPlayer.position - const Duration(seconds: 10); _audioPlayer.seek(newPosition < Duration.zero ? Duration.zero : newPosition); } }, icon: const Icon(Icons.replay_10, color: Colors.white), iconSize: 28, ), ], ), StreamBuilder( stream: _audioPlayer.durationStream, builder: (context, durationSnapshot) { // اگر صدای من نیست، مدت زمان و پوزیشن را صفر نشان بده final duration = isMyVoice ? (durationSnapshot.data ?? Duration.zero) : Duration.zero; return StreamBuilder( stream: _audioPlayer.positionStream, builder: (context, positionSnapshot) { var position = isMyVoice ? (positionSnapshot.data ?? Duration.zero) : Duration.zero; if (position > duration) position = duration; return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DidvanText( _formatDuration(duration), fontSize: 12, color: Colors.white70, ), DidvanText( _formatDuration(position), fontSize: 12, color: Colors.white70, ), ], ), ), const SizedBox(height: 4), Directionality( textDirection: TextDirection.ltr, child: SliderTheme( data: const SliderThemeData( thumbShape: RoundSliderThumbShape( enabledThumbRadius: 6), overlayShape: RoundSliderOverlayShape( overlayRadius: 12), trackHeight: 4, activeTrackColor: Colors.white70, inactiveTrackColor: Colors.white30, thumbColor: Colors.transparent, overlayColor: Colors.transparent, ), child: Slider( value: position.inMilliseconds.toDouble(), max: duration.inMilliseconds.toDouble() > 0 ? duration.inMilliseconds.toDouble() : 1.0, onChanged: (value) { if (isMyVoice) { _audioPlayer.seek(Duration( milliseconds: value.toInt())); } }, ), ), ), ], ); }, ); }, ), ], ); }, ), ), ], ), ); } }