import 'dart:async'; import 'dart:math'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/media/widgets/audio_waveform_progress.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:just_audio/just_audio.dart'; import 'package:provider/provider.dart'; class AudioPlayerWidget extends StatelessWidget { final StudioDetailsData podcast; const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key); @override Widget build(BuildContext context) { final state = context.read(); return Stack( children: [ SkeletonImage( imageUrl: podcast.image, aspectRatio: 1 / 1.3, borderRadius: BorderRadius.circular(0), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.vertical(top: Radius.circular(24.0)), color: Theme.of(context).colorScheme.surface, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox( height: 30, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ StreamBuilder( stream: MediaService.audioPlayer.speedStream, builder: (context, snapshot) { if (!snapshot.hasData) { return const SizedBox(); } return const Column( children: [ SizedBox(), // PopupMenuButton( // child: Container( // width: 46, // alignment: Alignment.center, // margin: const EdgeInsets.fromLTRB(12, 0, 0, 0), // تغییر: 46 پایین حذف شد // padding: const EdgeInsets.only(top: 2), // decoration: BoxDecoration( // borderRadius: DesignConfig.mediumBorderRadius, // border: Border.all( // color: // Theme.of(context).colorScheme.title)), // child: DidvanText( // '${snapshot.data!.toString().replaceAll('.0', '')}X'), // ), // onSelected: (value) async { // await MediaService.audioPlayer.setSpeed(value); // }, // itemBuilder: (BuildContext context) => // [ // popUpSpeed(value: 0.5), // popUpSpeed(value: 0.75), // popUpSpeed(value: 1.0), // popUpSpeed(value: 1.25), // popUpSpeed(value: 1.5), // popUpSpeed(value: 2.0), // ], // ), ], ); }), Expanded( child: StreamBuilder( stream: MediaService.audioPlayer.positionStream, builder: (context, snapshot) { final position = snapshot.data ?? Duration.zero; return StreamBuilder( stream: MediaService.audioPlayer.durationStream, builder: (context, durationSnapshot) { final totalDuration = durationSnapshot.data ?? Duration(milliseconds: podcast.duration); final progress = totalDuration.inMilliseconds > 0 ? position.inMilliseconds / totalDuration.inMilliseconds : 0.0; return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( _formatDuration(totalDuration), style: TextStyle( fontSize: 12, color: Theme.of(context) .colorScheme .title .withOpacity(0.7), ), ), const SizedBox(width: 8), Expanded( child: AudioWaveformProgress( progress: progress.clamp(0.0, 1.0), isActive: true, onChanged: (value) { final newPosition = Duration( milliseconds: (totalDuration.inMilliseconds * value) .round(), ); MediaService.audioPlayer .seek(newPosition); }, ), ), const SizedBox(width: 8), Text( _formatDuration(position), style: TextStyle( fontSize: 12, color: Theme.of(context) .colorScheme .title .withOpacity(0.7), ), ), ], ); }, ); }, ), ), ], ), ), const SizedBox(height: 16), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Center( child: StatefulBuilder( builder: (context, setState) => Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(8, 8, 20, 8), child: Center( child: IconButton( icon: SvgPicture.asset( 'lib/assets/icons/timer-pause.svg', width: 35, height: 35, colorFilter: ColorFilter.mode( Theme.of(context).colorScheme.caption, BlendMode.srcIn, ), ), onPressed: () => _showSleepTimer( context, state, () => setState(() {}), ), ), ), ), // DidvanIconButton( // icon: state.timer == null && // !state.stopOnPodcastEnds // ? DidvanIcons.sleep_timer_regular // : DidvanIcons.sleep_enabled_regular, // color: Theme.of(context).colorScheme.title, // onPressed: () => _showSleepTimer( // context, // state, // () => setState(() {}), // ), // ), // if (state.timer != null) // DidvanText( // state.stopOnPodcastEnds // ? 'پایان پادکست' // : '\'${state.timerValue}', // isEnglishFont: true, // style: Theme.of(context).textTheme.labelSmall, // color: Theme.of(context).colorScheme.title, // ), ], ), ), ), ), Expanded( child: Center( child: Padding( padding: const EdgeInsets.all(8.0), child: IconButton( onPressed: () { MediaService.audioPlayer.seek( Duration( seconds: max( 0, MediaService .audioPlayer.position.inSeconds + 10, ), ), ); }, icon: SvgPicture.asset( 'lib/assets/icons/forward-10-seconds.svg', colorFilter: ColorFilter.mode( Theme.of(context).colorScheme.caption, BlendMode.srcIn, ), ), ), ), ), ), Expanded( child: Center( child: StreamBuilder( stream: MediaService.audioPlayer.playerStateStream, builder: (context, snapshot) { if (snapshot.data == null) { return const CircularProgressIndicator(); } return StreamBuilder( stream: MediaService.audioPlayer.playingStream, builder: (context, snapshot) { final isPlaying = snapshot.data ?? false; return _PlayPouseAnimatedIcon( audioSource: podcast.link, id: podcast.id, isPlaying: isPlaying, ); }, ); }, ), ), ), Expanded( child: Center( child: Padding( padding: const EdgeInsets.all(8.0), child: IconButton( onPressed: () { MediaService.audioPlayer.seek( Duration( seconds: max( 0, MediaService .audioPlayer.position.inSeconds - 5, ), ), ); }, icon: SvgPicture.asset( 'lib/assets/icons/backward-5-seconds.svg', colorFilter: ColorFilter.mode( Theme.of(context).colorScheme.caption, BlendMode.srcIn, ), ), ), ), ), ), Expanded( child: StreamBuilder( stream: MediaService.audioPlayer.speedStream, builder: (context, snapshot) { if (!snapshot.hasData) { return const SizedBox(); } return Center( child: PopupMenuButton( child: Padding( padding: const EdgeInsets.all(12.0), child: Container( width: 46, alignment: Alignment.center, margin: const EdgeInsets.fromLTRB(12, 0, 0, 0), padding: const EdgeInsets.only(top: 2), decoration: BoxDecoration( borderRadius: DesignConfig.mediumBorderRadius, border: Border.all( color: const Color.fromARGB( 255, 102, 102, 102))), child: Padding( padding: const EdgeInsets.all(8.0), child: DidvanText( '${snapshot.data!.toString().replaceAll('.0', '')}X', style: TextStyle( fontWeight: FontWeight.w900, color: Theme.of(context) .colorScheme .caption, ), ), ), ), ), onSelected: (value) async { await MediaService.audioPlayer.setSpeed(value); }, itemBuilder: (BuildContext context) => [ popUpSpeed(value: 0.5), popUpSpeed(value: 0.75), popUpSpeed(value: 1.0), popUpSpeed(value: 1.25), popUpSpeed(value: 1.5), popUpSpeed(value: 2.0), ], ), ); }, ), ), ], ), const SizedBox(height: 16), ], ), ), ), ], ); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final hours = duration.inHours; final minutes = duration.inMinutes.remainder(60); final seconds = duration.inSeconds.remainder(60); if (hours > 0) { return '$hours:${twoDigits(minutes)}:${twoDigits(seconds)}'; } else { return '${twoDigits(minutes)}:${twoDigits(seconds)}'; } } PopupMenuItem popUpSpeed({required double value}) { return PopupMenuItem( value: value, height: 32, child: DidvanText( '${value}X', ), ); } Future _showSleepTimer( BuildContext context, StudioDetailsState state, update) async { int timerValue = 10; final controller = FixedExtentScrollController(); bool isInit = true; Future.delayed( const Duration(milliseconds: 100), () async { await controller.animateTo( state.timerValue * 10, duration: DesignConfig.lowAnimationDuration, curve: Curves.easeIn, ); isInit = false; }, ); await ActionSheetUtils(context).showBottomSheet( data: ActionSheetData( content: StatefulBuilder( builder: (context, setState) => Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( 'lib/assets/icons/timer-pause.svg', height: 24, color: Theme.of(context).colorScheme.caption, ), const SizedBox( width: 10, ), Text( 'زمان خواب', style: TextStyle(color: Theme.of(context).colorScheme.caption), ) ], ), const SizedBox(height: 24), DidvanText( '$timerValue دقیقه', style: Theme.of(context).textTheme.displaySmall, ), const SizedBox(height: 12), const Icon(DidvanIcons.caret_down_solid), const SizedBox(height: 8), SizedBox( height: 50, child: RotatedBox( quarterTurns: 3, child: ListWheelScrollView( physics: const FixedExtentScrollPhysics(), controller: controller, itemExtent: 10, onSelectedItemChanged: (index) { if (!isInit) { state.stopOnPodcastEnds = false; } final minutes = index == 0 ? 1 : index; timerValue = minutes; setState(() {}); }, children: [ for (var i = 0; i < 61; i++) ...[ if (i % 5 == 0) Center( child: Container( color: Theme.of(context).colorScheme.text, width: 50, height: 3, ), ), if (i % 5 != 0) const SizedBox(height: 3), ], ], ), ), ), const SizedBox(height: 32), Padding( padding: const EdgeInsets.symmetric(horizontal: 64), child: DidvanButton( style: state.timerValue == MediaService.duration?.inMinutes && state.stopOnPodcastEnds ? ButtonStyleMode.primary : ButtonStyleMode.flat, title: 'پایان پادکست', onPressed: () async { state.timerValue = MediaService.duration!.inMinutes - MediaService.audioPlayer.position.inMinutes; await controller.animateTo( state.timerValue * 10, duration: DesignConfig.lowAnimationDuration, curve: Curves.easeIn, ); state.stopOnPodcastEnds = true; setState(() {}); }, ), ), ], ), ), onConfirmed: () { if (!state.stopOnPodcastEnds) { state.timer = Timer.periodic( const Duration(minutes: 1), (timer) { timerValue--; if (timerValue == 0) { MediaService.audioPlayer.pause(); state.stopOnPodcastEnds = false; state.timer?.cancel(); state.timer = null; state.timerValue = 10; state.update(); } }, ); } state.timerValue = timerValue; update(); }, confrimTitle: 'شروع زمان خواب', dismissTitle: 'لغو', onDismissed: () { state.timer?.cancel(); state.timer = null; state.timerValue = 10; update(); }, ), ); controller.dispose(); } } class _PlayPouseAnimatedIcon extends StatefulWidget { final String audioSource; final int id; final bool isPlaying; const _PlayPouseAnimatedIcon( {Key? key, required this.audioSource, required this.id, required this.isPlaying}) : super(key: key); @override State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState(); } class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon> { @override Widget build(BuildContext context) { return InkWrapper( borderRadius: BorderRadius.circular(100), onPressed: () { MediaService.handleAudioPlayback( audioSource: widget.audioSource, isVoiceMessage: false, id: widget.id, ); }, child: SvgPicture.asset( widget.isPlaying ? 'lib/assets/icons/pause-circle.svg' : 'lib/assets/icons/video-circle.svg', width: 65, height: 65, ), ); } }