diff --git a/lib/main.dart b/lib/main.dart index d663c2d..d16f740 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/route_generator.dart'; @@ -21,6 +22,9 @@ class Didvan extends StatelessWidget { Widget build(BuildContext context) { return MultiProvider( providers: [ + ChangeNotifierProvider( + create: (context) => MediaProvider(), + ), ChangeNotifierProvider( create: (context) => UserProvider(), ), diff --git a/lib/providers/media.dart b/lib/providers/media.dart new file mode 100644 index 0000000..0be72e0 --- /dev/null +++ b/lib/providers/media.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:didvan/models/enums.dart'; +import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/storage/storage.dart'; + +class MediaProvider extends CoreProvier { + static final List downloadedItemIds = []; + final List downloadQueue = []; + + Future getDownloadsList() async { + downloadedItemIds.clear(); + final videosDir = Directory( + StorageService.appDocsDir + ('/videos'), + ); + final podcastsDir = Directory( + StorageService.appDocsDir + ('/podcasts'), + ); + if (!await videosDir.exists()) { + await videosDir.create(); + } + if (!await podcastsDir.exists()) { + await podcastsDir.create(); + } + videosDir.list(recursive: false).listen( + (event) { + downloadedItemIds.add( + int.parse( + event.path.split('/').last.split('-').last.split('.').first, + ), + ); + }, + ); + podcastsDir.list(recursive: false).listen( + (event) { + downloadedItemIds.add( + int.parse( + event.path.split('/').last.split('-').last.split('.').first, + ), + ); + }, + ); + await Future.delayed(const Duration(milliseconds: 300), notifyListeners); + } + + Future download({ + required String url, + required String fileName, + required bool isVideo, + }) async { + appState = AppState.busy; + downloadQueue.add(url); + notifyListeners(); + final service = RequestService(url); + await service.download(fileName, isVideo ? 'videos' : 'podcasts'); + downloadQueue.remove(url); + getDownloadsList(); + } +} diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index a6595fd..5e6d816 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -1,7 +1,9 @@ import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/studio_details_data.dart'; +import 'package:didvan/providers/media.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; +import 'package:didvan/services/storage/storage.dart'; import 'package:flutter/foundation.dart'; import 'package:image_picker/image_picker.dart'; import 'package:just_audio/just_audio.dart'; @@ -30,6 +32,10 @@ class MediaService { String tag; tag = '${isVoiceMessage ? 'message' : 'podcast'}-$id'; isNetworkAudio ??= audioSource.runtimeType == String; + if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) { + audioSource = StorageService.appDocsDir + '/podcasts/podcast-$id.mp3'; + isNetworkAudio = false; + } if (audioPlayerTag == tag) { if (audioPlayer.playing) { await audioPlayer.pause(); diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index eeabccc..d444a9a 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -165,7 +165,7 @@ class RequestService { } Future download(String fileName, String subDirectory) async { - Permission.storage.request(); + await Permission.storage.request(); final response = await http.get(Uri.parse(url)); StorageService.createFile( bytes: response.bodyBytes, diff --git a/lib/services/storage/storage.dart b/lib/services/storage/storage.dart index 6547633..ed607e8 100644 --- a/lib/services/storage/storage.dart +++ b/lib/services/storage/storage.dart @@ -19,7 +19,7 @@ class StorageService { await dir.create(recursive: true); } final file = await io.File( - appDocsDir + '/$subDirectory/podcast-$name.mp3', + appDocsDir + '/$subDirectory/$name', ).create(recursive: true); await file.writeAsBytes(bytes); } diff --git a/lib/views/home/studio/studio_details/studio_details.mobile.dart b/lib/views/home/studio/studio_details/studio_details.mobile.dart index 42f0aa7..4f9e77a 100644 --- a/lib/views/home/studio/studio_details/studio_details.mobile.dart +++ b/lib/views/home/studio/studio_details/studio_details.mobile.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; @@ -93,6 +94,9 @@ class _StudioDetailsState extends State { await _changeFullSceen(false); return false; } + if (MediaService.currentPodcast != null) { + state.studio = MediaService.currentPodcast!; + } return true; }, child: DidvanScaffold( @@ -205,7 +209,6 @@ class _StudioDetailsState extends State { children: [ StudioDetailsWidget( scrollController: _scrollController, - studio: state.studio, ), ], ), diff --git a/lib/views/home/studio/studio_details/studio_details.web.dart b/lib/views/home/studio/studio_details/studio_details.web.dart index f936205..7dd83af 100644 --- a/lib/views/home/studio/studio_details/studio_details.web.dart +++ b/lib/views/home/studio/studio_details/studio_details.web.dart @@ -2,6 +2,7 @@ import 'dart:ui' as ui; import 'package:didvan/config/design_config.dart'; import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; @@ -93,6 +94,9 @@ class _StudioDetailsState extends State { await _changeFullSceen(false); return false; } + if (MediaService.currentPodcast != null) { + state.studio = MediaService.currentPodcast!; + } return true; }, child: DidvanScaffold( @@ -144,7 +148,6 @@ class _StudioDetailsState extends State { children: [ StudioDetailsWidget( scrollController: _scrollController, - studio: state.studio, ), ], ), diff --git a/lib/views/home/studio/studio_details/studio_details_state.dart b/lib/views/home/studio/studio_details/studio_details_state.dart index 5151deb..b1843ab 100644 --- a/lib/views/home/studio/studio_details/studio_details_state.dart +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -6,6 +6,7 @@ import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/providers/media.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; @@ -17,9 +18,9 @@ class StudioDetailsState extends CoreProvier { StudioDetailsData? prevStudio; late int initialIndex; late StudioRequestArgs args; + StudioRequestArgs? podcastArgs; final List relatedQueue = []; bool _positionListenerActivated = false; - final List downloadedFileIds = []; int _selectedDetailsIndex = 0; Timer? timer; @@ -39,15 +40,19 @@ class StudioDetailsState extends CoreProvier { int id, { StudioRequestArgs? args, bool? isForward, + bool forceFetch = false, }) async { if (args != null) { this.args = args; } - if (MediaService.currentPodcast?.id == id && this.args.type == 'podcast') { + if (this.args.type == 'podcast') { + podcastArgs = this.args; + } + if (MediaService.currentPodcast?.id == id && + this.args.type == 'podcast' && + !forceFetch) { return; } - - _getDownloadsList(); _selectedDetailsIndex = 0; if (isForward != null) { if (isForward) { @@ -85,7 +90,7 @@ class StudioDetailsState extends CoreProvier { if (result['prevStudio'].isNotEmpty && this.args.page != 0) { prevStudio = StudioDetailsData.fromJson(result['prevStudio']); } - if (isForward == null) { + if (isForward == null && !forceFetch) { await _handlePodcastPlayback(studio); } appState = AppState.idle; @@ -100,14 +105,10 @@ class StudioDetailsState extends CoreProvier { if (args.type == 'podcast') { MediaService.currentPodcast = studio; MediaService.podcastPlaylistArgs = args; - final downloaded = downloadedFileIds.contains(studio.id); await MediaService.handleAudioPlayback( - audioSource: downloaded - ? StorageService.appDocsDir + '/podcasts/podcast-${studio.id}.mp3' - : studio.media, + audioSource: studio.media, id: studio.id, isVoiceMessage: false, - isNetworkAudio: !downloaded, ); if (nextStudio != null && !_positionListenerActivated) { _positionListenerActivated = true; @@ -129,25 +130,6 @@ class StudioDetailsState extends CoreProvier { } } - Future _getDownloadsList() async { - downloadedFileIds.clear(); - final dir = Directory( - StorageService.appDocsDir + ('/${args.type}s'), - ); - if (!await dir.exists()) { - await dir.create(); - } - dir.list(recursive: false).listen( - (event) { - downloadedFileIds.add( - int.parse( - event.path.split('/').last.split('-').last.split('.').first, - ), - ); - }, - ); - } - Future getRelatedContents() async { if (studio.relatedContents.isNotEmpty) return; relatedQueue.add(studio.id); diff --git a/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart index 832c9d3..67da8bf 100644 --- a/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart +++ b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart @@ -15,129 +15,131 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class StudioDetailsWidget extends StatelessWidget { - final StudioDetailsData studio; final ScrollController? scrollController; final VoidCallback? onCommentsTabSelected; const StudioDetailsWidget({ Key? key, - required this.studio, this.onCommentsTabSelected, this.scrollController, }) : super(key: key); - bool get _isVideo => studio.media.contains('iframe'); - @override Widget build(BuildContext context) { final ds = MediaQuery.of(context).size; return Consumer( - builder: (context, state, child) => Container( - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (!_isVideo) - DetailsTabBar( - isVideo: _isVideo, - onCommentsTabSelected: onCommentsTabSelected ?? () {}, - ), - const SizedBox(height: 16), - StateHandler( - onRetry: () {}, - state: state, - builder: (context, state) { - if (state.selectedDetailsIndex == 0) { - return SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - DidvanText(state.studio.description), - if (studio.tags.isNotEmpty) const SizedBox(height: 20), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - for (var i = 0; i < studio.tags.length; i++) - TagItem(tag: studio.tags[i]), - ], - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(), - if (state.nextStudio != null) - _StudioPreview( - isNext: true, - studio: state.nextStudio!, - scrollController: scrollController, - ), - if (state.prevStudio != null) - _StudioPreview( - isNext: false, - studio: state.prevStudio!, - scrollController: scrollController, - ), - const SizedBox(), - ], - ) - ], - ), - ); - } - if (state.selectedDetailsIndex == 1) { - return ChangeNotifierProvider( - create: (context) => CommentsState(), - child: SizedBox( - height: ds.height - - ds.width * 9 / 16 - - 144 - - MediaQuery.of(context).padding.top, - child: Comments( - pageData: { - 'id': studio.id, - 'type': 'studio', - 'title': studio.title, - 'onCommentsChanged': state.onCommentsChanged, - 'isPage': false, - }, + builder: (context, state, child) { + bool isVideo = state.studio.media.contains('iframe'); + return Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!isVideo) + DetailsTabBar( + isVideo: isVideo, + onCommentsTabSelected: onCommentsTabSelected ?? () {}, + ), + const SizedBox(height: 16), + StateHandler( + onRetry: () {}, + state: state, + builder: (context, state) { + if (state.selectedDetailsIndex == 0) { + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + DidvanText(state.studio.description), + if (state.studio.tags.isNotEmpty) + const SizedBox(height: 20), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (var i = 0; i < state.studio.tags.length; i++) + TagItem(tag: state.studio.tags[i]), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + if (state.nextStudio != null) + _StudioPreview( + isNext: true, + studio: state.nextStudio!, + scrollController: scrollController, + ), + if (state.prevStudio != null) + _StudioPreview( + isNext: false, + studio: state.prevStudio!, + scrollController: scrollController, + ), + const SizedBox(), + ], + ) + ], ), - ), - ); - } - return Column( - children: [ - if (studio.relatedContents.isEmpty) - for (var i = 0; i < 3; i++) + ); + } + if (state.selectedDetailsIndex == 1) { + return ChangeNotifierProvider( + create: (context) => CommentsState(), + child: SizedBox( + height: ds.height - + ds.width * 9 / 16 - + 144 - + MediaQuery.of(context).padding.top, + child: Comments( + pageData: { + 'id': state.studio.id, + 'type': 'studio', + 'title': state.studio.title, + 'onCommentsChanged': state.onCommentsChanged, + 'isPage': false, + }, + ), + ), + ); + } + return Column( + children: [ + if (state.studio.relatedContents.isEmpty) + for (var i = 0; i < 3; i++) + Padding( + padding: const EdgeInsets.only( + bottom: 8, + left: 16, + right: 16, + ), + child: MultitypeOverview.placeholder, + ), + for (var i = 0; + i < state.studio.relatedContents.length; + i++) Padding( padding: const EdgeInsets.only( bottom: 8, left: 16, right: 16, ), - child: MultitypeOverview.placeholder, + child: MultitypeOverview( + item: state.studio.relatedContents[i], + onMarkChanged: (id, value) {}, + ), ), - for (var i = 0; i < studio.relatedContents.length; i++) - Padding( - padding: const EdgeInsets.only( - bottom: 8, - left: 16, - right: 16, - ), - child: MultitypeOverview( - item: studio.relatedContents[i], - onMarkChanged: (id, value) {}, - ), - ), - ], - ); - }, - ), - ], - ), - ), + ], + ); + }, + ), + ], + ), + ); + }, ); } } diff --git a/lib/views/home/studio/studio_state.dart b/lib/views/home/studio/studio_state.dart index 8d1e40a..d80e609 100644 --- a/lib/views/home/studio/studio_state.dart +++ b/lib/views/home/studio/studio_state.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; @@ -9,15 +8,10 @@ import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; -import 'package:didvan/services/storage/storage.dart'; class StudioState extends CoreProvier { final List studios = []; final List sliders = []; - final List downloadedFileIds = []; - final List downloadQueue = []; - - AppState downloadState = AppState.idle; String search = ''; String lastSearch = ''; @@ -37,29 +31,9 @@ class StudioState extends CoreProvier { _videosSelected = value; selectedSortTypeIndex = 0; _getSliders(); - getDownloadsList(); getStudios(page: page); } - Future getDownloadsList() async { - downloadedFileIds.clear(); - final dir = Directory( - StorageService.appDocsDir + (videosSelected ? '/videos' : '/podcasts'), - ); - if (!await dir.exists()) { - await dir.create(); - } - dir.list(recursive: false).listen( - (event) { - downloadedFileIds.add( - int.parse( - event.path.split('/').last.split('-').last.split('.').first, - ), - ); - }, - ); - } - String get order { if (selectedSortTypeIndex == 0 || selectedSortTypeIndex == 1) return 'date'; if (selectedSortTypeIndex == 2) return 'view'; @@ -83,7 +57,6 @@ class StudioState extends CoreProvier { lastSearch = ''; _videosSelected = true; selectedSortTypeIndex = 0; - getDownloadsList(); Future.delayed(Duration.zero, () { _getSliders(); getStudios(page: 1); @@ -148,15 +121,4 @@ class StudioState extends CoreProvier { studios.firstWhere((radar) => radar.id == id).comments = count; notifyListeners(); } - - Future download(String url, String fileName) async { - downloadState = AppState.busy; - downloadQueue.add(url); - notifyListeners(); - final service = RequestService(url); - await service.download(fileName, videosSelected ? 'videos' : 'podcasts'); - downloadState = AppState.idle; - downloadQueue.remove(url); - notifyListeners(); - } } diff --git a/lib/views/home/studio/widgets/tab_bar.dart b/lib/views/home/studio/widgets/tab_bar.dart index b1ed37b..ca27288 100644 --- a/lib/views/home/studio/widgets/tab_bar.dart +++ b/lib/views/home/studio/widgets/tab_bar.dart @@ -76,7 +76,10 @@ class _StudioTypeButton extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, + onTap: () { + onTap(); + FocusScope.of(context).unfocus(); + }, child: Container( color: Colors.transparent, child: Column( diff --git a/lib/views/home/widgets/audio/audio_player_widget.dart b/lib/views/home/widgets/audio/audio_player_widget.dart index 300139d..2dfc3e7 100644 --- a/lib/views/home/widgets/audio/audio_player_widget.dart +++ b/lib/views/home/widgets/audio/audio_player_widget.dart @@ -7,6 +7,7 @@ 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/providers/media.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'; @@ -346,10 +347,6 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon> audioSource: widget.audioSource, isVoiceMessage: false, id: widget.id, - isNetworkAudio: !context - .read() - .downloadedFileIds - .contains(widget.id), ); _handleAnimation(); }, diff --git a/lib/views/home/widgets/overview/podcast.dart b/lib/views/home/widgets/overview/podcast.dart index f4cef1b..16533a7 100644 --- a/lib/views/home/widgets/overview/podcast.dart +++ b/lib/views/home/widgets/overview/podcast.dart @@ -3,9 +3,10 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/providers/media.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/date_time.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/bookmark_button.dart'; import 'package:didvan/views/home/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; @@ -21,19 +22,18 @@ import 'package:provider/provider.dart'; class PodcastOverview extends StatelessWidget { final OverviewData podcast; final void Function(int id, bool value) onMarkChanged; - final StudioRequestArgs? studioRequestArgs; + final StudioRequestArgs studioRequestArgs; final bool hasUnmarkConfirmation; const PodcastOverview({ Key? key, required this.podcast, required this.onMarkChanged, - this.studioRequestArgs, + required this.studioRequestArgs, this.hasUnmarkConfirmation = false, }) : super(key: key); @override Widget build(BuildContext context) { - final state = context.read(); return DidvanCard( onTap: () { context @@ -77,52 +77,57 @@ class PodcastOverview extends StatelessWidget { overflow: TextOverflow.ellipsis, ), const DidvanDivider(verticalPadding: 8), - Row( - children: [ - DurationWidget(duration: podcast.duration!), - const Spacer(), - if (!kIsWeb) ...[ - if (state.downloadState == AppState.idle || - !state.downloadQueue.contains(podcast.media)) - DidvanIconButton( - gestureSize: 28, - color: _isDownloaded(state) - ? Theme.of(context).colorScheme.primary - : null, - icon: _isDownloaded(state) - ? DidvanIcons.download_solid - : DidvanIcons.download_regular, - onPressed: _isDownloaded(state) - ? () {} - : () => state.download( - podcast.media!, podcast.id.toString()), - ), - if (state.downloadState == AppState.busy && - state.downloadQueue.contains(podcast.media)) - const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, + Consumer( + builder: (context, state, child) => Row( + children: [ + DurationWidget(duration: podcast.duration!), + const Spacer(), + if (!kIsWeb) ...[ + if (state.appState == AppState.idle || + !state.downloadQueue.contains(podcast.media)) + DidvanIconButton( + gestureSize: 28, + color: _isDownloaded + ? Theme.of(context).colorScheme.primary + : null, + icon: _isDownloaded + ? DidvanIcons.download_solid + : DidvanIcons.download_regular, + onPressed: _isDownloaded + ? () {} + : () => state.download( + fileName: 'podcast-${podcast.id}.mp3', + isVideo: false, + url: podcast.media!, + ), ), - ), - const SizedBox(width: 16), + if (state.appState == AppState.busy && + state.downloadQueue.contains(podcast.media)) + const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ), + const SizedBox(width: 16), + ], + BookmarkButton( + askForConfirmation: hasUnmarkConfirmation, + gestureSize: 32, + value: podcast.marked, + onMarkChanged: (value) => onMarkChanged(podcast.id, value), + ), ], - BookmarkButton( - askForConfirmation: hasUnmarkConfirmation, - gestureSize: 32, - value: podcast.marked, - onMarkChanged: (value) => onMarkChanged(podcast.id, value), - ), - ], + ), ), ], ), ); } - bool _isDownloaded(state) { - return state.downloadedFileIds.contains(podcast.id); + bool get _isDownloaded { + return MediaProvider.downloadedItemIds.contains(podcast.id); } static Widget get placeholder => DidvanCard( diff --git a/lib/views/home/widgets/overview/radar.dart b/lib/views/home/widgets/overview/radar.dart index fa79acd..856868d 100644 --- a/lib/views/home/widgets/overview/radar.dart +++ b/lib/views/home/widgets/overview/radar.dart @@ -102,13 +102,6 @@ class RadarOverview extends StatelessWidget { const DidvanDivider(), Row( children: [ - BookmarkButton( - gestureSize: 32, - value: radar.marked, - onMarkChanged: (value) => onMarkChanged(radar.id, value), - askForConfirmation: hasUnmarkConfirmation, - ), - const Spacer(), if (radar.comments != 0) DidvanText(radar.comments.toString()), const SizedBox(width: 4), DidvanIconButton( @@ -125,10 +118,17 @@ class RadarOverview extends StatelessWidget { }, ), ), - const SizedBox(width: 16), + // const SizedBox(width: 16), // const DidvanText('10'), // const SizedBox(width: 4), // const Icon(DidvanIcons.evaluation_regular), + const Spacer(), + BookmarkButton( + gestureSize: 32, + value: radar.marked, + onMarkChanged: (value) => onMarkChanged(radar.id, value), + askForConfirmation: hasUnmarkConfirmation, + ), ], ), ], diff --git a/lib/views/splash/splash.dart b/lib/views/splash/splash.dart index 72e3b26..b997efc 100644 --- a/lib/views/splash/splash.dart +++ b/lib/views/splash/splash.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/main.dart'; +import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/user_provider.dart'; @@ -109,6 +110,7 @@ class _SplashState extends State { final String? token = await userProvider.setAndGetToken(); if (token != null) { log(token); + context.read().getDownloadsList(); RequestService.token = token; final result = await userProvider.getUserInfo(); if (!result) { diff --git a/lib/views/widgets/didvan/bnb.dart b/lib/views/widgets/didvan/bnb.dart index 3c4b804..652c535 100644 --- a/lib/views/widgets/didvan/bnb.dart +++ b/lib/views/widgets/didvan/bnb.dart @@ -2,6 +2,7 @@ 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/providers/media.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; @@ -26,6 +27,73 @@ class DidvanBNB extends StatelessWidget { {Key? key, required this.currentTabIndex, required this.onTabChanged}) : super(key: key); + @override + Widget build(BuildContext context) { + return Stack( + children: [ + const _PlayerNavBar(), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + height: 72, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: + const BorderRadius.vertical(top: Radius.circular(16)), + boxShadow: DesignConfig.defaultShadow, + ), + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + _NavBarItem( + isSelected: currentTabIndex == 0, + title: 'اخبار', + selectedIcon: DidvanIcons.news_solid, + unselectedIcon: DidvanIcons.news_light, + onTap: () => onTabChanged(0), + ), + _NavBarItem( + isSelected: currentTabIndex == 1, + title: 'آمار', + selectedIcon: DidvanIcons.chart_solid, + unselectedIcon: DidvanIcons.chart_light, + onTap: () => onTabChanged(1), + ), + _NavBarItem( + isSelected: currentTabIndex == 2, + title: 'رادار', + selectedIcon: DidvanIcons.radar_solid, + unselectedIcon: DidvanIcons.radar_light, + onTap: () => onTabChanged(2), + ), + _NavBarItem( + isSelected: currentTabIndex == 3, + title: 'استودیو', + selectedIcon: DidvanIcons.play_circle_solid, + unselectedIcon: DidvanIcons.play_circle_light, + onTap: () => onTabChanged(3), + ), + _NavBarItem( + isSelected: currentTabIndex == 4, + title: 'تنظیمات', + selectedIcon: DidvanIcons.setting_solid, + unselectedIcon: DidvanIcons.setting_light, + onTap: () => onTabChanged(4), + ), + ], + ), + ), + ), + ], + ); + } +} + +class _PlayerNavBar extends StatelessWidget { + const _PlayerNavBar({Key? key}) : super(key: key); + bool _enablePlayerController(StudioDetailsState state) => MediaService.currentPodcast != null || (MediaService.audioPlayerTag?.contains('podcast') ?? false); @@ -33,227 +101,170 @@ class DidvanBNB extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder( - stream: MediaService.audioPlayer.playingStream, - builder: (context, snapshot) { - return Stack( - children: [ - GestureDetector( - onTap: () => MediaService.currentPodcast == null - ? null - : _showPlayerBottomSheet(context), - child: Consumer( - 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: !_enablePlayerController(state) - ? const SizedBox() - : MediaService.currentPodcast == null - ? SizedBox( - height: 32, - child: Center( - child: SpinKitThreeBounce( - size: 18, + stream: MediaService.audioPlayer.playingStream, + builder: (context, snapshot) => GestureDetector( + onTap: () => MediaService.currentPodcast == null + ? null + : _showPlayerBottomSheet(context), + child: Consumer( + 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: !_enablePlayerController(state) + ? const SizedBox() + : MediaService.currentPodcast == null + ? SizedBox( + height: 32, + child: Center( + child: SpinKitThreeBounce( + size: 18, + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.secondCTA, + ), + ), + ) + : 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: 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, color: DesignConfig.isDark ? null : Theme.of(context) .colorScheme .secondCTA, ), - ), - ) - : SizedBox( - height: 56, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - right: 12, - left: 16, - ), - child: DidvanIconButton( - icon: DidvanIcons.close_regular, + AudioSlider( + disableThumb: true, + tag: MediaService.audioPlayerTag!, + ), + ], + ), + ), + StreamBuilder( + stream: + MediaService.audioPlayer.playerStateStream, + builder: (context, snapshot) { + final playerState = MediaService + .audioPlayer.playerState.processingState; + if (playerState == ProcessingState.loading || + state.appState == AppState.busy) { + 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 - ? null + ? Theme.of(context) + .colorScheme + .title : Theme.of(context) .colorScheme .secondCTA, - gestureSize: 28, - 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, - color: DesignConfig.isDark - ? null - : Theme.of(context) - .colorScheme - .secondCTA, - ), - AudioSlider( - disableThumb: true, - tag: MediaService.audioPlayerTag!, - ), - ], - ), - ), - StreamBuilder( - stream: MediaService - .audioPlayer.playerStateStream, - builder: (context, snapshot) { - if (state.appState == AppState.busy || - MediaService.audioPlayer.playerState - .processingState == - ProcessingState.loading) { - return Padding( - padding: const EdgeInsets.only( - top: 4, - left: 16, - right: 16, - ), - child: SizedBox( - height: 18, - width: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Theme.of(context) - .colorScheme - .secondCTA, - ), - ), - ); - } - return const SizedBox(); - }, - ), - if (state.appState != AppState.busy && - MediaService.audioPlayer.playerState - .processingState != - ProcessingState.loading) - 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: () { - MediaService.handleAudioPlayback( - audioSource: state - .downloadedFileIds - .contains(state.studio.id) - ? StorageService.appDocsDir + - '/podcasts/podcast-${state.studio.id}.mp3' - : state.studio.media, - isNetworkAudio: !state - .downloadedFileIds - .contains(state.studio.id), - id: state.studio.id, - isVoiceMessage: false, - ); - }, - ), - ), - ], + ); + } + return const SizedBox(); + }, + ), + if (state.appState != AppState.busy && + MediaService.audioPlayer.playerState + .processingState != + ProcessingState.loading) + 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, + forceFetch: true, + ); + } + MediaService.handleAudioPlayback( + audioSource: + MediaService.currentPodcast!.media, + id: MediaService.currentPodcast!.id, + isVoiceMessage: false, + ); + }, ), ), - ), - ), - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Container( - height: 72, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: - const BorderRadius.vertical(top: Radius.circular(16)), - boxShadow: DesignConfig.defaultShadow, - ), - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - _NavBarItem( - isSelected: currentTabIndex == 0, - title: 'اخبار', - selectedIcon: DidvanIcons.news_solid, - unselectedIcon: DidvanIcons.news_light, - onTap: () => onTabChanged(0), + ], + ), ), - _NavBarItem( - isSelected: currentTabIndex == 1, - title: 'آمار', - selectedIcon: DidvanIcons.chart_solid, - unselectedIcon: DidvanIcons.chart_light, - onTap: () => onTabChanged(1), - ), - _NavBarItem( - isSelected: currentTabIndex == 2, - title: 'رادار', - selectedIcon: DidvanIcons.radar_solid, - unselectedIcon: DidvanIcons.radar_light, - onTap: () => onTabChanged(2), - ), - _NavBarItem( - isSelected: currentTabIndex == 3, - title: 'استودیو', - selectedIcon: DidvanIcons.play_circle_solid, - unselectedIcon: DidvanIcons.play_circle_light, - onTap: () => onTabChanged(3), - ), - _NavBarItem( - isSelected: currentTabIndex == 4, - title: 'تنظیمات', - selectedIcon: DidvanIcons.setting_solid, - unselectedIcon: DidvanIcons.setting_light, - onTap: () => onTabChanged(4), - ), - ], - ), - ), - ), - ], - ); - }); + ), + ), + ), + ); } void _showPlayerBottomSheet(BuildContext context) { final sheetKey = GlobalKey(); bool isExpanded = false; final detailsState = context.read(); + if (detailsState.args.type == 'video') { + detailsState.getStudioDetails( + MediaService.currentPodcast!.id, + args: detailsState.podcastArgs, + forceFetch: true, + ); + } final state = context.read(); showModalBottomSheet( backgroundColor: Colors.transparent, @@ -288,23 +299,18 @@ class DidvanBNB extends StatelessWidget { color: Theme.of(context).colorScheme.surface, child: Column( children: [ - StatefulBuilder( - builder: (context, setState) => DidvanIconButton( - size: 32, - icon: isExpanded - ? DidvanIcons.angle_down_regular - : DidvanIcons.angle_up_regular, - onPressed: () { - if (!isExpanded) { - sheetKey.currentState?.expand(); - isExpanded = true; - } else { - isExpanded = false; - sheetKey.currentState?.contract(); - } - setState(() {}); - }, - ), + 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), ], @@ -322,7 +328,6 @@ class DidvanBNB extends StatelessWidget { ), ) : StudioDetailsWidget( - studio: detailsState.studio, onCommentsTabSelected: () { Future.delayed( const Duration(milliseconds: 100),