diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 7ba82ad..b30b685 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -41,6 +41,7 @@ import 'package:didvan/views/news/news_details/news_details_state.dart'; import 'package:didvan/views/news/news_state.dart'; import 'package:didvan/views/notification_time/notification_time_state.dart'; import 'package:didvan/views/podcasts/podcasts.dart'; +import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/profile/profile.dart'; import 'package:didvan/views/radar/radar.dart'; import 'package:didvan/views/radar/radar_details/radar_details.dart'; @@ -298,8 +299,11 @@ class RouteGenerator { case Routes.studioDetails: if (settings.arguments is Map) { return _createRoute( - StudioDetails( - pageData: settings.arguments as Map, + ChangeNotifierProvider( + create: (context) => StudioDetailsState(), + child: StudioDetails( + pageData: settings.arguments as Map, + ), ), ); } diff --git a/lib/services/story_service.dart b/lib/services/story_service.dart index 0f70a9e..062959b 100644 --- a/lib/services/story_service.dart +++ b/lib/services/story_service.dart @@ -9,7 +9,6 @@ import 'package:provider/provider.dart'; class StoryService { static Future> getStories() async { - // دریافت UserProvider از طریق navigatorKey final userProvider = Provider.of(navigatorKey.currentContext!, listen: false); final userId = userProvider.user.id; diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart index ae1b67e..182e73b 100644 --- a/lib/views/home/main/main_page.dart +++ b/lib/views/home/main/main_page.dart @@ -14,6 +14,7 @@ import 'package:didvan/views/home/main/widgets/story_section.dart'; import 'package:didvan/views/widgets/didvan/slider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; @@ -21,6 +22,27 @@ import 'package:didvan/services/network/request.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:didvan/views/home/main/widgets/swot_item_card.dart'; +import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform; +import 'package:flutter/material.dart'; // برای دسترسی به TargetPlatform +// این پکیج فقط برای نسخه وب استفاده می‌شود +import 'package:universal_html/html.dart' as html; + +/// تشخیص می‌دهد که آیا دستگاه یک موبایل است (چه نیتیو و چه وب) +bool isAnyMobile() { + // اگر کد روی وب در حال اجراست + if (kIsWeb) { + final userAgent = html.window.navigator.userAgent.toLowerCase(); + return userAgent.contains('mobile') || + userAgent.contains('android') || + userAgent.contains('ios'); + } + + // اگر کد روی پلتفرم نیتیو در حال اجراست + // defaultTargetPlatform پلتفرم اصلی (مثلا اندروید یا iOS) را برمی‌گرداند + return defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; +} + class MainPage extends StatefulWidget { const MainPage({ super.key, @@ -46,97 +68,96 @@ class _MainPageState extends State { Widget build(BuildContext context) { print("DEBUG: _MainPageState build called"); return StateHandler( - onRetry: () => { - print("DEBUG: _MainPageState onRetry called"), - context.read().init - }, + onRetry: () => + {print("DEBUG: _MainPageState onRetry called"), context.read().init}, state: context.watch(), builder: (context, state) { print("DEBUG: FutureBuilder waiting"); print("DEBUG: FutureBuilder state.stories.isNotEmpty: ${state.stories.isNotEmpty}"); print("DEBUG: FutureBuilder state.content: ${state.content!.lists}"); print("DEBUG: FutureBuilder state.content != null: ${state.content != null}"); - print("DEBUG: FutureBuilder state.content!.lists.isNotEmpty: ${state.content!.lists.isNotEmpty}"); + print( + "DEBUG: FutureBuilder state.content!.lists.isNotEmpty: ${state.content!.lists.isNotEmpty}"); return ListView( - padding: const EdgeInsets.symmetric(vertical: 16), - children: [ - if (state.stories.isNotEmpty) StorySection(stories: state.stories), - const SizedBox(height: 12), - const MainPageMainContent(), - Builder(builder: (context) { - final List pageContent = []; - if (state.content != null && state.content!.lists.isNotEmpty) { - final lists = state.content!.lists; - - for (int i = 0; i < lists.length; i++) { - final currentList = lists[i]; - - if (i == 4) { - pageContent.add( - Padding( - padding: const EdgeInsets.only(top: 32), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 16, - top: 28, + padding: const EdgeInsets.symmetric(vertical: 16), + children: [ + if (state.stories.isNotEmpty) StorySection(stories: state.stories), + const SizedBox(height: 12), + const MainPageMainContent(), + Builder(builder: (context) { + final List pageContent = []; + if (state.content != null && state.content!.lists.isNotEmpty) { + final lists = state.content!.lists; + + for (int i = 0; i < lists.length; i++) { + final currentList = lists[i]; + + if (i == 4) { + pageContent.add( + Padding( + padding: const EdgeInsets.only(top: 32), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 16, + top: 28, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const InfoTitle(), + GestureDetector( + onTap: () => { + Navigator.of(context) + .pushNamed(Routes.infography) + }, + child: Row( + children: [ + DidvanText( + "همه", + color: Theme.of(context) + .colorScheme + .primary, + ), + Icon( + DidvanIcons.angle_left_light, + color: Theme.of(context) + .colorScheme + .primary, + ) + ], + ), + ) + ], + ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const InfoTitle(), - GestureDetector( - onTap: () => { - Navigator.of(context) - .pushNamed(Routes.infography) - }, - child: Row( - children: [ - DidvanText( - "همه", - color: Theme.of(context) - .colorScheme - .primary, - ), - Icon( - DidvanIcons.angle_left_light, - color: Theme.of(context) - .colorScheme - .primary, - ) - ], - ), - ) - ], + const MainPageBanner( + isFirst: false, ), - ), - const MainPageBanner( - isFirst: false, - ), - ], + ], + ), ), - ), - ); - } - - pageContent.add(_MainPageSection( - list: currentList, - isLast: i == lists.length - 1, - )); - - if (currentList.type == 'startup') { - pageContent.add(_SwotSection(swotItems: state.swotItems)); + ); + } + + pageContent.add(_MainPageSection( + list: currentList, + isLast: i == lists.length - 1, + )); + + if (currentList.type == 'startup') { + pageContent.add(_SwotSection(swotItems: state.swotItems)); + } } } - } - print("DEBUG: FutureBuilder error"); - return Column(children: pageContent); - }), - ], - ); + print("DEBUG: FutureBuilder error"); + return Column(children: pageContent); + }), + ], + ); }, ); } @@ -210,7 +231,7 @@ class _SwotSection extends StatelessWidget { DidvanSlider( height: 330, itemCount: 7, - viewportFraction: 0.65, + viewportFraction: isAnyMobile() ? 0.65 : 0.55, itemBuilder: (context, index, realIndex) => Padding( padding: const EdgeInsets.symmetric(horizontal: 0.0), child: Padding( @@ -390,7 +411,10 @@ class _MainPageSection extends StatelessWidget { return DidvanSlider( height: 260 + (_maxSublistCount() - (list.type == 'radar' ? 1 : 0)) * 20, itemCount: list.contents.length, - viewportFraction: 0.65, + // -- START: کد اصلاح شده در اینجا قرار دارد -- + // از isMobile() برای تنظیم اندازه آیتم‌ها بر اساس موبایل یا دسکتاپ بودن استفاده می‌کنیم + viewportFraction: isAnyMobile() ? 0.65 : 0.55, + // -- END: کد اصلاح شده -- itemBuilder: (context, index, realIndex) => Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: MainPageGeneralItem( @@ -400,4 +424,4 @@ class _MainPageSection extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/views/podcasts/studio_details/studio_details.mobile.dart b/lib/views/podcasts/studio_details/studio_details.mobile.dart index f01af12..3512dbc 100644 --- a/lib/views/podcasts/studio_details/studio_details.mobile.dart +++ b/lib/views/podcasts/studio_details/studio_details.mobile.dart @@ -66,32 +66,42 @@ class _StudioDetailsState extends State { } Future _initializePlayer(StudioDetailsData studio) async { - // Disposing old controllers before creating new ones. - _videoPlayerController?.dispose(); - _chewieController?.dispose(); + if (widget.pageData['args']['type'] == 'video') { + // Disposing old controllers before creating new ones. + _videoPlayerController?.dispose(); + _chewieController?.dispose(); - _videoPlayerController = VideoPlayerController.network(studio.link); - - try { - await _videoPlayerController!.initialize(); - if (mounted) { - setState(() { - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController!, - customControls: const PrimaryControls(), - autoPlay: true, - looping: true, - aspectRatio: 16 / 9, - materialProgressColors: ChewieProgressColors( - playedColor: Theme.of(context).colorScheme.title, - handleColor: Theme.of(context).colorScheme.title, - ), - ); - _currentlyPlayingId = studio.id; - }); + _videoPlayerController = VideoPlayerController.network(studio.link); + + try { + await _videoPlayerController!.initialize(); + if (mounted) { + setState(() { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController!, + customControls: const PrimaryControls(), + autoPlay: true, + looping: true, + aspectRatio: 16 / 9, + materialProgressColors: ChewieProgressColors( + playedColor: Theme.of(context).colorScheme.title, + handleColor: Theme.of(context).colorScheme.title, + ), + ); + _currentlyPlayingId = studio.id; + }); + } + } catch (e) { + debugPrint("Error initializing video player: $e"); } - } catch (e) { - debugPrint("Error initializing video player: $e"); + } else { + // Handle audio playback using MediaService + await MediaService.handleAudioPlayback( + audioSource: studio.link, + id: studio.id, + isVoiceMessage: false, + ); + _currentlyPlayingId = studio.id; } } @@ -139,7 +149,7 @@ class _StudioDetailsState extends State { appBarData: AppBarData( trailing: BookmarkButton( itemId: state.studio.id, - type: 'video', + type: state.args?.type == 'video' ? 'video' : 'podcast', value: state.studio.marked, onMarkChanged: (value) { widget.pageData['onMarkChanged']( @@ -157,20 +167,21 @@ class _StudioDetailsState extends State { height: d.size.height - d.padding.top - 56, child: Column( children: [ - AspectRatio( - aspectRatio: 16 / 9, - child: (_chewieController != null && - _chewieController! - .videoPlayerController.value.isInitialized) - ? Chewie(controller: _chewieController!) - : Center( - child: Image.asset( - Assets.loadingAnimation, - width: 100, - height: 100, + if (state.args?.type == 'video') + AspectRatio( + aspectRatio: 16 / 9, + child: (_chewieController != null && + _chewieController! + .videoPlayerController.value.isInitialized) + ? Chewie(controller: _chewieController!) + : Center( + child: Image.asset( + Assets.loadingAnimation, + width: 100, + height: 100, + ), ), - ), - ), + ), Expanded( child: StudioDetailsWidget( onMarkChanged: (id, value) => widget diff --git a/lib/views/widgets/audio/player_navbar.dart b/lib/views/widgets/audio/player_navbar.dart index d61e749..7916640 100644 --- a/lib/views/widgets/audio/player_navbar.dart +++ b/lib/views/widgets/audio/player_navbar.dart @@ -1,6 +1,8 @@ // ignore_for_file: unused_element import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/models/requests/radar.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; @@ -40,24 +42,24 @@ class _PlayerNavBarState extends State { stream: MediaService.audioPlayer.playingStream, 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; + (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( builder: (context, state, child) => AnimatedContainer( diff --git a/lib/views/widgets/overview/podcast.dart b/lib/views/widgets/overview/podcast.dart index 1c8a187..9b1ff6a 100644 --- a/lib/views/widgets/overview/podcast.dart +++ b/lib/views/widgets/overview/podcast.dart @@ -36,9 +36,14 @@ class PodcastOverview extends StatelessWidget { Widget build(BuildContext context) { return DidvanCard( onTap: () { - context - .read() - .getStudioDetails(podcast.id, args: studioRequestArgs); + Navigator.of(context).pushNamed( + Routes.studioDetails, + arguments: { + 'id': podcast.id, + 'args': studioRequestArgs, + 'onMarkChanged': onMarkChanged, + }, + ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -149,50 +154,50 @@ class PodcastOverview extends StatelessWidget { } static Widget get placeholder => DidvanCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Row( - crossAxisAlignment: CrossAxisAlignment.center, + ShimmerPlaceholder(height: 64, width: 64), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ShimmerPlaceholder(height: 64, width: 64), - SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ShimmerPlaceholder(height: 18, width: 200), - SizedBox(height: 8), - ShimmerPlaceholder(height: 18, width: 100), - ], - ), - ], - ), - const SizedBox(height: 16), - const ShimmerPlaceholder( - height: 16, - width: double.infinity, - ), - const SizedBox(height: 8), - const ShimmerPlaceholder( - height: 16, - width: 200, - ), - const SizedBox(height: 4), - const DidvanDivider(verticalPadding: 8), - Row( - children: [ - ShimmerPlaceholder( - height: 36, - width: 92, - borderRadius: BorderRadius.circular(5), - ), - const Spacer(), - const ShimmerPlaceholder(width: 24, height: 24), - const SizedBox(width: 16), - const ShimmerPlaceholder(width: 24, height: 24), + ShimmerPlaceholder(height: 18, width: 200), + SizedBox(height: 8), + ShimmerPlaceholder(height: 18, width: 100), ], ), ], ), - ); + const SizedBox(height: 16), + const ShimmerPlaceholder( + height: 16, + width: double.infinity, + ), + const SizedBox(height: 8), + const ShimmerPlaceholder( + height: 16, + width: 200, + ), + const SizedBox(height: 4), + const DidvanDivider(verticalPadding: 8), + Row( + children: [ + ShimmerPlaceholder( + height: 36, + width: 92, + borderRadius: BorderRadius.circular(5), + ), + const Spacer(), + const ShimmerPlaceholder(width: 24, height: 24), + const SizedBox(width: 16), + const ShimmerPlaceholder(width: 24, height: 24), + ], + ), + ], + ), + ); } diff --git a/pubspec.yaml b/pubspec.yaml index 5786330..625b499 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,7 @@ dependencies: cached_network_image: ^3.2.0 skeleton_text: ^3.0.0 carousel_slider: ^5.0.0 - universal_html: ^2.0.8 + universal_html: ^2.2.4 record: ^5.1.2 persian_number_utility: ^1.1.1