From 628ebbe271c9fbf811bcdaf70c97de07e5eddf8e Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:35:15 +0330 Subject: [PATCH] D1APP-99 studio progress --- lib/routes/route_generator.dart | 7 +- lib/services/media/media.dart | 17 ++- lib/utils/action_sheet.dart | 79 +++++----- .../home/direct/widgets/audio_widget.dart | 2 +- lib/views/home/hashtag/hashtag.dart | 4 +- lib/views/home/studio/studio.dart | 8 +- .../studio/studio_details/studio_details.dart | 101 +++++-------- .../studio_details/studio_details_state.dart | 43 +++--- lib/views/home/widgets/bnb.dart | 142 +++++++++++++++--- lib/views/home/widgets/overview/video.dart | 3 + pubspec.lock | 7 + pubspec.yaml | 1 + 12 files changed, 248 insertions(+), 166 deletions(-) diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 2989dee..7514e7c 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -64,6 +64,9 @@ class RouteGenerator { ChangeNotifierProvider( create: (context) => StudioState(), ), + ChangeNotifierProvider( + create: (context) => StudioDetailsState(), + ), ], child: const Home(), ), @@ -103,8 +106,8 @@ class RouteGenerator { ); case Routes.studioDetails: return _createRoute( - ChangeNotifierProvider( - create: (context) => StudioDetailsState(), + ChangeNotifierProvider.value( + value: (settings.arguments as Map)['state'], child: StudioDetails( pageData: settings.arguments as Map, ), diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index fb0fe74..353b309 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -1,3 +1,5 @@ +import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:flutter/foundation.dart'; @@ -7,8 +9,8 @@ import 'package:just_audio/just_audio.dart'; class MediaService { static final AudioPlayer audioPlayer = AudioPlayer(); static String? audioPlayerTag; - static String? audioPlayerTitle; - static String? audioPlayerCover; + static StudioDetailsData? currentPodcast; + static StudioRequestArgs? podcastPlaylistArgs; static void init() { audioPlayer.positionStream.listen((event) { @@ -21,6 +23,7 @@ class MediaService { static Future handleAudioPlayback({ required dynamic audioSource, + bool isVoiceMessage = true, }) async { bool isNetworkAudio = audioSource.runtimeType == String; String tag; @@ -40,9 +43,11 @@ class MediaService { audioPlayerTag = tag; if (isNetworkAudio) { await audioPlayer.setUrl( - RequestHelper.baseUrl + - audioSource + - '?accessToken=${RequestService.token}', + isVoiceMessage + ? (RequestHelper.baseUrl + + audioSource + + '?accessToken=${RequestService.token}') + : audioSource, ); } else { if (kIsWeb) { @@ -58,6 +63,8 @@ class MediaService { static Future resetAudioPlayer() async { audioPlayerTag = null; + currentPodcast = null; + podcastPlaylistArgs = null; MediaService.audioPlayer.stop(); } diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index 0bdb571..15ea262 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -79,7 +79,7 @@ class ActionSheetUtils { isScrollControlled: true, context: context, builder: (context) => Container( - padding: const EdgeInsets.all(20), + padding: data.hasPadding ? const EdgeInsets.all(20) : EdgeInsets.zero, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical( @@ -91,6 +91,7 @@ class ActionSheetUtils { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Center( child: Container( height: 3, @@ -99,23 +100,24 @@ class ActionSheetUtils { ), ), const SizedBox(height: 8), - Row( - children: [ - if (data.titleIcon != null) - Icon( - data.titleIcon, + if (data.title != null) + Row( + children: [ + if (data.titleIcon != null) + Icon( + data.titleIcon, + color: data.titleColor ?? + Theme.of(context).colorScheme.title, + ), + if (data.titleIcon != null) const SizedBox(width: 8), + DidvanText( + data.title!, + style: Theme.of(context).textTheme.subtitle1, color: data.titleColor ?? Theme.of(context).colorScheme.title, - ), - if (data.titleIcon != null) const SizedBox(width: 8), - DidvanText( - data.title, - style: Theme.of(context).textTheme.subtitle1, - color: - data.titleColor ?? Theme.of(context).colorScheme.title, - ) - ], - ), + ) + ], + ), const SizedBox(height: 28), data.content, const SizedBox(height: 28), @@ -169,30 +171,31 @@ class ActionSheetUtils { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Icon( - data.titleIcon, - size: 20, - color: data.titleColor, + if (data.title != null) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Icon( + data.titleIcon, + size: 20, + color: data.titleColor, + ), ), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: DidvanText( - data.title, - style: Theme.of(context).textTheme.headline3, - color: data.titleColor, - fontWeight: FontWeight.bold, + const SizedBox( + width: 8, ), - ), - ], - ), + Expanded( + child: DidvanText( + data.title!, + style: Theme.of(context).textTheme.headline3, + color: data.titleColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ), const SizedBox( height: 12, ), diff --git a/lib/views/home/direct/widgets/audio_widget.dart b/lib/views/home/direct/widgets/audio_widget.dart index 5bb7ba7..a33827c 100644 --- a/lib/views/home/direct/widgets/audio_widget.dart +++ b/lib/views/home/direct/widgets/audio_widget.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:didvan/services/media/media.dart'; -import 'package:didvan/views/home/widgets/audio_slider.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/home/widgets/player_controller_button.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/home/hashtag/hashtag.dart b/lib/views/home/hashtag/hashtag.dart index d833ad9..24a9a7c 100644 --- a/lib/views/home/hashtag/hashtag.dart +++ b/lib/views/home/hashtag/hashtag.dart @@ -1,8 +1,8 @@ import 'package:didvan/models/tag.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/hashtag/hashtag_state.dart'; -import 'package:didvan/views/home/widgets/news_overview.dart'; -import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/home/widgets/overview/news.dart'; +import 'package:didvan/views/home/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index d4cba73..1ffca83 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -6,9 +6,9 @@ import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/studio/widgets/slider.dart'; import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/home/widgets/podcast_overview.dart'; +import 'package:didvan/views/home/widgets/overview/podcast.dart'; +import 'package:didvan/views/home/widgets/overview/video.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; -import 'package:didvan/views/home/widgets/video_overview.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/radial_button.dart'; @@ -25,7 +25,7 @@ class Studio extends StatefulWidget { } class _StudioState extends State { - final FocusNode _focusNode = FocusNode(); + final _focusNode = FocusNode(); @override void initState() { @@ -119,8 +119,6 @@ class _StudioState extends State { : PodcastOverview( podcast: state.studios[index], onMarkChanged: state.changeMark, - hasUnmarkConfirmation: false, - onCommentsChanged: state.onCommentsChanged, studioRequestArgs: StudioRequestArgs( page: state.page, order: state.order, diff --git a/lib/views/home/studio/studio_details/studio_details.dart b/lib/views/home/studio/studio_details/studio_details.dart index 26491e2..9d72d2b 100644 --- a/lib/views/home/studio/studio_details/studio_details.dart +++ b/lib/views/home/studio/studio_details/studio_details.dart @@ -1,12 +1,18 @@ import 'dart:io'; import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/view/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/widgets/audio_slider.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; @@ -30,15 +36,15 @@ class _StudioDetailsState extends State { double _dwInPortrait = 0; double _scaleInPortrait = 1; - bool get _isVideo => widget.pageData['isVideo']; - @override void initState() { final state = context.read(); + Future.delayed( Duration.zero, () => state.getStudioDetails(widget.pageData['id']), ); + state.args = widget.pageData['args']; if (Platform.isAndroid) WebView.platform = AndroidWebView(); super.initState(); @@ -96,6 +102,8 @@ class _StudioDetailsState extends State { return true; }, child: DidvanScaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + padding: EdgeInsets.zero, appBarData: _isFullScreen ? null : AppBarData( @@ -103,15 +111,14 @@ class _StudioDetailsState extends State { title: state.currentStudio.title, ), children: [ - if (_isVideo) - SizedBox( - width: ds.width, - height: _isFullScreen ? ds.height : ds.width * 9 / 16, - child: Stack( - children: [ - WebView( - initialUrl: Uri.dataFromString( - ''' + SizedBox( + width: ds.width, + height: _isFullScreen ? ds.height : ds.width * 9 / 16, + child: Stack( + children: [ + WebView( + initialUrl: Uri.dataFromString( + ''' { ''', - mimeType: 'text/html', - ).toString(), - javascriptMode: JavascriptMode.unrestricted, - ), - Positioned( - right: 42, - bottom: 8, - child: GestureDetector( - onTap: () => _changeFullSceen(!_isFullScreen), - child: Container( - color: Colors.transparent, - width: 24, - height: 30, - ), + mimeType: 'text/html', + ).toString(), + javascriptMode: JavascriptMode.unrestricted, + ), + Positioned( + right: 42, + bottom: 8, + child: GestureDetector( + onTap: () => _changeFullSceen(!_isFullScreen), + child: Container( + color: Colors.transparent, + width: 24, + height: 30, ), ), - ], - ), + ), + ], ), - if (!_isVideo) - AudioPlayerWidget(podcast: state.currentStudio), + ), ], ), ), @@ -172,39 +177,3 @@ class _StudioDetailsState extends State { ); } } - -class AudioPlayerWidget extends StatelessWidget { - final StudioDetailsData podcast; - const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Hero( - tag: podcast.media, - child: SkeletonImage( - imageUrl: podcast.image, - aspectRatio: 1 / 1, - ), - ), - ), - const SizedBox(height: 16), - DidvanText( - podcast.title, - style: Theme.of(context).textTheme.bodyText1, - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: AudioSlider( - tag: podcast.media, - showTimer: true, - ), - ), - ], - ); - } -} 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 4a4d9e2..9f50e72 100644 --- a/lib/views/home/studio/studio_details/studio_details_state.dart +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -6,15 +6,14 @@ 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/services/media/media.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class StudioDetailsState extends CoreProvier { final List studios = []; - late Timer _trackingTimer; - int _trackingTimerCounter = 0; - late final int initialIndex; - late final StudioRequestArgs args; + late int initialIndex; + late StudioRequestArgs args; bool isFetchingNewItem = false; final List relatedQueue = []; @@ -29,25 +28,36 @@ class StudioDetailsState extends CoreProvier { } } - Future getStudioDetails(int id, {bool? isForward}) async { + Future getStudioDetails(int id, + {bool? isForward, StudioRequestArgs? args}) async { + if (args != null) { + this.args = args; + } if (isForward == null) { appState = AppState.busy; } else { isFetchingNewItem = true; notifyListeners(); } - final service = RequestService(RequestHelper.studioDetails(id, args)); + final service = RequestService(RequestHelper.studioDetails(id, this.args)); await service.httpGet(); - _handleTracking(sendRequest: isForward != null); if (service.isSuccess) { final result = service.result; final studio = StudioDetailsData.fromJson(result['studio']); - if (args.page == 0) { + if (this.args.page == 0) { studios.add(studio); initialIndex = 0; appState = AppState.idle; return; } + if (this.args.type == 'podcast') { + MediaService.currentPodcast = studio; + MediaService.podcastPlaylistArgs = args; + await MediaService.handleAudioPlayback( + audioSource: studio.media, + isVoiceMessage: false, + ); + } StudioDetailsData? prevStudio; if (result['prevStudio'].isNotEmpty) { @@ -121,21 +131,4 @@ class StudioDetailsState extends CoreProvier { count; notifyListeners(); } - - Future _handleTracking({bool sendRequest = true}) async { - if (!sendRequest) { - _trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - _trackingTimerCounter++; - }); - return; - } - //send request - _trackingTimerCounter = 0; - } - - @override - void dispose() { - _trackingTimer.cancel(); - super.dispose(); - } } diff --git a/lib/views/home/widgets/bnb.dart b/lib/views/home/widgets/bnb.dart index 4c78de0..33bec5d 100644 --- a/lib/views/home/widgets/bnb.dart +++ b/lib/views/home/widgets/bnb.dart @@ -1,11 +1,21 @@ 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/view/action_sheet_data.dart'; +import 'package:didvan/routes/routes.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.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/studio/studio_state.dart'; +import 'package:didvan/views/home/widgets/audio/audio_player_widget.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; 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/material.dart'; +import 'package:provider/provider.dart'; class DidvanBNB extends StatelessWidget { final int currentTabIndex; @@ -15,6 +25,9 @@ class DidvanBNB extends StatelessWidget { {Key? key, required this.currentTabIndex, required this.onTabChanged}) : super(key: key); + bool get _enablePlayerController => + MediaService.currentPodcast != null || MediaService.audioPlayer.playing; + @override Widget build(BuildContext context) { return StreamBuilder( @@ -22,29 +35,89 @@ class DidvanBNB extends StatelessWidget { builder: (context, snapshot) { return Stack( children: [ - AnimatedContainer( - duration: DesignConfig.lowAnimationDuration, - height: snapshot.data == true ? 120 : 72, - decoration: BoxDecoration( - color: DesignConfig.isDark - ? Theme.of(context).colorScheme.focused - : Theme.of(context).colorScheme.navigation, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(16), - ), - ), - child: Row( - children: [ - const DidvanIconButton( - icon: DidvanIcons.close_regular, - gestureSize: 24, - onPressed: MediaService.resetAudioPlayer, + GestureDetector( + onTap: () => _showPlayerBottomSheet(context), + child: AnimatedContainer( + padding: const EdgeInsets.only(top: 12), + duration: DesignConfig.lowAnimationDuration, + height: _enablePlayerController ? 120 : 72, + decoration: BoxDecoration( + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.focused + : Theme.of(context).colorScheme.navigation, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), ), - const SizedBox(width: 16), - if (MediaService.audioPlayerCover != null) - SkeletonImage(imageUrl: MediaService.audioPlayerCover!), - const SizedBox(width: 16), - ], + ), + child: !_enablePlayerController + ? const SizedBox() + : SizedBox( + height: 48, + 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, + ), + AudioSlider( + disableThumb: true, + tag: MediaService.audioPlayerTag!, + ), + ], + ), + ), + 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: MediaService.audioPlayerTag, + ); + }, + ), + ), + ], + ), + ), ), ), Positioned( @@ -105,6 +178,31 @@ class DidvanBNB extends StatelessWidget { ); }); } + + void _showPlayerBottomSheet(BuildContext context) { + final detailsState = context.read(); + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + isScrollControlled: true, + builder: (context) => ChangeNotifierProvider.value( + value: detailsState, + child: ExpandableBottomSheet( + background: Container(), + persistentHeader: AudioPlayerWidget( + podcast: MediaService.currentPodcast!, + ), + expandableContent: Container( + height: 300, + width: double.infinity, + color: Theme.of(context).colorScheme.surface, + alignment: Alignment.center, + child: const DidvanText('!Under Construction'), + ), + ), + ), + ); + } } class _NavBarItem extends StatelessWidget { diff --git a/lib/views/home/widgets/overview/video.dart b/lib/views/home/widgets/overview/video.dart index 1dbcdfa..3150b34 100644 --- a/lib/views/home/widgets/overview/video.dart +++ b/lib/views/home/widgets/overview/video.dart @@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.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/widgets/bookmark_button.dart'; import 'package:didvan/views/home/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; @@ -13,6 +14,7 @@ import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class VideoOverview extends StatelessWidget { final OverviewData video; @@ -41,6 +43,7 @@ class VideoOverview extends StatelessWidget { 'args': studioRequestArgs, 'hasUnmarkConfirmation': hasUnmarkConfirmation, 'isVideo': true, + 'state': context.read(), }, ), child: Row( diff --git a/pubspec.lock b/pubspec.lock index 825fa09..ab49287 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4+1" + expandable_bottom_sheet: + dependency: "direct main" + description: + name: expandable_bottom_sheet + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1+1" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 41c210a..b3e664b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: firebase_messaging: ^11.2.8 firebase_core: ^1.13.1 webview_flutter: ^3.0.1 + expandable_bottom_sheet: ^1.1.1+1 dev_dependencies: