395 lines
14 KiB
Dart
395 lines
14 KiB
Dart
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:assets_audio_player/assets_audio_player.dart';
|
|
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<StudioDetailsState>();
|
|
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}',
|
|
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.currentPosition
|
|
.value.inSeconds +
|
|
30,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
DidvanText(
|
|
'30',
|
|
isEnglishFont: true,
|
|
color: Theme.of(context).colorScheme.title,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Center(
|
|
child: StreamBuilder<PlayingAudio?>(
|
|
stream: MediaService.audioPlayer.onReadyToPlay,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.data == null) {
|
|
return const CircularProgressIndicator();
|
|
}
|
|
return StreamBuilder<bool>(
|
|
stream: MediaService.audioPlayer.isPlaying,
|
|
builder: (context, snapshot) {
|
|
return _PlayPouseAnimatedIcon(
|
|
audioSource: podcast.link,
|
|
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.currentPosition.value
|
|
.inSeconds -
|
|
10,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
DidvanText(
|
|
'10',
|
|
isEnglishFont: true,
|
|
color: Theme.of(context).colorScheme.title,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Center(
|
|
child: BookmarkButton(
|
|
itemId: state.studio.id,
|
|
type: 'podcast',
|
|
gestureSize: 48,
|
|
color: Theme.of(context).colorScheme.title,
|
|
value: podcast.marked,
|
|
onMarkChanged: (value) => context
|
|
.read<StudioState>()
|
|
.changeMark(podcast.id, value, true),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _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.duration?.inMinutes &&
|
|
state.stopOnPodcastEnds
|
|
? ButtonStyleMode.primary
|
|
: ButtonStyleMode.flat,
|
|
title: 'پایان پادکست',
|
|
onPressed: () async {
|
|
state.timerValue = MediaService.duration!.inMinutes -
|
|
MediaService
|
|
.audioPlayer.currentPosition.value.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;
|
|
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.isPlaying.value) {
|
|
_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,
|
|
);
|
|
_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();
|
|
}
|
|
}
|