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/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.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 Container( decoration: BoxDecoration( borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), color: Theme.of(context).colorScheme.surface, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( margin: const EdgeInsets.symmetric(vertical: 20), height: 3, width: 50, color: Theme.of(context).colorScheme.hint, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: SkeletonImage( imageUrl: podcast.image, aspectRatio: 1 / 1, ), ), const SizedBox(height: 16), DidvanText( podcast.title, style: Theme.of(context).textTheme.bodyText1, ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AudioSlider( tag: 'podcast-${podcast.id}', showTimer: true, duration: podcast.duration, ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Center( child: StatefulBuilder( builder: (context, setState) => Column( children: [ DidvanIconButton( icon: state.timer == null && !state.stopOnPodcastEnds ? DidvanIcons.sleep_timer_regular : DidvanIcons.sleep_enabled_regular, color: Theme.of(context).colorScheme.title, onPressed: () => _showSleepTimer( state, () => setState(() {}), ), ), if (state.timer != null) DidvanText( state.stopOnPodcastEnds ? 'پایان پادکست' : '\'' + state.timerValue.toString(), isEnglishFont: true, style: Theme.of(context).textTheme.overline, color: Theme.of(context).colorScheme.title, ), ], ), ), ), ), Expanded( child: Center( child: Column( children: [ DidvanIconButton( color: Theme.of(context).colorScheme.title, size: 32, icon: DidvanIcons.media_forward_solid, onPressed: () { MediaService.audioPlayer.seek( Duration( seconds: MediaService.audioPlayer.position.inSeconds + 30, ), ); }, ), DidvanText( '30', isEnglishFont: true, color: Theme.of(context).colorScheme.title, ), ], ), ), ), Expanded( child: Center( child: StreamBuilder( stream: MediaService.audioPlayer.playingStream, builder: (context, snapshot) { return _PlayPouseAnimatedIcon( audioSource: podcast.media, id: podcast.id, ); }, ), ), ), Expanded( child: Center( child: Column( children: [ DidvanIconButton( size: 32, icon: DidvanIcons.media_backward_solid, color: Theme.of(context).colorScheme.title, onPressed: () { MediaService.audioPlayer.seek( Duration( seconds: max( 0, MediaService.audioPlayer.position.inSeconds - 10, ), ), ); }, ), DidvanText( '10', isEnglishFont: true, color: Theme.of(context).colorScheme.title, ), ], ), ), ), Expanded( child: Center( child: BookmarkButton( gestureSize: 48, color: Theme.of(context).colorScheme.title, value: podcast.marked, onMarkChanged: (value) => context .read() .changeMark(podcast.id, value), ), ), ), ], ), ], ), ); } Future _showSleepTimer(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.showBottomSheet( data: ActionSheetData( content: StatefulBuilder( builder: (context, setState) => Column( children: [ const ItemTitle( title: 'زمان خواب', icon: DidvanIcons.sleep_timer_regular, ), const SizedBox(height: 24), DidvanText( timerValue.toString() + ' دقیقه', style: Theme.of(context).textTheme.headline3, ), 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.audioPlayer.duration?.inMinutes && state.stopOnPodcastEnds ? ButtonStyleMode.primary : ButtonStyleMode.flat, title: 'پایان پادکست', onPressed: () async { state.timerValue = MediaService.audioPlayer.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( Duration(minutes: timerValue), MediaService.audioPlayer.stop, ); } 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; const _PlayPouseAnimatedIcon( {Key? key, required this.audioSource, required this.id}) : super(key: key); @override State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState(); } class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon> with SingleTickerProviderStateMixin { late final AnimationController _animationController; @override void didUpdateWidget(covariant _PlayPouseAnimatedIcon oldWidget) { _handleAnimation(); super.didUpdateWidget(oldWidget); } @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: DesignConfig.lowAnimationDuration, ); } void _handleAnimation() { if (MediaService.audioPlayer.playing) { _animationController.forward(); } else { _animationController.reverse(); } } @override Widget build(BuildContext context) { return InkWrapper( borderRadius: BorderRadius.circular(100), onPressed: () { MediaService.handleAudioPlayback( audioSource: widget.audioSource, isVoiceMessage: false, id: widget.id, isNetworkAudio: !context .read() .downloadedFileIds .contains(widget.id), ); _handleAnimation(); }, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.title, shape: BoxShape.circle, ), child: AnimatedIcon( size: 40, color: Theme.of(context).colorScheme.surface, icon: AnimatedIcons.play_pause, progress: _animationController, ), ), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } }