import 'dart:math'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/main.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/widgets/overview/multitype.dart'; import 'package:didvan/views/widgets/tag_item.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:url_launcher/url_launcher_string.dart'; class DidvanPageView extends StatefulWidget { final List items; final int initialIndex; final int currentIndex; final bool isRadar; final void Function(int id, bool value) onMarkChanged; final ScrollController scrollController; final void Function(int index) onPageChanged; const DidvanPageView({ Key? key, required this.initialIndex, required this.items, required this.scrollController, required this.onPageChanged, required this.isRadar, required this.currentIndex, required this.onMarkChanged, }) : super(key: key); @override State createState() => _DidvanPageViewState(); } class _DidvanPageViewState extends State { @override Widget build(BuildContext context) { final double deviceTopPadding = MediaQuery.of(context).padding.top; return Stack( children: [ Directionality( textDirection: TextDirection.ltr, child: CarouselSlider.builder( itemCount: widget.items.length, options: CarouselOptions( onPageChanged: (index, reason) => widget.onPageChanged(index), height: double.infinity, initialPage: widget.initialIndex, viewportFraction: 0.94, enableInfiniteScroll: false, ), itemBuilder: (context, index, realIndex) => Directionality( textDirection: TextDirection.rtl, child: SizedBox( height: MediaQuery.of(context).size.height, child: SingleChildScrollView( controller: index == widget.currentIndex ? widget.scrollController : null, physics: const BouncingScrollPhysics(), padding: EdgeInsets.only( left: 4, right: 4, top: 16 + deviceTopPadding, bottom: 92, ), child: DidvanCard( padding: EdgeInsets.zero, enableBorder: false, child: Builder( builder: (context) { final item = widget.items[index]; if (item == null) { return const SizedBox(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SkeletonImage( imageUrl: item.image, aspectRatio: 16 / 9, ), const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: DidvanText( item.title, style: Theme.of(context).textTheme.titleMedium, ), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _subtitle(item), ), if (widget.isRadar && item.podcast != null) Container( padding: const EdgeInsets.all(12) .copyWith(left: 0, bottom: 4), margin: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all( color: Theme.of(context).colorScheme.hint, ), borderRadius: DesignConfig.lowBorderRadius, color: Theme.of(context).colorScheme.background, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const DidvanText('این مطلب را بشنوید:'), const SizedBox(height: 8), AudioWidget( id: item.id, audioUrl: item.podcast, audioMetaData: StudioDetailsData( id: item.id, link: item.podcast, createdAt: item.createdAt, order: 0, marked: item.marked, comments: 0, tags: [], iframe: '', duration: item.duration, title: item.title, image: item.image, description: 'radar', type: 'radar', ), ), ], ), ), for (var i = 0; i < item.contents.length; i++) Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 4, ), child: _contentBuilder(item, i), ), if (item.tags.isNotEmpty) const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Wrap( spacing: 8, runSpacing: 8, children: [ for (var i = 0; i < item.tags.length; i++) TagItem( tag: item.tags[i], onMarkChanged: widget.onMarkChanged, type: widget.isRadar ? 'radar' : 'news', ), ], ), ), const Padding( padding: EdgeInsets.only( left: 16, right: 16, top: 16, ), child: DidvanDivider(verticalPadding: 0), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 16.0), child: ItemTitle(title: 'مطالب مشابه'), ), if (item.relatedContents.isEmpty && !item.relatedContentsIsEmpty) 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 < item.relatedContents.length; i++) Padding( padding: const EdgeInsets.only(bottom: 8), child: MultitypeOverview( item: item.relatedContents[i], onMarkChanged: (id, value) {}, ), ), const SizedBox(height: 8), ], ); }, ), ), ), ), ), ), ), Positioned( right: 24, top: 24 + deviceTopPadding, child: _BackButton(scrollController: widget.scrollController), ), ], ); } Widget _contentBuilder(dynamic item, int index) { final content = item.contents[index]; if (content.text != null) { return Html( data: content.text, onAnchorTap: (href, _, element) { if (href!.contains('navigate-')) { if (href.contains('statistic')) { Navigator.of(navigatorKey.currentContext!) .pushNamed(Routes.statisticDetails, arguments: { 'onMarkChanged': (value) {}, 'label': href.split('-')[2], 'title': href.split('-').last, 'marked': false, }); } else if (href.contains('radar')) { Navigator.of(navigatorKey.currentContext!).pushNamed( Routes.radarDetails, arguments: { 'onMarkChanged': (id, value) {}, 'onCommentsChanged': (id, count) {}, 'id': int.parse(href.split('-').last), 'args': const RadarRequestArgs(page: 0), 'hasUnmarkConfirmation': false, }, ); } else if (href.contains('news')) { Navigator.of(navigatorKey.currentContext!).pushNamed( Routes.newsDetails, arguments: { 'onMarkChanged': (id, value) {}, 'id': int.parse(href.split('-').last), 'args': const NewsRequestArgs(page: 0), 'hasUnmarkConfirmation': false, }, ); } } else if (href.contains('popup-')) { showDialog( context: context, builder: (context) => Dialog( shape: const RoundedRectangleBorder( borderRadius: DesignConfig.lowBorderRadius, ), alignment: Alignment.center, child: SizedBox( width: ActionSheetUtils(context).mediaQueryData.size.width, child: DidvanCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Icon( DidvanIcons.info_circle_solid, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), DidvanText( element!.text, style: Theme.of(context).textTheme.titleSmall, ), ], ), const SizedBox(height: 8), DidvanText(href.split('-').last), const SizedBox(height: 16), DidvanButton( title: 'بستن', onPressed: ActionSheetUtils(context).pop, ), ], ), ), ), ), ); } else { launchUrlString(href, mode: LaunchMode.inAppWebView); } }, style: { '*': Style( direction: TextDirection.rtl, textAlign: TextAlign.right, lineHeight: LineHeight.percent(135), margin: const Margins(), padding: HtmlPaddings.zero, ), 'a': Style( textAlign: TextAlign.left, ), }, ); } if (content.image != null) { return Column( children: [ GestureDetector( onTap: () { showDialog( context: context, builder: (context) => Stack( children: [ Positioned.fill( child: InteractiveViewer( child: Center( child: SkeletonImage( width: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height), imageUrl: content.largeImage ?? content.image, ), ), ), ), const Positioned( right: 24, top: 24, child: _BackButton(), ), ], ), ); }, child: SkeletonImage( imageUrl: content.image!, width: double.infinity, ), ), if (content.caption != null) Center( child: DidvanText( content.caption, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall, ), ), ], ); } return const SizedBox(); } Widget _subtitle(dynamic item) { if (widget.isRadar) { return Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ DidvanText( 'رادار ', style: Theme.of(context).textTheme.bodySmall, ), for (var i = 0; i < item.categories.length; i++) DidvanText( item.categories[i].label + '${i != item.categories.length - 1 ? '،' : ''} ', style: Theme.of(context).textTheme.bodySmall, ), DidvanText( ' - ${DateTimeUtils.momentGenerator(item.createdAt)}', style: Theme.of(context).textTheme.bodySmall, ), ], ); } else { return Row( children: [ DidvanText( item.reference, style: Theme.of(context).textTheme.bodySmall, ), DidvanText( ' - ${DateTimeUtils.momentGenerator(item.createdAt)}', style: Theme.of(context).textTheme.bodySmall, ), ], ); } } } class _BackButton extends StatefulWidget { final ScrollController? scrollController; const _BackButton({Key? key, this.scrollController}) : super(key: key); @override __BackButtonState createState() => __BackButtonState(); } class __BackButtonState extends State<_BackButton> { bool _isVisible = false; @override void didUpdateWidget(covariant _BackButton oldWidget) { if (widget.scrollController != null) { _isVisible = false; _handleScroll(); } super.didUpdateWidget(oldWidget); } @override void initState() { if (widget.scrollController != null) { _handleScroll(); } else { _isVisible = true; } super.initState(); } void _handleScroll() { widget.scrollController!.addListener(() { final position = widget.scrollController!.position.pixels; final size = MediaQuery.of(context).size.height * 0.3; if (position > size && _isVisible == false) { setState(() { _isVisible = true; }); } if (position < size && _isVisible == true) { setState(() { _isVisible = false; }); } }); } @override Widget build(BuildContext context) { return AnimatedVisibility( duration: DesignConfig.lowAnimationDuration, isVisible: _isVisible, child: InkWrapper( borderRadius: DesignConfig.lowBorderRadius, onPressed: Navigator.of(context).pop, child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.splash, border: Border.all(color: Theme.of(context).colorScheme.border), borderRadius: DesignConfig.lowBorderRadius, ), child: const Icon( DidvanIcons.back_regular, size: 32, ), ), ), ); } }