didvan-app/lib/views/home/main/widgets/didvan_voice_section.dart

255 lines
11 KiB
Dart

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<DidvanVoiceSection> createState() => _DidvanVoiceSectionState();
}
class _DidvanVoiceSectionState extends State<DidvanVoiceSection> {
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<PlayerState>(
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<Duration?>(
stream: _audioPlayer.durationStream,
builder: (context, durationSnapshot) {
// اگر صدای من نیست، مدت زمان و پوزیشن را صفر نشان بده
final duration = isMyVoice
? (durationSnapshot.data ?? Duration.zero)
: Duration.zero;
return StreamBuilder<Duration>(
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()));
}
},
),
),
),
],
);
},
);
},
),
],
);
},
),
),
],
),
);
}
}