didvan-app/lib/views/home/widgets/audio/audio_player_widget.dart

309 lines
10 KiB
Dart

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/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/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.media,
showTimer: true,
duration: podcast.duration,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
StatefulBuilder(
builder: (context, setState) => Column(
children: [
DidvanIconButton(
icon: state.timer == null
? DidvanIcons.sleep_timer_regular
: DidvanIcons.sleep_enabled_regular,
color: Theme.of(context).colorScheme.title,
onPressed: () => _showSleepTimer(
state,
() => setState(() {}),
),
),
if (state.timer != null)
DidvanText(
state.timerValue.toString() + '\'',
isEnglishFont: true,
style: Theme.of(context).textTheme.overline,
color: Theme.of(context).colorScheme.caption,
),
],
),
),
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,
),
],
),
StreamBuilder<bool>(
stream: MediaService.audioPlayer.playingStream,
builder: (context, snapshot) {
return _PlayPouseAnimatedIcon(
audioSource: podcast.media,
);
},
),
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,
),
],
),
BookmarkButton(
gestureSize: 48,
color: Theme.of(context).colorScheme.title,
value: podcast.marked,
onMarkChanged: (value) =>
context.read<StudioState>().changeMark(podcast.id, value),
),
const SizedBox(),
],
),
],
),
);
}
Future<void> _showSleepTimer(StudioDetailsState state, update) async {
int timerValue = 10;
final controller = FixedExtentScrollController();
Future.delayed(
const Duration(milliseconds: 100),
() => controller.animateTo(
50 * (state.timerValue / 5 - 2),
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
);
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(
controller: controller,
physics: const FixedExtentScrollPhysics(),
itemExtent: 48,
onSelectedItemChanged: (index) {
final minutes = (index + 2) * 5;
timerValue = minutes;
setState(() {});
},
children: [
for (var i = 0; i < 9; i++)
Center(
child: Container(
color: Theme.of(context).colorScheme.text,
width: 50,
height: 3,
),
),
],
),
),
),
],
),
),
onConfirmed: () {
state.timer = Timer(
Duration(minutes: timerValue),
MediaService.audioPlayer.stop,
);
state.timerValue = timerValue;
update();
},
dismissTitle: 'لغو',
onDismissed: () {
state.timer?.cancel();
state.timer = null;
state.timerValue = 10;
update();
},
),
);
controller.dispose();
}
}
class _PlayPouseAnimatedIcon extends StatefulWidget {
final String audioSource;
const _PlayPouseAnimatedIcon({Key? key, required this.audioSource})
: 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,
);
_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();
}
}