notif fix -- rhmn

This commit is contained in:
OkaykOrhmn 2024-07-06 09:02:06 +03:30
parent 7262ae8eb5
commit 1365673b93
12 changed files with 899 additions and 648 deletions

View File

@ -123,6 +123,9 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
// MediaService.audioPlayer.dispose(); // MediaService.audioPlayer.dispose();
if(MediaService.currentPodcast != null){
MediaService.audioPlayer.dispose();
}
super.dispose(); super.dispose();
} }

View File

@ -48,6 +48,8 @@ import 'package:didvan/views/podcasts/studio_details/studio_details.mobile.dart'
if (dart.library.html) 'package:didvan/views/podcasts/studio_details/studio_details.web.dart'; if (dart.library.html) 'package:didvan/views/podcasts/studio_details/studio_details.web.dart';
import 'package:didvan/views/splash/splash.dart'; import 'package:didvan/views/splash/splash.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/widgets/audio/player_navbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';

View File

@ -0,0 +1,133 @@
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
class AudioHandler {
late AudioPlayer _player;
PlayerState? _playerState;
Duration? audioDuration = Duration.zero;
Duration? audioPosition = Duration.zero;
bool isLoading = true;
StreamSubscription? durationSubscriptionS;
StreamSubscription? positionSubscriptionS;
StreamSubscription? playerCompleteSubscriptionS;
StreamSubscription? playerStateChangeSubscriptionS;
AudioPlayer get player => _player;
PlayerState? get playerState => _playerState;
bool get isPlaying => _playerState == PlayerState.playing;
bool get isPaused => _playerState == PlayerState.paused;
String get durationText => audioDuration?.toString().split('.').first ?? '';
String get positionText => audioPosition?.toString().split('.').first ?? '';
AudioHandler(String url) {
_player = AudioPlayer();
_player.setReleaseMode(ReleaseMode.stop);
_player
.play(UrlSource(url))
.then((value) {
_player.pause();
isLoading = false;
_playerState = PlayerState.paused;
});
player.getDuration().then((value) => audioDuration = value);
player.getCurrentPosition().then((value) => audioPosition = value);
}
StreamSubscription? durationSubscription(Function(Duration d) func) {
return durationSubscriptionS = player.onDurationChanged.listen((duration) {
// setState(() => _duration = duration);
audioDuration = duration;
func(duration);
});
}
StreamSubscription? positionSubscription(Function(Duration d) func) {
return positionSubscriptionS = player.onPositionChanged.listen(
(p) {
audioPosition = p;
func(p);
},
);
}
StreamSubscription? playerCompleteSubscription(Function() func) {
return playerCompleteSubscriptionS =
player.onPlayerComplete.listen((event) {
stop();
func();
});
}
StreamSubscription? playerStateChangeSubscription(
Function(PlayerState) func) {
return playerStateChangeSubscriptionS =
player.onPlayerStateChanged.listen((state) {
_playerState = state;
func(state);
});
}
Future<void> play() async {
await _player.resume();
// _playerState = PlayerState.playing;
}
Future<void> pause() async {
await _player.pause();
// _playerState = PlayerState.paused;
}
Future<void> stop() async {
await _player.stop();
// _playerState = PlayerState.stopped;
audioPosition = Duration.zero;
}
// void _initStreams() {
// durationSubscription = player.onDurationChanged.listen((duration) {
// setState(() => _duration = duration);
// });
//
// _positionSubscription = player.onPositionChanged.listen(
// (p) {
// setState(() {
// _position = p;
// for (var i = 0; i < bars.length / 10; i++) {
// if (i <= _position!.inSeconds) {
// bars[i].color = primaryColor;
// }
// }
// });
// },
// );
//
// _playerCompleteSubscription = player.onPlayerComplete.listen((event) {
// setState(() {
// _playerState = PlayerState.stopped;
// _position = Duration.zero;
// });
// });
//
// _playerCompleteSubscription = player.onSeekComplete.listen((event) {
// setState(() {
// print(
// "Complete------------------------------------------------------------");
// });
// });
//
// _playerStateChangeSubscription =
// player.onPlayerStateChanged.listen((state) {
// setState(() {
// _playerState = state;
// });
// });
// }
}

View File

@ -24,70 +24,70 @@ class MediaService {
bool isVoiceMessage = true, bool isVoiceMessage = true,
void Function(bool isNext)? onTrackChanged, void Function(bool isNext)? onTrackChanged,
}) async { }) async {
String tag; try {
tag = String tag;
'${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id'; tag =
if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) { '${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id';
audioSource = '${StorageService.appDocsDir}/podcasts/podcast-$id.mp3'; if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) {
isNetworkAudio = false; audioSource = '${StorageService.appDocsDir}/podcasts/podcast-$id.mp3';
} isNetworkAudio = false;
if (audioPlayerTag == tag) {
await audioPlayer.playOrPause();
return;
}
await audioPlayer.stop();
audioPlayerTag = tag;
Audio audio;
String source;
if (isNetworkAudio) {
if (isVoiceMessage) {
source =
'${RequestHelper.baseUrl + audioSource}?accessToken=${RequestService.token}';
} else {
source = audioSource;
} }
audio = Audio.network( if (audioPlayerTag == tag) {
kIsWeb ? source.replaceAll('%3A', ':') : source, await audioPlayer.playOrPause();
metas: isVoiceMessage
? null return;
: Metas( }
artist: 'استودیو دیدوان', // await audioPlayer.stop();
title: currentPodcast?.title ?? '', audioPlayerTag = tag;
), Audio audio;
); String source;
} else { if (isNetworkAudio) {
audio = Audio.file( if (isVoiceMessage) {
audioSource, source =
metas: isVoiceMessage '${RequestHelper.baseUrl + audioSource}?accessToken=${RequestService.token}';
? null } else {
: Metas( source = audioSource;
artist: 'استودیو دیدوان', }
title: currentPodcast?.title ?? '', audio = Audio.network(
), kIsWeb ? source.replaceAll('%3A', ':') : source,
metas: isVoiceMessage
? null
: Metas(
artist: 'استودیو دیدوان',
title: currentPodcast?.title ?? '',
),
);
} else {
audio = Audio.file(
audioSource,
metas: isVoiceMessage
? null
: Metas(
artist: 'استودیو دیدوان',
title: currentPodcast?.title ?? '',
),
);
}
await audioPlayer.open(
audio,
showNotification: !isVoiceMessage,
notificationSettings: NotificationSettings(
customStopAction: (_) => resetAudioPlayer(),
customNextAction: (_) => onTrackChanged?.call(true),
customPrevAction: (_) => onTrackChanged?.call(false),
),
); );
} catch (e) {
// resetAudioPlayer();
rethrow;
} }
await audioPlayer.open(
audio,
showNotification: !isVoiceMessage,
notificationSettings: NotificationSettings(
customStopAction: (_) => resetAudioPlayer(),
customNextAction: (_) => onTrackChanged?.call(true),
customPrevAction: (_) => onTrackChanged?.call(false),
),
);
} }
static Future<void> resetAudioPlayer() async { static Future<void> resetAudioPlayer() async {
audioPlayerTag = null; audioPlayerTag = null;
currentPodcast = null; currentPodcast = null;
podcastPlaylistArgs = null; podcastPlaylistArgs = null;
if(MediaService.audioPlayer.isPlaying.value){ await MediaService.audioPlayer.stop();
MediaService.audioPlayer.stop();
}else{
MediaService.audioPlayer.dispose();
}
} }
static Future<XFile?> pickImage({required ImageSource source}) async { static Future<XFile?> pickImage({required ImageSource source}) async {

View File

@ -123,7 +123,9 @@ class StudioDetailsState extends CoreProvier {
notifyListeners(); notifyListeners();
} }
} catch (e) { } catch (e) {
MediaService.audioPlayer.dispose(); // MediaService.resetAudioPlayer();
update();
appState = AppState.idle;
rethrow; rethrow;
} }
} }
@ -163,7 +165,7 @@ class StudioDetailsState extends CoreProvier {
} }
} else { } else {
MediaService.audioPlayer.pause(); MediaService.audioPlayer.pause();
MediaService.audioPlayer.dispose(); // MediaService.audioPlayer.dispose();
} }
} }

View File

@ -0,0 +1,338 @@
import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
import '../../../config/design_config.dart';
import '../../../constants/app_icons.dart';
import '../../../models/enums.dart';
import '../../../models/requests/radar.dart';
import '../../../routes/routes.dart';
import '../../../services/media/media.dart';
import '../../../utils/action_sheet.dart';
import '../../podcasts/podcasts_state.dart';
import '../../podcasts/studio_details/studio_details_state.dart';
import '../../podcasts/studio_details/widgets/studio_details_widget.dart';
import '../didvan/icon_button.dart';
import '../didvan/text.dart';
import '../skeleton_image.dart';
import 'audio_player_widget.dart';
import 'audio_slider.dart';
class PlayerNavBar extends StatefulWidget {
final bool inHome;
const PlayerNavBar({Key? key, required this.inHome}) : super(key: key);
@override
State<PlayerNavBar> createState() => _PlayerNavBarState();
}
class _PlayerNavBarState extends State<PlayerNavBar> {
bool _enablePlayerController(StudioDetailsState state) =>
MediaService.currentPodcast != null ||
(MediaService.audioPlayerTag?.contains('podcast') ?? false);
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: MediaService.audioPlayer.isPlaying,
builder: (context, isPlaying) => GestureDetector(
onTap: () => (MediaService.currentPodcast == null &&
(MediaService.audioPlayerTag ?? '')
.split('-')[1]
.isNotEmpty) ||
MediaService.currentPodcast?.description == 'radar'
? Navigator.of(context).pushNamed(
Routes.radarDetails,
arguments: {
'onMarkChanged': (id, value) {},
'onCommentsChanged': (id, value) {},
'id': MediaService.currentPodcast?.id,
'args': const RadarRequestArgs(page: 0),
'hasUnmarkConfirmation': false,
},
)
: (MediaService.audioPlayerTag ?? '').split('-')[1].isNotEmpty
? _showPlayerBottomSheet(context)
: null,
child: Consumer<StudioDetailsState>(
builder: (context, state, child) => AnimatedContainer(
height: widget.inHome
? _enablePlayerController(state)
? 72 + 32 + 32
: 72
: null,
duration: DesignConfig.lowAnimationDuration,
decoration: BoxDecoration(
color: DesignConfig.isDark
? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.navigation,
borderRadius: BorderRadius.vertical(
top: const Radius.circular(16),
bottom:
widget.inHome ? Radius.zero : const Radius.circular(16)),
),
alignment: widget.inHome ? Alignment.topCenter : Alignment.center,
child: Builder(builder: (context) {
if (!_enablePlayerController(state)) {
return const SizedBox();
}
if (state.appState == AppState.failed) {
Future.delayed(const Duration(seconds: 2), () {
MediaService.resetAudioPlayer();
state.update();
// _enablePlayerController(state);
});
return Padding(
padding: const EdgeInsets.only(top: 18.0),
child: DidvanText(
'اتصال اینترنت برقرار نمی‌باشد',
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
),
);
}
if (MediaService.currentPodcast == null) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 16)
.copyWith(bottom: widget.inHome ? 72 + 16 : 16),
child: Row(
children: [
DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: () async {
await MediaService.resetAudioPlayer();
},
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 48),
child: SpinKitThreeBounce(
size: 18,
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
),
),
),
),
],
),
);
}
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: () async {
await MediaService.resetAudioPlayer();
// _enablePlayerController(state);
// state.update();
},
),
const SizedBox(width: 8),
SkeletonImage(
imageUrl: MediaService.currentPodcast!.image,
width: 32,
height: 32,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
MediaService.currentPodcast!.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
),
AudioSlider(
disableThumb: true,
tag: MediaService.audioPlayerTag!,
),
],
),
),
const SizedBox(
width: 8,
),
// StreamBuilder<PlayingAudio?>(
// stream: MediaService.audioPlayer.onReadyToPlay,
// builder: (context, snapshot) {
// if (snapshot.data == null ||
// state.appState == AppState.busy &&
// MediaService.currentPodcast?.description !=
// 'radar') {
// return SizedBox(
// height: 18,
// width: 18,
// child: CircularProgressIndicator(
// strokeWidth: 2,
// color: DesignConfig.isDark
// ? Theme.of(context).colorScheme.title
// : Theme.of(context).colorScheme.secondCTA,
// ),
// );
// }
// return const SizedBox();
// },
// ),
(state.appState != AppState.busy &&
isPlaying.data != null ||
MediaService.currentPodcast?.description == 'radar')
? DidvanIconButton(
gestureSize: 28,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
icon: isPlaying.data!
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
onPressed: () {
if (state.args?.type == 'video') {
state.getStudioDetails(
MediaService.currentPodcast!.id,
args: state.podcastArgs,
fetchOnly: true,
);
}
MediaService.handleAudioPlayback(
audioSource: MediaService.currentPodcast!.link,
id: MediaService.currentPodcast!.id,
isVoiceMessage: false,
);
},
)
: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
),
)
],
),
);
}),
),
),
),
);
}
void _showPlayerBottomSheet(BuildContext context) {
final sheetKey = GlobalKey<ExpandableBottomSheetState>();
bool isExpanded = false;
final detailsState = context.read<StudioDetailsState>();
if (detailsState.args?.type == 'video') {
detailsState.getStudioDetails(
MediaService.currentPodcast!.id,
args: detailsState.podcastArgs,
fetchOnly: true,
);
}
final state = context.read<PodcastsState>();
showModalBottomSheet(
constraints: BoxConstraints(
maxWidth: ActionSheetUtils.mediaQueryData.size.width,
),
backgroundColor: Colors.transparent,
context: context,
isScrollControlled: true,
builder: (context) => ChangeNotifierProvider<PodcastsState>.value(
value: state,
child: Consumer<StudioDetailsState>(
builder: (context, state, child) => MediaQuery(
data: ActionSheetUtils.mediaQueryData,
child: ExpandableBottomSheet(
key: sheetKey,
background: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
color: Theme.of(context).colorScheme.surface,
),
),
persistentHeader: GestureDetector(
onVerticalDragUpdate: (details) {
if (details.delta.dy > 10) {
Navigator.of(context).pop();
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AudioPlayerWidget(
podcast: MediaService.currentPodcast!,
),
Container(
width: MediaQuery.of(context).size.width,
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
DidvanIconButton(
size: 32,
icon: DidvanIcons.angle_up_regular,
onPressed: () {
if (!isExpanded) {
sheetKey.currentState?.expand();
isExpanded = true;
} else {
isExpanded = false;
sheetKey.currentState?.contract();
}
},
),
const SizedBox(height: 16),
],
),
),
],
),
),
expandableContent: state.appState == AppState.busy
? Container(
height: MediaQuery.of(context).size.height / 2,
alignment: Alignment.center,
child: SpinKitSpinningLines(
color: Theme.of(context).colorScheme.primary,
),
)
: StudioDetailsWidget(
onMarkChanged: (id, value) => context
.read<PodcastsState>()
.changeMark(id, value, true),
),
),
),
),
),
);
}
}

View File

@ -36,13 +36,17 @@ class DidvanAppBar extends StatelessWidget implements PreferredSizeWidget {
), ),
child: Row( child: Row(
children: [ children: [
IconButton( appBarData.hasBack? Column(
onPressed: () => Navigator.of(context).pop(), children: [
color: Theme.of(context).colorScheme.title, IconButton(
icon: const Icon( onPressed: () => Navigator.of(context).pop(),
DidvanIcons.back_regular, color: Theme.of(context).colorScheme.title,
), icon: const Icon(
), DidvanIcons.back_regular,
),
),
],
):const SizedBox(),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Align( child: Align(

View File

@ -16,10 +16,14 @@ import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart'; import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../audio/player_navbar.dart';
class DidvanBNB extends StatelessWidget { class DidvanBNB extends StatelessWidget {
final int currentTabIndex; final int currentTabIndex;
final void Function(int index) onTabChanged; final void Function(int index) onTabChanged;
@ -32,7 +36,7 @@ class DidvanBNB extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
children: [ children: [
const _PlayerNavBar(), const PlayerNavBar(inHome: true,),
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@ -92,310 +96,6 @@ class DidvanBNB extends StatelessWidget {
} }
} }
class _PlayerNavBar extends StatelessWidget {
const _PlayerNavBar({Key? key}) : super(key: key);
bool _enablePlayerController(StudioDetailsState state) =>
MediaService.currentPodcast != null ||
(MediaService.audioPlayerTag?.contains('podcast') ?? false);
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: MediaService.audioPlayer.isPlaying,
builder: (context, snapshot) => GestureDetector(
onTap: () => (MediaService.currentPodcast == null &&
(MediaService.audioPlayerTag ?? '')
.split('-')[1]
.isNotEmpty) ||
MediaService.currentPodcast?.description == 'radar'
? Navigator.of(context).pushNamed(
Routes.radarDetails,
arguments: {
'onMarkChanged': (id, value) {},
'onCommentsChanged': (id, value) {},
'id': MediaService.currentPodcast?.id,
'args': const RadarRequestArgs(page: 0),
'hasUnmarkConfirmation': false,
},
)
: (MediaService.audioPlayerTag ?? '').split('-')[1].isNotEmpty
? _showPlayerBottomSheet(context)
: null,
child: Consumer<StudioDetailsState>(
builder: (context, state, child) => AnimatedContainer(
padding: const EdgeInsets.only(top: 12),
duration: DesignConfig.lowAnimationDuration,
height: _enablePlayerController(state) ? 128 : 72,
decoration: BoxDecoration(
color: DesignConfig.isDark
? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.navigation,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(16),
),
),
alignment: Alignment.topCenter,
child: Builder(builder: (context) {
if (!_enablePlayerController(state)) return const SizedBox();
if (state.appState == AppState.failed) {
Future.delayed(const Duration(seconds: 2), () async{
await MediaService.resetAudioPlayer();
_enablePlayerController(state);
});
return DidvanText(
'اتصال اینترنت برقرار نمی‌باشد',
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
);
}
if (MediaService.currentPodcast == null) {
return SizedBox(
height: 32,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 16,
),
child: DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: () {
MediaService.resetAudioPlayer();
state.update();
},
),
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 48),
child: SpinKitThreeBounce(
size: 18,
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
),
),
),
),
],
),
);
}
return SizedBox(
height: 56,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 16,
),
child: DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: () async {
await MediaService.resetAudioPlayer();
_enablePlayerController(state);
},
),
),
SkeletonImage(
imageUrl: MediaService.currentPodcast!.image,
width: 32,
height: 32,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
MediaService.currentPodcast!.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
),
AudioSlider(
disableThumb: true,
tag: MediaService.audioPlayerTag!,
),
],
),
),
StreamBuilder<PlayingAudio?>(
stream: MediaService.audioPlayer.onReadyToPlay,
builder: (context, snapshot) {
if (snapshot.data == null ||
state.appState == AppState.busy &&
MediaService.currentPodcast?.description !=
'radar') {
return Padding(
padding: const EdgeInsets.only(
top: 4,
left: 16,
right: 16,
),
child: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
),
),
);
}
return const SizedBox();
},
),
if (state.appState != AppState.busy &&
snapshot.data != null ||
MediaService.currentPodcast?.description == 'radar')
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 16,
),
child: DidvanIconButton(
gestureSize: 28,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
icon: snapshot.data!
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
onPressed: () {
if (state.args?.type == 'video') {
state.getStudioDetails(
MediaService.currentPodcast!.id,
args: state.podcastArgs,
fetchOnly: true,
);
}
MediaService.handleAudioPlayback(
audioSource: MediaService.currentPodcast!.link,
id: MediaService.currentPodcast!.id,
isVoiceMessage: false,
);
},
),
),
],
),
);
}),
),
),
),
);
}
void _showPlayerBottomSheet(BuildContext context) {
final sheetKey = GlobalKey<ExpandableBottomSheetState>();
bool isExpanded = false;
final detailsState = context.read<StudioDetailsState>();
if (detailsState.args?.type == 'video') {
detailsState.getStudioDetails(
MediaService.currentPodcast!.id,
args: detailsState.podcastArgs,
fetchOnly: true,
);
}
final state = context.read<PodcastsState>();
showModalBottomSheet(
constraints: BoxConstraints(
maxWidth: ActionSheetUtils.mediaQueryData.size.width,
),
backgroundColor: Colors.transparent,
context: context,
isScrollControlled: true,
builder: (context) => ChangeNotifierProvider<PodcastsState>.value(
value: state,
child: Consumer<StudioDetailsState>(
builder: (context, state, child) => MediaQuery(
data: ActionSheetUtils.mediaQueryData,
child: ExpandableBottomSheet(
key: sheetKey,
background: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
color: Theme.of(context).colorScheme.surface,
),
),
persistentHeader: GestureDetector(
onVerticalDragUpdate: (details) {
if (details.delta.dy > 10) {
Navigator.of(context).pop();
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AudioPlayerWidget(
podcast: MediaService.currentPodcast!,
),
Container(
width: MediaQuery.of(context).size.width,
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
DidvanIconButton(
size: 32,
icon: DidvanIcons.angle_up_regular,
onPressed: () {
if (!isExpanded) {
sheetKey.currentState?.expand();
isExpanded = true;
} else {
isExpanded = false;
sheetKey.currentState?.contract();
}
},
),
const SizedBox(height: 16),
],
),
),
],
),
),
expandableContent: state.appState == AppState.busy
? Container(
height: MediaQuery.of(context).size.height / 2,
alignment: Alignment.center,
child: SpinKitSpinningLines(
color: Theme.of(context).colorScheme.primary,
),
)
: StudioDetailsWidget(
onMarkChanged: (id, value) => context
.read<PodcastsState>()
.changeMark(id, value, true),
),
),
),
),
),
);
}
}
class _NavBarItem extends StatelessWidget { class _NavBarItem extends StatelessWidget {
final VoidCallback onTap; final VoidCallback onTap;

View File

@ -22,6 +22,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../audio/player_navbar.dart';
class DidvanScaffold extends StatefulWidget { class DidvanScaffold extends StatefulWidget {
final List<Widget>? slivers; final List<Widget>? slivers;
final List<Widget>? children; final List<Widget>? children;
@ -63,11 +65,11 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
@override @override
void dispose() { void dispose() {
if(!widget.hidePlayer){ // if(!widget.hidePlayer){
if(MediaService.currentPodcast != null){ // if(MediaService.currentPodcast != null){
MediaService.audioPlayer.dispose(); // MediaService.audioPlayer.dispose();
} // }
} // }
super.dispose(); super.dispose();
} }
@ -85,7 +87,7 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).size.height - height: MediaQuery.of(context).size.height -
statusBarHeight - statusBarHeight -
systemNavigationBarHeight, systemNavigationBarHeight ,
child: Stack( child: Stack(
children: [ children: [
CustomScrollView( CustomScrollView(
@ -143,6 +145,8 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
12, 12,
), ),
), ),
if(!widget.hidePlayer)
const SliverPadding(padding: EdgeInsets.only(bottom: 90))
], ],
), ),
if (widget.reverse && widget.appBarData != null) if (widget.reverse && widget.appBarData != null)
@ -152,10 +156,16 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
), ),
if (!widget.hidePlayer) if (!widget.hidePlayer)
const Positioned( const Positioned(
bottom: 20, bottom: 16,
left: 0, left: 16,
right: 0, right: 16,
child: _PlayerNavBar(), child: SizedBox(
child: Center(
child: PlayerNavBar(
inHome: false,
),
),
),
), ),
], ],
), ),
@ -168,6 +178,7 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
class _AppBar extends StatefulWidget { class _AppBar extends StatefulWidget {
final AppBarData appBarData; final AppBarData appBarData;
final ScrollController scrollController; final ScrollController scrollController;
const _AppBar({ const _AppBar({
Key? key, Key? key,
required this.appBarData, required this.appBarData,
@ -209,272 +220,272 @@ class __AppBarState extends State<_AppBar> {
} }
} }
class _PlayerNavBar extends StatelessWidget { // class _PlayerNavBar extends StatelessWidget {
const _PlayerNavBar({Key? key}) : super(key: key); // const _PlayerNavBar({Key? key}) : super(key: key);
//
bool _enablePlayerController(StudioDetailsState state) => // bool _enablePlayerController(StudioDetailsState state) =>
MediaService.currentPodcast != null || // MediaService.currentPodcast != null ||
(MediaService.audioPlayerTag?.contains('podcast') ?? false); // (MediaService.audioPlayerTag?.contains('podcast') ?? false);
//
//
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return StreamBuilder<bool>( // return StreamBuilder<bool>(
stream: MediaService.audioPlayer.isPlaying, // stream: MediaService.audioPlayer.isPlaying,
builder: (context, snapshot) => GestureDetector( // builder: (context, snapshot) => GestureDetector(
onTap: () => MediaService.currentPodcast == null || // onTap: () => MediaService.currentPodcast == null ||
MediaService.currentPodcast?.description == 'radar' // MediaService.currentPodcast?.description == 'radar'
? Navigator.of(context).pushNamed( // ? Navigator.of(context).pushNamed(
Routes.radarDetails, // Routes.radarDetails,
arguments: { // arguments: {
'onMarkChanged': (id, value) {}, // 'onMarkChanged': (id, value) {},
'onCommentsChanged': (id, value) {}, // 'onCommentsChanged': (id, value) {},
'id': MediaService.currentPodcast?.id, // 'id': MediaService.currentPodcast?.id,
'args': const RadarRequestArgs(page: 0), // 'args': const RadarRequestArgs(page: 0),
'hasUnmarkConfirmation': false, // 'hasUnmarkConfirmation': false,
}, // },
) // )
: _showPlayerBottomSheet(context), // : _showPlayerBottomSheet(context),
child: Consumer<StudioDetailsState>( // child: Consumer<StudioDetailsState>(
builder: (context, state, child) => AnimatedContainer( // builder: (context, state, child) => AnimatedContainer(
padding: const EdgeInsets.only(top: 12), // padding: const EdgeInsets.only(top: 12),
duration: DesignConfig.lowAnimationDuration, // duration: DesignConfig.lowAnimationDuration,
height: _enablePlayerController(state) ? 60 : 0, // height: _enablePlayerController(state) ? 60 : 0,
margin: const EdgeInsets.symmetric(horizontal: 20), // margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration( // decoration: BoxDecoration(
color: DesignConfig.isDark // color: DesignConfig.isDark
? Theme.of(context).colorScheme.focused // ? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.navigation, // : Theme.of(context).colorScheme.navigation,
borderRadius: BorderRadius.circular(200), // borderRadius: BorderRadius.circular(200),
), // ),
alignment: Alignment.topCenter, // alignment: Alignment.topCenter,
child: Builder(builder: (context) { // child: Builder(builder: (context) {
if (!_enablePlayerController(state)) return const SizedBox(); // if (!_enablePlayerController(state)) return const SizedBox();
if (state.appState == AppState.failed) { // if (state.appState == AppState.failed) {
Future.delayed(const Duration(seconds: 2), () { // Future.delayed(const Duration(seconds: 2), () {
MediaService.resetAudioPlayer(); // MediaService.resetAudioPlayer();
}); // });
return DidvanText( // return DidvanText(
'اتصال اینترنت برقرار نمی‌باشد', // 'اتصال اینترنت برقرار نمی‌باشد',
color: DesignConfig.isDark // color: DesignConfig.isDark
? Theme.of(context).colorScheme.title // ? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
); // );
} // }
if (MediaService.currentPodcast == null) { // if (MediaService.currentPodcast == null) {
return SizedBox( // return SizedBox(
height: 32, // height: 32,
child: Center( // child: Center(
child: SpinKitThreeBounce( // child: SpinKitThreeBounce(
size: 18, // size: 18,
color: DesignConfig.isDark // color: DesignConfig.isDark
? Theme.of(context).colorScheme.title // ? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
), // ),
), // ),
); // );
} // }
return SizedBox( // return SizedBox(
height: 56, // height: 56,
child: Row( // child: Row(
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
children: [ // children: [
Padding( // Padding(
padding: const EdgeInsets.only( // padding: const EdgeInsets.only(
right: 12, // right: 12,
left: 8, // left: 8,
), // ),
child: DidvanIconButton( // child: DidvanIconButton(
icon: DidvanIcons.close_regular, // icon: DidvanIcons.close_regular,
color: DesignConfig.isDark // color: DesignConfig.isDark
? null // ? null
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
gestureSize: 32, // gestureSize: 32,
onPressed: MediaService.resetAudioPlayer, // onPressed: MediaService.resetAudioPlayer,
), // ),
), // ),
SkeletonImage( // SkeletonImage(
imageUrl: MediaService.currentPodcast!.image, // imageUrl: MediaService.currentPodcast!.image,
width: 32, // width: 32,
height: 32, // height: 32,
), // ),
const SizedBox(width: 16), // const SizedBox(width: 16),
Expanded( // Expanded(
child: Column( // child: Column(
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
children: [ // children: [
DidvanText( // DidvanText(
MediaService.currentPodcast!.title, // MediaService.currentPodcast!.title,
maxLines: 1, // maxLines: 1,
overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
color: DesignConfig.isDark // color: DesignConfig.isDark
? null // ? null
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
), // ),
AudioSlider( // AudioSlider(
disableThumb: true, // disableThumb: true,
tag: MediaService.audioPlayerTag!, // tag: MediaService.audioPlayerTag!,
), // ),
], // ],
), // ),
), // ),
StreamBuilder<PlayingAudio?>( // StreamBuilder<PlayingAudio?>(
stream: MediaService.audioPlayer.onReadyToPlay, // stream: MediaService.audioPlayer.onReadyToPlay,
builder: (context, snapshot) { // builder: (context, snapshot) {
if (snapshot.data == null || // if (snapshot.data == null ||
state.appState == AppState.busy && // state.appState == AppState.busy &&
MediaService.currentPodcast?.description != // MediaService.currentPodcast?.description !=
'radar') { // 'radar') {
return Padding( // return Padding(
padding: const EdgeInsets.only( // padding: const EdgeInsets.only(
top: 4, // top: 4,
left: 16, // left: 16,
right: 16, // right: 16,
), // ),
child: SizedBox( // child: SizedBox(
height: 18, // height: 18,
width: 18, // width: 18,
child: CircularProgressIndicator( // child: CircularProgressIndicator(
strokeWidth: 2, // strokeWidth: 2,
color: DesignConfig.isDark // color: DesignConfig.isDark
? Theme.of(context).colorScheme.title // ? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
), // ),
), // ),
); // );
} // }
return const SizedBox(); // return const SizedBox();
}, // },
), // ),
if (state.appState != AppState.busy && // if (state.appState != AppState.busy &&
snapshot.data != null || // snapshot.data != null ||
MediaService.currentPodcast?.description == 'radar') // MediaService.currentPodcast?.description == 'radar')
Padding( // Padding(
padding: const EdgeInsets.only( // padding: const EdgeInsets.only(
left: 12, // left: 12,
right: 12, // right: 12,
), // ),
child: DidvanIconButton( // child: DidvanIconButton(
gestureSize: 32, // gestureSize: 32,
color: DesignConfig.isDark // color: DesignConfig.isDark
? null // ? null
: Theme.of(context).colorScheme.secondCTA, // : Theme.of(context).colorScheme.secondCTA,
icon: snapshot.data! // icon: snapshot.data!
? DidvanIcons.pause_solid // ? DidvanIcons.pause_solid
: DidvanIcons.play_solid, // : DidvanIcons.play_solid,
onPressed: () { // onPressed: () {
if (state.args?.type == 'video') { // if (state.args?.type == 'video') {
state.getStudioDetails( // state.getStudioDetails(
MediaService.currentPodcast!.id, // MediaService.currentPodcast!.id,
args: state.podcastArgs, // args: state.podcastArgs,
fetchOnly: true, // fetchOnly: true,
); // );
} // }
MediaService.handleAudioPlayback( // MediaService.handleAudioPlayback(
audioSource: MediaService.currentPodcast!.link, // audioSource: MediaService.currentPodcast!.link,
id: MediaService.currentPodcast!.id, // id: MediaService.currentPodcast!.id,
isVoiceMessage: false, // isVoiceMessage: false,
); // );
}, // },
), // ),
), // ),
], // ],
), // ),
); // );
}), // }),
), // ),
), // ),
), // ),
); // );
} // }
//
void _showPlayerBottomSheet(BuildContext context) { // void _showPlayerBottomSheet(BuildContext context) {
final sheetKey = GlobalKey<ExpandableBottomSheetState>(); // final sheetKey = GlobalKey<ExpandableBottomSheetState>();
bool isExpanded = false; // bool isExpanded = false;
final detailsState = context.read<StudioDetailsState>(); // final detailsState = context.read<StudioDetailsState>();
if (detailsState.args?.type == 'video') { // if (detailsState.args?.type == 'video') {
detailsState.getStudioDetails( // detailsState.getStudioDetails(
MediaService.currentPodcast!.id, // MediaService.currentPodcast!.id,
args: detailsState.podcastArgs, // args: detailsState.podcastArgs,
fetchOnly: true, // fetchOnly: true,
); // );
} // }
final state = context.read<PodcastsState>(); // final state = context.read<PodcastsState>();
showModalBottomSheet( // showModalBottomSheet(
constraints: BoxConstraints( // constraints: BoxConstraints(
maxWidth: ActionSheetUtils.mediaQueryData.size.width, // maxWidth: ActionSheetUtils.mediaQueryData.size.width,
), // ),
backgroundColor: Colors.transparent, // backgroundColor: Colors.transparent,
context: context, // context: context,
isScrollControlled: true, // isScrollControlled: true,
builder: (context) => ChangeNotifierProvider<PodcastsState>.value( // builder: (context) => ChangeNotifierProvider<PodcastsState>.value(
value: state, // value: state,
child: Consumer<StudioDetailsState>( // child: Consumer<StudioDetailsState>(
builder: (context, state, child) => MediaQuery( // builder: (context, state, child) => MediaQuery(
data: ActionSheetUtils.mediaQueryData, // data: ActionSheetUtils.mediaQueryData,
child: ExpandableBottomSheet( // child: ExpandableBottomSheet(
key: sheetKey, // key: sheetKey,
background: Align( // background: Align(
alignment: Alignment.bottomCenter, // alignment: Alignment.bottomCenter,
child: Container( // child: Container(
height: MediaQuery.of(context).size.height * 0.7, // height: MediaQuery.of(context).size.height * 0.7,
color: Theme.of(context).colorScheme.surface, // color: Theme.of(context).colorScheme.surface,
), // ),
), // ),
persistentHeader: GestureDetector( // persistentHeader: GestureDetector(
onVerticalDragUpdate: (details) { // onVerticalDragUpdate: (details) {
if (details.delta.dy > 10) { // if (details.delta.dy > 10) {
Navigator.of(context).pop(); // Navigator.of(context).pop();
} // }
}, // },
child: Column( // child: Column(
crossAxisAlignment: CrossAxisAlignment.center, // crossAxisAlignment: CrossAxisAlignment.center,
children: [ // children: [
AudioPlayerWidget( // AudioPlayerWidget(
podcast: MediaService.currentPodcast!, // podcast: MediaService.currentPodcast!,
), // ),
Container( // Container(
width: MediaQuery.of(context).size.width, // width: MediaQuery.of(context).size.width,
color: Theme.of(context).colorScheme.surface, // color: Theme.of(context).colorScheme.surface,
child: Column( // child: Column(
children: [ // children: [
DidvanIconButton( // DidvanIconButton(
size: 32, // size: 32,
icon: DidvanIcons.angle_up_regular, // icon: DidvanIcons.angle_up_regular,
onPressed: () { // onPressed: () {
if (!isExpanded) { // if (!isExpanded) {
sheetKey.currentState?.expand(); // sheetKey.currentState?.expand();
isExpanded = true; // isExpanded = true;
} else { // } else {
isExpanded = false; // isExpanded = false;
sheetKey.currentState?.contract(); // sheetKey.currentState?.contract();
} // }
}, // },
), // ),
const SizedBox(height: 16), // const SizedBox(height: 16),
], // ],
), // ),
), // ),
], // ],
), // ),
), // ),
expandableContent: state.appState == AppState.busy // expandableContent: state.appState == AppState.busy
? Container( // ? Container(
height: MediaQuery.of(context).size.height / 2, // height: MediaQuery.of(context).size.height / 2,
alignment: Alignment.center, // alignment: Alignment.center,
child: SpinKitSpinningLines( // child: SpinKitSpinningLines(
color: Theme.of(context).colorScheme.primary, // color: Theme.of(context).colorScheme.primary,
), // ),
) // )
: StudioDetailsWidget( // : StudioDetailsWidget(
onMarkChanged: (id, value) => context // onMarkChanged: (id, value) => context
.read<PodcastsState>() // .read<PodcastsState>()
.changeMark(id, value, true), // .changeMark(id, value, true),
), // ),
), // ),
), // ),
), // ),
), // ),
); // );
} // }
} // }

View File

@ -130,6 +130,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
return; return;
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
icon: DidvanIcons.back_regular, icon: DidvanIcons.back_regular,
), ),

View File

@ -73,6 +73,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.3"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: "752039d6aa752597c98ec212e9759519061759e402e7da59a511f39d43aa07d2"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4
url: "https://pub.dev"
source: hosted
version: "5.0.0"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b
url: "https://pub.dev"
source: hosted
version: "6.0.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: db8fc420dadf80da18e2286c18e746fb4c3b2c5adbf0c963299dde046828886d
url: "https://pub.dev"
source: hosted
version: "5.0.0"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
awesome_notifications: awesome_notifications:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -85,6 +85,7 @@ dependencies:
android_intent_plus: ^5.0.0 android_intent_plus: ^5.0.0
get: ^4.6.6 get: ^4.6.6
firebase_auth: ^4.19.6 firebase_auth: ^4.19.6
audioplayers: ^6.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: