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) { final imageUrl = '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; // برای یک لقمه استراتژی، لینک مستقیم از سرور استفاده می‌شود final audioUrl = widget.didvanVoice.file.startsWith('http') ? widget.didvanVoice.file : '${RequestHelper.baseUrl}${widget.didvanVoice.file}?accessToken=${RequestService.token}'; 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())); } }, ), ), ), ], ); }, ); }, ), ], ); }, ), ), ], ), ); } }