// ignore_for_file: use_build_context_synchronously, deprecated_member_use import 'package:chewie/chewie.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/comments/comments.dart'; import 'package:didvan/views/comments/comments_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/podcasts/studio_details/widgets/studio_details_widget.dart'; import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/audio/audio_player_widget.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/overview/multitype.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/tag_item.dart'; import 'package:didvan/views/widgets/video/primary_controls.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:video_player/video_player.dart'; class StudioDetails extends StatefulWidget { final Map pageData; const StudioDetails({Key? key, required this.pageData}) : super(key: key); @override State createState() => _StudioDetailsState(); } class _StudioDetailsState extends State with TickerProviderStateMixin, WidgetsBindingObserver { int _currentlyPlayingId = 0; VideoPlayerController? _videoPlayerController; ChewieController? _chewieController; bool _isDescriptionExpanded = false; final _focusNode = FocusNode(); late AnimationController _mainAnimationController; late Animation _fadeAnimation; late Animation _slideAnimation; late AnimationController _playerAnimationController; late Animation _playerScaleAnimation; late Animation _playerFadeAnimation; late AnimationController _titleAnimationController; late Animation _titleSlideAnimation; late Animation _titleFadeAnimation; late AnimationController _tagsAnimationController; late Animation _tagsFadeAnimation; late AnimationController _bookmarkAnimationController; late Animation _bookmarkScaleAnimation; late Animation _bookmarkRotationAnimation; final GlobalKey _relatedContentKey = GlobalKey(); final GlobalKey _commentsKey = GlobalKey(); @override void initState() { super.initState(); _mainAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _mainAnimationController, curve: const Interval(0.0, 0.5, curve: Curves.easeIn), ), ); _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation( parent: _mainAnimationController, curve: const Interval(0.0, 0.6, curve: Curves.easeOutCubic), ), ); _playerAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), ); _playerScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( CurvedAnimation( parent: _playerAnimationController, curve: Curves.elasticOut, ), ); _playerFadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _playerAnimationController, curve: const Interval(0.0, 0.5, curve: Curves.easeIn), ), ); _titleAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _titleSlideAnimation = Tween(begin: const Offset(-0.3, 0), end: Offset.zero).animate( CurvedAnimation( parent: _titleAnimationController, curve: Curves.easeOutBack, ), ); _titleFadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _titleAnimationController, curve: Curves.easeIn, ), ); _tagsAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 600), ); _tagsFadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _tagsAnimationController, curve: Curves.easeIn, ), ); _bookmarkAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 600), ); _bookmarkScaleAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _bookmarkAnimationController, curve: Curves.elasticOut, ), ); _bookmarkRotationAnimation = Tween(begin: -0.5, end: 0.0).animate( CurvedAnimation( parent: _bookmarkAnimationController, curve: Curves.easeOut, ), ); final state = context.read(); state.args = widget.pageData['args']; Future.delayed( Duration.zero, () => state.getStudioDetails(widget.pageData['id']).then((_) { if (mounted) { _initializePlayer(state.studio); Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { state.getRelatedContents(); _mainAnimationController.forward(); Future.delayed(const Duration(milliseconds: 200), () { if (mounted) { _playerAnimationController.forward(); } }); Future.delayed(const Duration(milliseconds: 400), () { if (mounted) { _titleAnimationController.forward(); } }); Future.delayed(const Duration(milliseconds: 600), () { if (mounted) { _tagsAnimationController.forward(); } }); Future.delayed(const Duration(milliseconds: 800), () { if (mounted) { _bookmarkAnimationController.forward(); } }); } }); } }), ); } @override void didChangeDependencies() { super.didChangeDependencies(); WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { _stopPodcast(); } } void _stopPodcast() { if (MediaService.audioPlayer.playing) { MediaService.audioPlayer.stop(); } MediaService.currentPodcast = null; MediaService.audioPlayerTag = null; } Future _initializePlayer(StudioDetailsData studio) async { if (studio.type == 'video') { _videoPlayerController?.dispose(); _chewieController?.dispose(); debugPrint("Playing video from URL: ${studio.link}"); _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"); } } else if (studio.type == 'podcast') { await MediaService.handleAudioPlayback( audioSource: studio.link, id: studio.id, isVoiceMessage: false, ); if (mounted) { setState(() { _currentlyPlayingId = studio.id; }); } } } @override Widget build(BuildContext context) { return Consumer( builder: (context, state, child) { if (state.isStudioLoaded && _currentlyPlayingId != state.studio.id) { Future.microtask(() => _initializePlayer(state.studio)); } return StateHandler( state: state, onRetry: () { try { state.getStudioDetails(state.studio.id); } catch (e) { state.getStudioDetails(widget.pageData['id']); } }, builder: (context, state) { if (!state.isStudioLoaded) { return Scaffold( body: Center( child: Image.asset( Assets.loadingAnimation, width: 100, height: 100, ), ), ); } return WillPopScope( onWillPop: () async { if (MediaService.currentPodcast != null) { state.studio = MediaService.currentPodcast!; } state.handleTracking(id: state.studio.id); return true; }, child: Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, appBar: PreferredSize( preferredSize: const Size.fromHeight(90.0), child: AppBar( backgroundColor: Colors.white, elevation: 0, automaticallyImplyLeading: false, flexibleSpace: SafeArea( child: Container( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Center( child: SvgPicture.asset( 'lib/assets/images/logos/logo-horizontal-light.svg', height: 55, ), ), IconButton( icon: SvgPicture.asset( 'lib/assets/icons/arrow-left.svg', color: const Color.fromARGB(255, 102, 102, 102), height: 24, ), onPressed: () { Navigator.pop(context); }, ), ], ), ), ), )), body: SingleChildScrollView( child: FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Hero( tag: 'media-${state.studio.id}', child: FadeTransition( opacity: _playerFadeAnimation, child: ScaleTransition( scale: _playerScaleAnimation, child: Stack( children: [ if (state.studio.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, ), ), ), if (state.studio.type == 'podcast') AudioPlayerWidget(podcast: state.studio), Positioned( top: 1, left: 1, child: ScaleTransition( scale: _bookmarkScaleAnimation, child: RotationTransition( turns: _bookmarkRotationAnimation, child: BookmarkButton( value: state.studio.marked, onMarkChanged: (value) { if (widget.pageData[ 'onMarkChanged'] != null) { widget.pageData[ 'onMarkChanged']( state.studio.id, value, true); } }, gestureSize: 35, type: state.studio.type == 'video' ? 'video' : 'podcast', itemId: state.studio.id, ), ), ), ), ], ), ), ), ), _buildDescriptionSection(state), _buildRelatedContentSection(state), _buildCommentsSection(state), ], ), ), ), ), ), ); }, ); }, ); } Widget _buildDescriptionSection(StudioDetailsState state) { return AnimatedSwitcher( duration: const Duration(milliseconds: 500), child: KeyedSubtree( key: ValueKey('description-${state.studio.id}'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SlideTransition( position: _titleSlideAnimation, child: FadeTransition( opacity: _titleFadeAnimation, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( state.studio.title, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 0, 53, 70)), ), ), ), ), Container( decoration: BoxDecoration( borderRadius: BorderRadius.vertical( bottom: Radius.circular(_isDescriptionExpanded ? 0 : 16.0), ), boxShadow: _isDescriptionExpanded ? null : [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8.0, spreadRadius: -2.0, offset: const Offset(0, 6), ), ], ), child: Stack( alignment: Alignment.bottomCenter, children: [ AnimatedSize( duration: const Duration(milliseconds: 300), child: ClipRRect( borderRadius: BorderRadius.vertical( bottom: Radius.circular( _isDescriptionExpanded ? 0 : 16.0), top: Radius.circular( _isDescriptionExpanded ? 0 : 16.0), ), child: Container( color: Theme.of(context).colorScheme.surface, child: ConstrainedBox( constraints: BoxConstraints( maxHeight: _isDescriptionExpanded ? double.infinity : 140.0, ), child: Html( key: ValueKey(state.studio.id), data: state.studio.description, onAnchorTap: (href, _, __) => launchUrlString(href!), style: { '*': Style( direction: TextDirection.rtl, textAlign: TextAlign.right, lineHeight: LineHeight.percent(135), margin: const Margins(), padding: HtmlPaddings.zero, color: const Color.fromARGB( 255, 102, 102, 102), fontWeight: FontWeight.normal, ), }, ), ), ), ), ), Positioned( bottom: 0, left: 0, right: 0, child: AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: _isDescriptionExpanded ? 0.0 : 1.0, child: Container( height: 50, decoration: BoxDecoration( borderRadius: const BorderRadius.vertical( bottom: Radius.circular(16.0), top: Radius.circular(16.0), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Theme.of(context) .colorScheme .surface .withOpacity(0.5), Theme.of(context) .colorScheme .surface .withOpacity(0.9), ], stops: const [0.0, 0.5], ), ), ), ), ), ], ), ), Transform.translate( offset: const Offset(0, -14.0), child: InkWell( onTap: () { setState(() { _isDescriptionExpanded = !_isDescriptionExpanded; }); }, child: Padding( padding: EdgeInsets.zero, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(5.0), decoration: const BoxDecoration( color: Color.fromARGB(255, 230, 243, 250), shape: BoxShape.circle, ), child: SvgPicture.asset( _isDescriptionExpanded ? 'lib/assets/icons/arrow-up2.svg' : 'lib/assets/icons/arrow-down.svg', color: Theme.of(context).primaryColor, height: 25, ), ), ], ), ), ), ), if (state.studio.tags.isNotEmpty) const SizedBox(height: 16), if (state.studio.tags.isNotEmpty) FadeTransition( opacity: _tagsFadeAnimation, child: Wrap( spacing: 8, runSpacing: 8, children: [ for (var i = 0; i < state.studio.tags.length; i++) TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: Duration(milliseconds: 400 + (i * 100)), curve: Curves.easeOutBack, builder: (context, value, child) { return Transform.scale( scale: value, child: Opacity( opacity: value, child: child, ), ); }, child: TagItem( tag: state.studio.tags[i], onMarkChanged: (id, value) { if (widget.pageData['onMarkChanged'] != null) { widget.pageData['onMarkChanged']( id, value, true); } }, type: state.studio.type == 'video' ? 'video' : 'podcast', ), ), ], ), ), ], ), ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(), if (state.nextStudio != null && state.alongSideState == AppState.idle) StudioPreview( isNext: true, studio: state.nextStudio!, ), if (state.alongSideState == AppState.busy) StudioPreview.placeHolder, if (state.prevStudio != null && state.alongSideState == AppState.idle) StudioPreview( isNext: false, studio: state.prevStudio!, ), if (state.alongSideState == AppState.busy) StudioPreview.placeHolder, const SizedBox(), ], ), ], ), ), ); } Widget _buildCommentsSection(StudioDetailsState state) { return ChangeNotifierProvider( create: (context) => CommentsState() ..itemId = state.studio.id ..type = 'studio' ..onCommentsChanged = state.onCommentsChanged ..getComments(), child: Padding( padding: const EdgeInsets.all(8.0), child: Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Consumer( builder: (context, userProvider, child) { final user = userProvider.user; final hasProfileImage = user.photo != null && user.photo!.isNotEmpty; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 20, backgroundColor: Colors.white, backgroundImage: hasProfileImage ? NetworkImage(user.photo!) : null, child: !hasProfileImage ? const Icon( DidvanIcons.avatar_light, size: 50, color: Colors.black, ) : null, ), const SizedBox(width: 8), Expanded( child: CommentMessageBox(focusNode: _focusNode), ), ], ); }, ), const SizedBox(height: 16), const SizedBox( width: double.infinity, child: DidvanText( 'نظرات کاربران:', style: TextStyle( color: Color.fromARGB(255, 0, 53, 70), fontSize: 18, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 16), SizedBox( height: 400, child: Comments( key: _commentsKey, pageData: const {'isPage': false}, ), ), ], ), ), ), ); } Widget _buildRelatedContentSection(StudioDetailsState state) { debugPrint("تعداد مطالب مرتبط: ${state.studio.relatedContents.length}"); debugPrint( "آیا لیست مطالب مرتبط خالی است؟ ${state.studio.relatedContentsIsEmpty}"); debugPrint("تعداد tags: ${state.studio.tags.length}"); return Container( width: double.infinity, margin: const EdgeInsets.all(8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.all(8.0), child: Text( "مطالب مرتبط:", style: TextStyle( fontSize: 18, color: Color.fromARGB(255, 0, 53, 70), fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.all(8), child: Builder( builder: (context) { if (state.studio.relatedContents.isNotEmpty) { return AnimatedList( key: _relatedContentKey, initialItemCount: state.studio.relatedContents.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index, animation) { final item = state.studio.relatedContents[index]; return FadeTransition( opacity: animation, child: SlideTransition( position: Tween( begin: const Offset(0, 0.2), end: Offset.zero, ).animate(animation), child: Padding( padding: const EdgeInsets.only(bottom: 8), child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () { _stopPodcast(); String routeName; Map arguments; if (item.type == 'video') { routeName = Routes.videoDetails; arguments = { 'id': item.id, 'type': item.type, 'onMarkChanged': (int id, bool value) { if (widget.pageData['onMarkChanged'] != null) { widget.pageData['onMarkChanged']( id, value, true); } }, }; } else if (item.type == 'podcast') { routeName = Routes.studioDetails; arguments = { 'id': item.id, 'type': item.type, 'onMarkChanged': (int id, bool value, [bool shouldUpdate = true]) { if (widget.pageData['onMarkChanged'] != null) { widget.pageData['onMarkChanged']( id, value, shouldUpdate); } }, }; } else if (item.type == 'news') { routeName = Routes.newsDetails; arguments = { 'id': item.id, }; } else if (item.type == 'radar') { routeName = Routes.radarDetails; arguments = { 'id': item.id, }; } else { routeName = Routes.studioDetails; arguments = { 'id': item.id, 'type': item.type, 'onMarkChanged': (int id, bool value, [bool shouldUpdate = true]) { // Match original if (widget.pageData['onMarkChanged'] != null) { widget.pageData['onMarkChanged']( id, value, shouldUpdate); } }, }; } Navigator.pushNamed( context, routeName, arguments: arguments, ); }, child: MultitypeOverview( item: item, onMarkChanged: (id, value) { if (widget.pageData['onMarkChanged'] != null) { widget.pageData['onMarkChanged']( id, value, true); // Modified } }, ), ), ), ), ); }, ); } else if (state.studio.relatedContentsIsEmpty) { return const Padding( padding: EdgeInsets.all(32.0), child: Center( child: DidvanText( 'مطالب مرتبطی یافت نشد', style: TextStyle( color: Colors.grey, fontSize: 16, ), ), ), ); } else { return Column( children: [ const Padding( padding: EdgeInsets.all(8.0), child: Text( 'در حال بارگذاری مطالب مرتبط...', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ), ...List.generate( 3, (index) => Padding( padding: const EdgeInsets.only(bottom: 8), child: MultitypeOverview.placeholder, )), ], ); } }, ), ), ], ), ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _stopPodcast(); _mainAnimationController.dispose(); _playerAnimationController.dispose(); _titleAnimationController.dispose(); _tagsAnimationController.dispose(); _bookmarkAnimationController.dispose(); _videoPlayerController?.dispose(); _chewieController?.dispose(); _focusNode.dispose(); super.dispose(); } }