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() {
WidgetsBinding.instance.removeObserver(this);
// MediaService.audioPlayer.dispose();
if(MediaService.currentPodcast != null){
MediaService.audioPlayer.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';
import 'package:didvan/views/splash/splash.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/material.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,6 +24,7 @@ class MediaService {
bool isVoiceMessage = true,
void Function(bool isNext)? onTrackChanged,
}) async {
try {
String tag;
tag =
'${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id';
@ -33,9 +34,10 @@ class MediaService {
}
if (audioPlayerTag == tag) {
await audioPlayer.playOrPause();
return;
}
await audioPlayer.stop();
// await audioPlayer.stop();
audioPlayerTag = tag;
Audio audio;
String source;
@ -75,19 +77,17 @@ class MediaService {
customPrevAction: (_) => onTrackChanged?.call(false),
),
);
} catch (e) {
// resetAudioPlayer();
rethrow;
}
}
static Future<void> resetAudioPlayer() async {
audioPlayerTag = null;
currentPodcast = null;
podcastPlaylistArgs = null;
if(MediaService.audioPlayer.isPlaying.value){
MediaService.audioPlayer.stop();
}else{
MediaService.audioPlayer.dispose();
}
await MediaService.audioPlayer.stop();
}
static Future<XFile?> pickImage({required ImageSource source}) async {

View File

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

@ -35,6 +35,8 @@ class DidvanAppBar extends StatelessWidget implements PreferredSizeWidget {
color: backgroundColor ?? Theme.of(context).colorScheme.background,
),
child: Row(
children: [
appBarData.hasBack? Column(
children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
@ -43,6 +45,8 @@ class DidvanAppBar extends StatelessWidget implements PreferredSizeWidget {
DidvanIcons.back_regular,
),
),
],
):const SizedBox(),
const SizedBox(width: 16),
Expanded(
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/skeleton_image.dart';
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
import '../audio/player_navbar.dart';
class DidvanBNB extends StatelessWidget {
final int currentTabIndex;
final void Function(int index) onTabChanged;
@ -32,7 +36,7 @@ class DidvanBNB extends StatelessWidget {
Widget build(BuildContext context) {
return Stack(
children: [
const _PlayerNavBar(),
const PlayerNavBar(inHome: true,),
Positioned(
bottom: 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 {
final VoidCallback onTap;

View File

@ -22,6 +22,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
import '../audio/player_navbar.dart';
class DidvanScaffold extends StatefulWidget {
final List<Widget>? slivers;
final List<Widget>? children;
@ -63,11 +65,11 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
@override
void dispose() {
if(!widget.hidePlayer){
if(MediaService.currentPodcast != null){
MediaService.audioPlayer.dispose();
}
}
// if(!widget.hidePlayer){
// if(MediaService.currentPodcast != null){
// MediaService.audioPlayer.dispose();
// }
// }
super.dispose();
}
@ -85,7 +87,7 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
child: SizedBox(
height: MediaQuery.of(context).size.height -
statusBarHeight -
systemNavigationBarHeight,
systemNavigationBarHeight ,
child: Stack(
children: [
CustomScrollView(
@ -143,6 +145,8 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
12,
),
),
if(!widget.hidePlayer)
const SliverPadding(padding: EdgeInsets.only(bottom: 90))
],
),
if (widget.reverse && widget.appBarData != null)
@ -152,10 +156,16 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
),
if (!widget.hidePlayer)
const Positioned(
bottom: 20,
left: 0,
right: 0,
child: _PlayerNavBar(),
bottom: 16,
left: 16,
right: 16,
child: SizedBox(
child: Center(
child: PlayerNavBar(
inHome: false,
),
),
),
),
],
),
@ -168,6 +178,7 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
class _AppBar extends StatefulWidget {
final AppBarData appBarData;
final ScrollController scrollController;
const _AppBar({
Key? key,
required this.appBarData,
@ -209,272 +220,272 @@ class __AppBarState extends State<_AppBar> {
}
}
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.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,
},
)
: _showPlayerBottomSheet(context),
child: Consumer<StudioDetailsState>(
builder: (context, state, child) => AnimatedContainer(
padding: const EdgeInsets.only(top: 12),
duration: DesignConfig.lowAnimationDuration,
height: _enablePlayerController(state) ? 60 : 0,
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: DesignConfig.isDark
? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.navigation,
borderRadius: BorderRadius.circular(200),
),
alignment: Alignment.topCenter,
child: Builder(builder: (context) {
if (!_enablePlayerController(state)) return const SizedBox();
if (state.appState == AppState.failed) {
Future.delayed(const Duration(seconds: 2), () {
MediaService.resetAudioPlayer();
});
return DidvanText(
'اتصال اینترنت برقرار نمی‌باشد',
color: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.secondCTA,
);
}
if (MediaService.currentPodcast == null) {
return SizedBox(
height: 32,
child: Center(
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: 8,
),
child: DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 32,
onPressed: MediaService.resetAudioPlayer,
),
),
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: 12,
),
child: DidvanIconButton(
gestureSize: 32,
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 _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.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,
// },
// )
// : _showPlayerBottomSheet(context),
// child: Consumer<StudioDetailsState>(
// builder: (context, state, child) => AnimatedContainer(
// padding: const EdgeInsets.only(top: 12),
// duration: DesignConfig.lowAnimationDuration,
// height: _enablePlayerController(state) ? 60 : 0,
// margin: const EdgeInsets.symmetric(horizontal: 20),
// decoration: BoxDecoration(
// color: DesignConfig.isDark
// ? Theme.of(context).colorScheme.focused
// : Theme.of(context).colorScheme.navigation,
// borderRadius: BorderRadius.circular(200),
// ),
// alignment: Alignment.topCenter,
// child: Builder(builder: (context) {
// if (!_enablePlayerController(state)) return const SizedBox();
// if (state.appState == AppState.failed) {
// Future.delayed(const Duration(seconds: 2), () {
// MediaService.resetAudioPlayer();
// });
// return DidvanText(
// 'اتصال اینترنت برقرار نمی‌باشد',
// color: DesignConfig.isDark
// ? Theme.of(context).colorScheme.title
// : Theme.of(context).colorScheme.secondCTA,
// );
// }
// if (MediaService.currentPodcast == null) {
// return SizedBox(
// height: 32,
// child: Center(
// 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: 8,
// ),
// child: DidvanIconButton(
// icon: DidvanIcons.close_regular,
// color: DesignConfig.isDark
// ? null
// : Theme.of(context).colorScheme.secondCTA,
// gestureSize: 32,
// onPressed: MediaService.resetAudioPlayer,
// ),
// ),
// 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: 12,
// ),
// child: DidvanIconButton(
// gestureSize: 32,
// 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),
// ),
// ),
// ),
// ),
// ),
// );
// }
// }

View File

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

View File

@ -73,6 +73,62 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:

View File

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