// ignore_for_file: unused_element_parameter import 'dart:async'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/home/bookmarks/bookmark_state.dart'; import 'package:didvan/views/home/main/widgets/swot_bookmark.dart'; import 'package:didvan/views/widgets/didvan/checkbox.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/date_picker_button.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/menu_item.dart'; import 'package:didvan/views/widgets/overview/multitype.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:didvan/views/widgets/search_field.dart'; import 'package:didvan/views/widgets/ai_chat_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; class Bookmarks extends StatefulWidget { const Bookmarks({Key? key}) : super(key: key); @override State createState() => _BookmarksState(); } class _BookmarksState extends State { final _focuseNode = FocusNode(); Timer? _timer; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().loadInitialData(); }); } // ignore: unused_element void _onSearchChanged(String value) { final state = context.read(); if (value.length < 3 && value.isNotEmpty) { if (state.search.isNotEmpty && value.isEmpty) { state.search = value; state.loadInitialData(); } return; } if (state.lastSearch == value && value.isNotEmpty) return; _timer?.cancel(); _timer = Timer(const Duration(milliseconds: 700), () { state.search = value; state.searchAndLoadData(page: 1); }); } Future _showFilterBottomSheet() async { final state = context.read(); final categories = [ {'id': 1, 'label': 'پویش افق'}, {'id': 2, 'label': 'دنیای فولاد'}, {'id': 3, 'label': 'استودیو آینده'}, {'id': 5, 'label': 'رادارهای استراتژیک'}, {'id': 6, 'label': 'سها'}, {'id': 7, 'label': 'اینفوگرافی'}, ]; await ActionSheetUtils(context).showBottomSheet( data: ActionSheetData( title: 'فیلتر جستجو', titleIconWidget: SvgPicture.asset( 'lib/assets/icons/document-filter.svg', width: 24, height: 24, ), dismissTitle: 'حذف فیلتر', confrimTitle: 'نمایش نتایج', onDismissed: () => state.resetFilters(false), onConfirmed: () => state.loadInitialData(), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ItemTitle( title: 'تاریخ', style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(fontWeight: FontWeight.bold), iconWidget: 'lib/assets/icons/calendar.svg', color: const Color.fromARGB(255, 27, 60, 89), ), const SizedBox(height: 8), StatefulBuilder( builder: (context, setState) => Row( children: [ DatePickerButton( initialValue: state.startDate?.toIso8601String().split('T').first, emptyText: 'از تاریخ', onPicked: (date) => setState(() { state.startDate = date != null ? DateTime.parse(date) : null; }), lastDate: state.endDate?.toIso8601String().split('T').first, ), const SizedBox(width: 8), DatePickerButton( initialValue: state.endDate?.toIso8601String().split('T').first, emptyText: 'تا تاریخ', onPicked: (date) => setState(() { state.endDate = date != null ? DateTime.parse(date) : null; }), firstDate: state.startDate?.toIso8601String().split('T').first, ), ], ), ), const SizedBox(height: 28), ItemTitle( title: 'دسته بندی', style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(fontWeight: FontWeight.bold), iconWidget: 'lib/assets/icons/ion_extension-puzzle-outline.svg', color: const Color.fromARGB(255, 27, 60, 89), ), const SizedBox(height: 12), Wrap( children: [ for (var category in categories) SizedBox( width: (MediaQuery.of(context).size.width - 40) / 2, child: DidvanCheckbox( title: category['label'] as String, value: state.selectedTypes.contains(category['id']), onChanged: (value) { if (value) { state.selectedTypes.add(category['id'] as int); } else { state.selectedTypes.remove(category['id']); } }, color: const Color.fromARGB(255, 61, 61, 61), ), ), ], ), ], ), ), ); } @override Widget build(BuildContext context) { final state = context.watch(); return DidvanScaffold( appBarData: null, showSliversFirst: true, physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(8.0), hidePlayer: true, slivers: [ SliverAppBar( pinned: true, expandedHeight: 140, backgroundColor: Theme.of(context).colorScheme.surface, automaticallyImplyLeading: false, flexibleSpace: FlexibleSpaceBar( background: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SvgPicture.asset( Assets.horizontalLogoWithText, height: 60, color: Theme.of(context).colorScheme.title, ), Row( children: [ GestureDetector( onTap: () { Navigator.pushNamed(context, Routes.profile); }, child: SizedBox( width: 44, height: 44, child: Center( child: SvgPicture.asset( 'lib/assets/icons/New_Profile.svg', width: 30, height: 30, color: const Color.fromARGB(255, 102, 102, 102), ), ), ), ), GestureDetector( onTap: () => Navigator.pop(context), child: SizedBox( width: 44, height: 44, child: Center( child: SvgPicture.asset( 'lib/assets/icons/arrow-left.svg', width: 25, height: 25, color: const Color.fromARGB(255, 102, 102, 102), ), ), ), ), ], ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: SearchField( title: 'دیدوان', focusNode: _focuseNode, onChanged: _onSearchChanged, onFilterButtonPressed: _showFilterBottomSheet, isFiltered: state.isFiltered, extraIconPath: 'lib/assets/icons/live ai.svg', onExtraIconPressed: () { showDialog( context: context, barrierDismissible: true, builder: (context) => const AiChatDialog(), ); }, ), ), ], ), ), ), SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16), sliver: SliverToBoxAdapter( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ AnimatedVisibility( duration: DesignConfig.lowAnimationDuration, isVisible: !state.searching, child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color.fromARGB(255, 224, 224, 224), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 10, offset: const Offset(0, 4), ) ], ), padding: const EdgeInsets.all(10.0), child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: const Color.fromARGB(255, 230, 243, 250), borderRadius: BorderRadius.circular(16), ), child: Center( child: DidvanText( 'نشان‌شده‌ها', style: Theme.of(context) .textTheme .titleMedium ?.copyWith( color: Colors.black, fontWeight: FontWeight.normal, fontSize: 15), ), ), ), const SizedBox(height: 15), _FadeInSlide( delay: const Duration(milliseconds: 100), child: MenuOption( onTap: () => _onCategorySelected(5), iconWidget: SvgPicture.asset( "lib/assets/icons/Stratzhic Radar.svg", width: 20, ), titleWidget: const DidvanText('رادارهای استراتژیک', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 150), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 200), child: MenuOption( onTap: () => _onCategorySelected(2), iconWidget: SvgPicture.asset( "lib/assets/icons/Donye_Foolad.svg", width: 20, ), titleWidget: const DidvanText('دنیای فولاد', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 250), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 300), child: MenuOption( onTap: () => _onCategorySelected(1), iconWidget: SvgPicture.asset( "lib/assets/icons/Pouyesh_Ofogh_New.svg", width: 20, ), titleWidget: const DidvanText('پویش افق', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 350), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 400), child: MenuOption( onTap: () => _onCategorySelected(3), iconWidget: SvgPicture.asset( "lib/assets/icons/video-play.svg", color: const Color.fromARGB(255, 0, 126, 167), width: 20, ), titleWidget: const DidvanText('ویدیو‌کست', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 450), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 500), child: MenuOption( onTap: () => _onCategorySelected(4), iconWidget: SvgPicture.asset( "lib/assets/icons/microphone-2.svg", color: const Color.fromARGB(255, 0, 126, 167), width: 20, ), titleWidget: const DidvanText('پادکست', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 550), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 600), child: MenuOption( onTap: () => _onCategorySelected(6), iconWidget: SvgPicture.asset( "lib/assets/icons/Saha.svg", width: 20, ), titleWidget: const DidvanText('سها', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 650), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 700), child: MenuOption( onTap: () => _onCategorySelected(7), iconWidget: SvgPicture.asset( "lib/assets/icons/hugeicons_chart-02.svg", color: const Color.fromARGB(255, 0, 126, 167), width: 20, ), titleWidget: const DidvanText('اینفوگرافی', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), ), ), const _FadeInSlide( delay: Duration(milliseconds: 750), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 800), child: MenuOption( onTap: () => _onCategorySelected(8), titleWidget: const DidvanText( 'ماژول بایدها و نبایدها', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), iconWidget: SvgPicture.asset( "lib/assets/icons/Swot_New.svg", width: 24), iconSize: 24, ), ), const _FadeInSlide( delay: Duration(milliseconds: 850), child: DidvanDivider(), ), _FadeInSlide( delay: const Duration(milliseconds: 900), child: MenuOption( onTap: () => _onCategorySelected(9), titleWidget: const DidvanText('ماهنامه تحلیلی', style: TextStyle( color: Color.fromARGB(255, 102, 102, 102))), iconWidget: SvgPicture.asset( "lib/assets/icons/Monthly.svg", width: 24), iconSize: 24, ), ), const SizedBox(height: 7), ], ), ), ), const ItemTitle(title: 'آخرین نشان شده‌ها') ], ), ), ), SliverStateHandler( state: state, enableEmptyState: state.bookmarks.isEmpty && state.bookmarkedSwotItems.isEmpty && !state.searching && !state.swotItemsLoading, emptyState: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ DidvanText( 'در قسمت رصدخانه من، تمامی مطالبی که در قسمت‌های مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشان‌دار) کرده‌اید، به تفکیک نمایش داده می‌شوند. هم‌چنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.', fontSize: 14, color: Theme.of(context).colorScheme.title, textAlign: TextAlign.justify, ), Image.asset( Assets.bookmarkAnimation, width: MediaQuery.sizeOf(context).width, height: 180, ), ], ), placeholder: state.searching && state.bookmarks.isEmpty && state.bookmarkedSwotItems.isEmpty && !state.swotItemsLoading ? EmptyResult(onNewSearch: _focuseNode.requestFocus) : MultitypeOverview.placeholder, builder: (context, state, index) { if (index >= state.bookmarks.length) { return const Center(child: CircularProgressIndicator()); } return _FadeInSlide( delay: Duration(milliseconds: (index % 10) * 100), child: Padding( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), child: Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color.fromRGBO(184, 184, 184, 1), width: 1, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: MultitypeOverview( item: state.bookmarks[index], onMarkChanged: state.onMarkChanged, hasUnmarkConfirmation: true, enableCaption: true, enableBookmark: true, showDivider: false, ), ), ), ), ); }, itemPadding: const EdgeInsets.only(bottom: 8, left: 16, right: 16), childCount: state.bookmarks.length + (state.page != state.lastPage && state.bookmarks.isNotEmpty ? 1 : 0), onRetry: () => state.loadInitialData(), ), if (state.appState == AppState.idle && state.bookmarkedSwotItems.isNotEmpty) SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final item = state.bookmarkedSwotItems[index]; return _FadeInSlide( delay: Duration(milliseconds: (index % 10) * 100), child: Padding( padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16), child: Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color.fromRGBO(184, 184, 184, 1), width: 1, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: SwotBookmark( item: item, onSwotUnbookmarked: (postId) { state.onSwotMarkChanged(postId); }, ), ), ), ), ); }, childCount: state.bookmarkedSwotItems.length, ), ) else if (state.swotItemsLoading) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16.0), child: Center(child: MultitypeOverview.placeholder), ), ) ], ); } void _onCategorySelected(int type) { FocusScope.of(context).unfocus(); final state = context.read(); Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: { 'type': type, 'onDeleted': (int id) { state.bookmarks.removeWhere( (element) => element.id == id && element.typeInteger == type); if (type == 8) { state.bookmarkedSwotItems.removeWhere((element) => element.id == id); } state.update(); }, }).then((_) { state.loadInitialData(); }); } } // void _onChanged(String value) { // final state = context.read(); // if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { // return; // } // _timer?.cancel(); // _timer = Timer(const Duration(seconds: 1), () { // state.search = value; // state.getBookmarks(page: 1); // }); // } // } class _FadeInSlide extends StatefulWidget { final Widget child; final Duration delay; final Duration duration; final double slideOffset; const _FadeInSlide({ Key? key, required this.child, this.delay = Duration.zero, // ignore: unused_element_parameter this.duration = const Duration(milliseconds: 400), this.slideOffset = 50.0, // میزانی که ویجت از پایین به بالا حرکت می‌کند }) : super(key: key); @override State<_FadeInSlide> createState() => _FadeInSlideState(); } class _FadeInSlideState extends State<_FadeInSlide> { bool _isVisible = false; @override void initState() { super.initState(); Timer(widget.delay, () { if (mounted) { setState(() { _isVisible = true; }); } }); } @override Widget build(BuildContext context) { return AnimatedOpacity( opacity: _isVisible ? 1.0 : 0.0, duration: widget.duration, curve: Curves.easeOut, child: AnimatedContainer( duration: widget.duration, curve: Curves.easeOut, transform: Matrix4.translationValues( 0, _isVisible ? 0 : widget.slideOffset, 0), child: widget.child, ), ); } }