diff --git a/lib/assets/icons/Ravand.svg b/lib/assets/icons/Ravand.svg new file mode 100644 index 0000000..360fdf7 --- /dev/null +++ b/lib/assets/icons/Ravand.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/assets/icons/Risk.svg b/lib/assets/icons/Risk.svg new file mode 100644 index 0000000..2c5a171 --- /dev/null +++ b/lib/assets/icons/Risk.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/lib/assets/icons/Startup.svg b/lib/assets/icons/Startup.svg new file mode 100644 index 0000000..06c3951 --- /dev/null +++ b/lib/assets/icons/Startup.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/assets/icons/Technology.svg b/lib/assets/icons/Technology.svg new file mode 100644 index 0000000..e6c7ed6 --- /dev/null +++ b/lib/assets/icons/Technology.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/assets/icons/document-filter.svg b/lib/assets/icons/document-filter.svg new file mode 100644 index 0000000..88ed350 --- /dev/null +++ b/lib/assets/icons/document-filter.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/constants/app_icons.dart b/lib/constants/app_icons.dart index f535aef..caa2156 100644 --- a/lib/constants/app_icons.dart +++ b/lib/constants/app_icons.dart @@ -571,4 +571,6 @@ class DidvanIcons { IconData(0xe90e, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData font_size_solid = IconData(0xe90f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData document_filter = + IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/models/requests/news.dart b/lib/models/requests/news.dart index 1e17e56..e51d673 100644 --- a/lib/models/requests/news.dart +++ b/lib/models/requests/news.dart @@ -3,11 +3,13 @@ class NewsRequestArgs { final String? startDate; final String? endDate; final String? search; + final List? categories; const NewsRequestArgs({ required this.page, this.startDate, this.endDate, this.search, + this.categories, }); } diff --git a/lib/models/view/action_sheet_data.dart b/lib/models/view/action_sheet_data.dart index 4f3c2d5..991f14f 100644 --- a/lib/models/view/action_sheet_data.dart +++ b/lib/models/view/action_sheet_data.dart @@ -9,6 +9,7 @@ class ActionSheetData { final String? title; final bool hasPadding; final IconData? titleIcon; + final Widget? titleIconWidget; final Color? titleColor; final bool hasDismissButton; final bool hasConfirmButton; @@ -28,6 +29,7 @@ class ActionSheetData { this.hasDismissButton = true, this.hasConfirmButton = true, this.titleIcon, + this.titleIconWidget, this.dismissTitle, this.onDismissed, this.smallDismissButton = false, diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 68a9f99..a111787 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_print +// ignore_for_file: avoid_print, deprecated_member_use import 'package:didvan/models/ai/ai_chat_args.dart'; import 'package:didvan/models/requests/news.dart'; @@ -650,7 +650,6 @@ class RouteGenerator { transitionDuration: const Duration(milliseconds: 400), transitionsBuilder: (context, animation, secondaryAnimation, child) { switch (animationType) { - // <<--- تغییر اصلی اینجاست case 'slide': const begin = Offset(1.0, 0.0); const end = Offset.zero; diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 2b5cdbc..0c031fc 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -41,11 +41,15 @@ class RequestHelper { required int page, String? search, List? types, + String? startDate, + String? endDate, }) => '$_baseHomeUrl/mark/v2${_urlConcatGenerator([ MapEntry('page', page), MapEntry('q', search), MapEntry('type', _urlListConcatGenerator(types)), + MapEntry('start', startDate), + MapEntry('end', endDate), ])}'; static const String confirmUsername = '$_baseUserUrl/confirmUsername'; diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index 580bc5e..90e50ea 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -129,13 +129,16 @@ class ActionSheetUtils { if (data.title != null) Row( children: [ - if (data.titleIcon != null) + if (data.titleIconWidget != null) + data.titleIconWidget! + else if (data.titleIcon != null) Icon( data.titleIcon, color: data.titleColor ?? Theme.of(context).colorScheme.title, ), - if (data.titleIcon != null) const SizedBox(width: 8), + if (data.titleIcon != null || data.titleIconWidget != null) + const SizedBox(width: 8), DidvanText( data.title!, style: Theme.of(context).textTheme.titleMedium, @@ -151,29 +154,29 @@ class ActionSheetUtils { Row( children: [ if (data.hasDismissButton) - Expanded( - child: DidvanButton( - onPressed: () { - Navigator.of(context).pop(); - data.onDismissed?.call(); - }, - title: data.dismissTitle ?? 'بازگشت', - style: ButtonStyleMode.secondary, + if (data.hasConfirmButton) + Expanded( + flex: 2, + child: DidvanButton( + style: ButtonStyleMode.primary, + onPressed: () { + data.onConfirmed?.call(); + pop(); + }, + title: data.confrimTitle ?? 'تایید', + ), ), - ), if (data.hasDismissButton) const SizedBox(width: 20), - if (data.hasConfirmButton) - Expanded( - flex: data.smallDismissButton ? 2 : 1, - child: DidvanButton( - style: ButtonStyleMode.primary, - onPressed: () { - data.onConfirmed?.call(); - pop(); - }, - title: data.confrimTitle ?? 'تایید', - ), + Expanded( + child: DidvanButton( + onPressed: () { + Navigator.of(context).pop(); + data.onDismissed?.call(); + }, + title: data.dismissTitle ?? 'بازگشت', + style: ButtonStyleMode.secondary, ), + ), ], ), ], @@ -206,9 +209,7 @@ class ActionSheetUtils { data.backgroundColor ?? Theme.of(context).colorScheme.surface, ), width: mediaQueryData.size.width * 0.8, - // BEGIN: EDIT THIS LINE - padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), // پدینگ بالا از 24 به 16 تغییر کرد - // END: EDIT THIS LINE + padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -217,7 +218,12 @@ class ActionSheetUtils { Row( mainAxisSize: MainAxisSize.min, children: [ - if (data.titleIcon != null) + if (data.titleIconWidget != null) + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: data.titleIconWidget!, + ) + else if (data.titleIcon != null) GestureDetector( onTap: () => Navigator.of(context).pop(), child: Icon( @@ -226,7 +232,8 @@ class ActionSheetUtils { color: data.titleColor, ), ), - if (data.titleIcon != null) + if (data.titleIcon != null || + data.titleIconWidget != null) const SizedBox( width: 4, ), @@ -448,4 +455,4 @@ class ActionSheetUtils { DesignConfig.updateSystemUiOverlayStyle(); Navigator.of(context).pop(); } -} \ No newline at end of file +} diff --git a/lib/views/ai/history_ai_chat_page.dart b/lib/views/ai/history_ai_chat_page.dart index 2b0d172..85efcfd 100644 --- a/lib/views/ai/history_ai_chat_page.dart +++ b/lib/views/ai/history_ai_chat_page.dart @@ -33,8 +33,7 @@ class HistoryAiChatPage extends StatefulWidget { class _HistoryAiChatPageState extends State { final ScrollController scrollController = ScrollController(); - final GlobalKey scaffKey = - GlobalKey(); + final GlobalKey scaffKey = GlobalKey(); Timer? _timer; late bool archived = widget.archived ?? false; @@ -62,7 +61,6 @@ class _HistoryAiChatPageState extends State { } return true; }, - child: Scaffold( key: scaffKey, appBar: HoshanAppBar( @@ -73,12 +71,12 @@ class _HistoryAiChatPageState extends State { } Navigator.pop(context); }, - withActions: false, // شبیه به صفحه چت - withInfo: true, // برای نمایش دکمه بازگشت + withActions: false, + withInfo: true, ), drawer: HoshanDrawer( scaffKey: scaffKey, - ), // <-- اضافه شد + ), body: CustomScrollView( physics: const BouncingScrollPhysics(), controller: scrollController, @@ -88,8 +86,7 @@ class _HistoryAiChatPageState extends State { scrolledUnderElevation: 0, automaticallyImplyLeading: false, pinned: true, - toolbarHeight: - archived ? 50 : 110, // ارتفاع بر اساس آرشیو بودن تنظیم می‌شود + toolbarHeight: archived ? 50 : 110, flexibleSpace: Container( color: Theme.of(context).colorScheme.surface, padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), @@ -190,10 +187,8 @@ class _HistoryAiChatPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Icon( archived - ? Icons - .folder_delete // <-- آیکون کشیدن برای آرشیو - : Icons - .create_new_folder_rounded, // <-- آیکون کشیدن برای خروج از آرشیو + ? Icons.folder_delete + : Icons.create_new_folder_rounded, color: Theme.of(context).colorScheme.white), ), movementDuration: const Duration(milliseconds: 600), @@ -426,7 +421,6 @@ class _HistoryAiChatPageState extends State { ) ], ) - // --- شروع بلاک تغییر یافته --- : Row( mainAxisAlignment: MainAxisAlignment @@ -453,8 +447,6 @@ class _HistoryAiChatPageState extends State { .updatedAt .toString()) .toPersianDateStr(), - // DateTimeUtils.momentGenerator( - // chat.updatedAt.toString()), style: const TextStyle( fontSize: @@ -518,8 +510,8 @@ class _HistoryAiChatPageState extends State { archived); break; - case 'آرشیو': // برای آرشیو کردن - case 'خارج کردن از آرشیو': // برای خارج کردن + case 'آرشیو': + case 'خارج کردن از آرشیو': await state .archivedChat( chat.id!, @@ -632,7 +624,6 @@ class _HistoryAiChatPageState extends State { ), ], ), - // --- پایان بلاک تغییر یافته --- // if (chat.prompts != null && // chat.prompts!.isNotEmpty && @@ -664,7 +655,6 @@ class _HistoryAiChatPageState extends State { ) ], ), - // --- پایان تغییرات --- ), ); } diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index ac0f0ab..ff329ea 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -12,7 +12,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; -class Message extends StatefulWidget { // 1. تبدیل به StatefulWidget +class Message extends StatefulWidget { final MessageData message; const Message({Key? key, required this.message}) : super(key: key); @@ -20,9 +20,7 @@ class Message extends StatefulWidget { // 1. تبدیل به StatefulWidget State createState() => _MessageState(); } -class _MessageState extends State - with SingleTickerProviderStateMixin { // 2. اضافه کردن Mixin - +class _MessageState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _slideAnimation; late Animation _opacityAnimation; @@ -30,14 +28,13 @@ class _MessageState extends State @override void initState() { super.initState(); - // 3. مقداردهی اولیه کنترلر و انیمیشن ها _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _slideAnimation = Tween( - begin: const Offset(0, 0.5), // شروع از کمی پایین تر + begin: const Offset(0, 0.5), end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, @@ -45,19 +42,19 @@ class _MessageState extends State )); _opacityAnimation = Tween( - begin: 0.0, // شروع با شفافیت صفر + begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeIn, )); - _controller.forward(); // 4. اجرای انیمیشن + _controller.forward(); } @override void dispose() { - _controller.dispose(); // 5. آزاد کردن کنترلر + _controller.dispose(); super.dispose(); } @@ -68,15 +65,13 @@ class _MessageState extends State .dailyMessages[ widget.message.createdAt.replaceAll('T', ' ').split(' ').first]! .last; - - // 6. اعمال انیمیشن ها به ویجت + return FadeTransition( opacity: _opacityAnimation, child: SlideTransition( position: _slideAnimation, child: GestureDetector( onLongPress: () { - // 7. استفاده از widget.message if (state.deletionQueue.contains(widget.message.id) || widget.message.writedByAdmin) { return; @@ -85,7 +80,8 @@ class _MessageState extends State state.update(); }, onTap: () { - if (state.deletionQueue.isEmpty || widget.message.writedByAdmin) return; + if (state.deletionQueue.isEmpty || widget.message.writedByAdmin) + return; if (!state.deletionQueue.contains(widget.message.id)) { state.deletionQueue.add(widget.message.id); } else { @@ -149,7 +145,8 @@ class _MessageState extends State AudioWave( file: widget.message.audio!, totalDuration: widget.message.duration != null - ? Duration(seconds: widget.message.duration!) + ? Duration( + seconds: widget.message.duration!) : null, ), if (widget.message.radar != null || @@ -258,7 +255,8 @@ class _MessageState extends State ), ), DidvanText( - DateTimeUtils.timeWithAmPm(widget.message.createdAt), + DateTimeUtils.timeWithAmPm( + widget.message.createdAt), style: Theme.of(context).textTheme.labelSmall, color: Theme.of(context).colorScheme.caption, ), @@ -284,7 +282,6 @@ class _MessageState extends State } } -// ویجت های کمکی پایین بدون تغییر باقی می مانند class _ReplyRadarOverview extends StatelessWidget { final MessageData message; const _ReplyRadarOverview({Key? key, required this.message}) @@ -431,4 +428,4 @@ class _MessageContainer extends StatelessWidget { child: child, ); } -} \ No newline at end of file +} diff --git a/lib/views/home/bookmarks/bookmark_state.dart b/lib/views/home/bookmarks/bookmark_state.dart index 62eee14..c82ed6c 100644 --- a/lib/views/home/bookmarks/bookmark_state.dart +++ b/lib/views/home/bookmarks/bookmark_state.dart @@ -1,22 +1,27 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; -import 'package:didvan/models/home_page_content/swot.dart'; +import 'package:didvan/models/home_page_content/swot.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; -import 'package:didvan/views/home/bookmarks/bookmark_service.dart'; +import 'package:didvan/views/home/bookmarks/bookmark_service.dart'; import 'package:didvan/services/swot_service.dart'; class BookmarksState extends CoreProvier { - final List bookmarks = []; + final List bookmarks = []; List bookmarkedSwotItems = []; String search = ''; String lastSearch = ''; - int page = 1; + int page = 1; int lastPage = 1; + List selectedTypes = []; + DateTime? startDate; + DateTime? endDate; + bool get isFiltered => + selectedTypes.isNotEmpty || startDate != null || endDate != null; - bool _swotItemsLoading = false; + bool _swotItemsLoading = false; bool get searching => search.isNotEmpty; bool get swotItemsLoading => _swotItemsLoading; @@ -32,12 +37,13 @@ class BookmarksState extends CoreProvier { _fetchSwotBookmarks(), ]); - if (appState != AppState.failed && !searching) { appState = AppState.idle; - } else if (appState != AppState.failed && searching && bookmarks.isEmpty && bookmarkedSwotItems.isEmpty) { - - appState = AppState.idle; + } else if (appState != AppState.failed && + searching && + bookmarks.isEmpty && + bookmarkedSwotItems.isEmpty) { + appState = AppState.idle; } _swotItemsLoading = false; notifyListeners(); @@ -49,7 +55,13 @@ class BookmarksState extends CoreProvier { if (!searching) lastSearch = search; final service = RequestService( - RequestHelper.searchMarks(page: page, search: search.isNotEmpty ? search : null), + RequestHelper.searchMarks( + page: page, + search: search.isNotEmpty ? search : null, + types: selectedTypes.isNotEmpty ? selectedTypes : null, + startDate: startDate?.toIso8601String().split('T').first, + endDate: endDate?.toIso8601String().split('T').first, + ), ); await service.httpGet(); if (service.isSuccess) { @@ -59,21 +71,21 @@ class BookmarksState extends CoreProvier { bookmarks.add(OverviewData.fromJson(marks[i])); } } else { - appState = AppState.failed; + appState = AppState.failed; } } - + Future searchAndLoadData({required int page}) async { appState = AppState.busy; - _swotItemsLoading = true; - bookmarks.clear(); + _swotItemsLoading = true; + bookmarks.clear(); notifyListeners(); await Future.wait([ - _fetchGeneralBookmarks(page: page), + _fetchGeneralBookmarks(page: page), _fetchSwotBookmarks(), ]); - + if (appState != AppState.failed) { appState = AppState.idle; } @@ -81,9 +93,13 @@ class BookmarksState extends CoreProvier { notifyListeners(); } - Future _fetchSwotBookmarks() async { try { + if (selectedTypes.isNotEmpty && !selectedTypes.contains(8)) { + bookmarkedSwotItems = []; + return; + } + final postIds = await BookmarkService.fetchBookmarks(); if (postIds.isNotEmpty) { final allSwots = await SwotService.fetchSwotItems(); @@ -91,23 +107,37 @@ class BookmarksState extends CoreProvier { bookmarkedSwotItems = allSwots.where((swot) { final isBookmarked = postIds.contains(swot.id); if (search.isEmpty) return isBookmarked; - return isBookmarked && swot.title.toLowerCase().contains(search.toLowerCase()); + return isBookmarked && + swot.title.toLowerCase().contains(search.toLowerCase()); }).toList(); } else { bookmarkedSwotItems = []; } } catch (e) { - bookmarkedSwotItems = []; + bookmarkedSwotItems = []; } } - void onMarkChanged(int id, bool value) { + void onMarkChanged(int id, bool value) { bookmarks.removeWhere((element) => element.id == id); notifyListeners(); } void onSwotMarkChanged(int swotId) { - bookmarkedSwotItems.removeWhere((element) => element.id == swotId); - notifyListeners(); + bookmarkedSwotItems.removeWhere((element) => element.id == swotId); + notifyListeners(); } -} \ No newline at end of file + + void resetFilters([bool notify = true]) { + selectedTypes.clear(); + startDate = null; + endDate = null; + if (notify) { + loadInitialData(); + } + } + + void update() { + notifyListeners(); + } +} diff --git a/lib/views/home/bookmarks/bookmarks.dart b/lib/views/home/bookmarks/bookmarks.dart index c87bb7f..a719ca7 100644 --- a/lib/views/home/bookmarks/bookmarks.dart +++ b/lib/views/home/bookmarks/bookmarks.dart @@ -1,14 +1,20 @@ +// 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'; @@ -60,6 +66,108 @@ class _BookmarksState extends State { }); } + 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(); @@ -110,7 +218,7 @@ class _BookmarksState extends State { ), GestureDetector( onTap: () => Navigator.pop(context), - child: Container( + child: SizedBox( width: 44, height: 44, child: Center( @@ -135,12 +243,8 @@ class _BookmarksState extends State { title: 'دیدوان', focusNode: _focuseNode, onChanged: _onSearchChanged, - onFilterButtonPressed: () { - //TODO:fix this - // Filter functionality for bookmarks - // Can be implemented later if needed - }, - isFiltered: false, + onFilterButtonPressed: _showFilterBottomSheet, + isFiltered: state.isFiltered, extraIconPath: 'lib/assets/icons/live ai.svg', onExtraIconPressed: () { showDialog( @@ -205,107 +309,158 @@ class _BookmarksState extends State { ), ), const SizedBox(height: 15), - MenuOption( - onTap: () => _onCategorySelected(5), - iconWidget: SvgPicture.asset( - "lib/assets/icons/Stratzhic Radar.svg", - width: 20, + _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))), ), - titleWidget: const DidvanText('رادارهای استراتژیک', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(2), - iconWidget: SvgPicture.asset( - "lib/assets/icons/Donye_Foolad.svg", - width: 20, + 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))), ), - titleWidget: const DidvanText('دنیای فولاد', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(1), - iconWidget: SvgPicture.asset( - "lib/assets/icons/Pouyesh_Ofogh_New.svg", - width: 20, + 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))), ), - titleWidget: const DidvanText('پویش افق', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(3), - iconWidget: SvgPicture.asset( - "lib/assets/icons/video-play.svg", - color: const Color.fromARGB(255, 0, 126, 167), - width: 20, + 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))), ), - titleWidget: const DidvanText('ویدیو‌کست', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(4), - iconWidget: SvgPicture.asset( - "lib/assets/icons/microphone-2.svg", - color: const Color.fromARGB(255, 0, 126, 167), - width: 20, + 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))), ), - titleWidget: const DidvanText('پادکست', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(6), - iconWidget: SvgPicture.asset( - "lib/assets/icons/Saha.svg", - width: 20, + 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))), ), - titleWidget: const DidvanText('سها', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - MenuOption( - onTap: () => _onCategorySelected(7), - iconWidget: SvgPicture.asset( - "lib/assets/icons/hugeicons_chart-02.svg", - color: const Color.fromARGB(255, 0, 126, 167), - width: 20, + 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))), ), - titleWidget: const DidvanText('اینفوگرافی', - style: TextStyle( - color: Color.fromARGB(255, 102, 102, 102))), ), - const DidvanDivider(), - 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: 750), + child: DidvanDivider(), ), - const DidvanDivider(), - 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, + _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), ], @@ -350,26 +505,29 @@ class _BookmarksState extends State { if (index >= state.bookmarks.length) { return const Center(child: CircularProgressIndicator()); } - return 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, + 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, + 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, + ), ), ), ), @@ -388,25 +546,28 @@ class _BookmarksState extends State { delegate: SliverChildBuilderDelegate( (context, index) { final item = state.bookmarkedSwotItems[index]; - return 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, + 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); - }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SwotBookmark( + item: item, + onSwotUnbookmarked: (postId) { + state.onSwotMarkChanged(postId); + }, + ), ), ), ), @@ -447,14 +608,66 @@ class _BookmarksState extends State { } } - // 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); - // }); - // } \ No newline at end of file +// 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, + ), + ); + } +} diff --git a/lib/views/home/explore/explore.dart b/lib/views/home/explore/explore.dart index ada2efc..9a17b4f 100644 --- a/lib/views/home/explore/explore.dart +++ b/lib/views/home/explore/explore.dart @@ -1,5 +1,3 @@ -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/main.dart'; import 'package:didvan/models/home_page_content/home_page_list.dart'; import 'package:didvan/models/home_page_content/swot.dart'; @@ -276,12 +274,12 @@ class SwotSection extends StatelessWidget { Row( children: [ SvgPicture.asset( - "lib/assets/images/features/Saha Solid.svg", - color: Theme.of(context).colorScheme.title, + "lib/assets/icons/Swot_New.svg", + color: const Color.fromARGB(255, 0, 126, 167), ), const SizedBox(width: 5), DidvanText( - "بایدها و نبایدها", + "ماژول بایدها و نبایدها", style: Theme.of(context).textTheme.titleSmall, color: const Color.fromARGB(255, 0, 89, 119), ), @@ -357,28 +355,28 @@ class MainPageSection extends StatelessWidget { Navigator.of(context).pushNamed(list.link); } - IconData? _generateIcon() { + String? _generateIcon() { switch (list.type) { case 'monthly': - return DidvanIcons.radar_solid; + return 'lib/assets/icons/Monthly.svg'; case 'news': - return DidvanIcons.foolad_solid; + return 'lib/assets/icons/Donye_Foolad.svg'; case 'radar': - return DidvanIcons.scanning_solid; + return 'lib/assets/icons/Pouyesh_Ofogh_New.svg'; case 'video': - return DidvanIcons.video_solid; + return 'lib/assets/icons/video-play.svg'; case 'podcast': - return DidvanIcons.podcast_solid; + return 'lib/assets/icons/play-circle.svg'; case 'trend': - return DidvanIcons.chart_solid; + return 'lib/assets/icons/Ravand.svg'; case 'technology': - return DidvanIcons.technology_solid; + return 'lib/assets/icons/Technology.svg'; case 'risk': - return DidvanIcons.exclamation_triangle_solid; + return 'lib/assets/icons/risk-radar.svg'; //TODO:FIX ICON case 'startup': - return DidvanIcons.startup_solid; + return 'lib/assets/icons/Startup.svg'; case 'survey': - return DidvanIcons.saha_solid; + return 'lib/assets/icons/Saha.svg'; default: return null; } @@ -445,7 +443,7 @@ class MainPageSection extends StatelessWidget { @override Widget build(BuildContext context) { - final icon = _generateIcon(); + final iconPath = _generateIcon(); if (list.contents.isEmpty) { return const SizedBox(); @@ -453,13 +451,13 @@ class MainPageSection extends StatelessWidget { return Column( children: [ - _buildSectionHeader(context, icon), + _buildSectionHeader(context, iconPath), _buildSectionSlider(context), ], ); } - Padding _buildSectionHeader(BuildContext context, IconData? icon) { + Padding _buildSectionHeader(BuildContext context, String? iconPath) { String headerText = list.header; if (list.type == 'news') { headerText = 'دنیای فولاد'; @@ -483,10 +481,12 @@ class MainPageSection extends StatelessWidget { children: [ Row( children: [ - if (icon != null) - Icon( - icon, - color: Theme.of(context).colorScheme.title, + if (iconPath != null) + SvgPicture.asset( + iconPath, + width: 24, + height: 24, + color: const Color.fromRGBO(0, 126, 167, 1), ), const SizedBox(width: 4), DidvanText( diff --git a/lib/views/monthly/monthly_list_page.dart b/lib/views/monthly/monthly_list_page.dart index 7825aba..7a76a4f 100644 --- a/lib/views/monthly/monthly_list_page.dart +++ b/lib/views/monthly/monthly_list_page.dart @@ -1,12 +1,18 @@ import 'dart:async'; +import 'package:didvan/constants/app_icons.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/services/network/request_helper.dart'; +import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/monthly/monthly_list_state.dart'; +import 'package:didvan/views/widgets/date_picker_button.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/home_app_bar.dart'; +import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; @@ -29,7 +35,9 @@ class _MonthlyListPageState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().fetchMonthlyList(); + final state = context.read(); + state.initCategories(); + state.fetchMonthlyList(); }); } @@ -42,6 +50,88 @@ class _MonthlyListPageState extends State { }); } + Future _showFilterBottomSheet() async { + final state = context.read(); + await ActionSheetUtils(context).showBottomSheet( + data: ActionSheetData( + title: 'فیلتر جستجو', + smallDismissButton: true, + titleIcon: DidvanIcons.filter_regular, + dismissTitle: 'حذف فیلتر', + confrimTitle: 'نمایش نتایج', + onDismissed: () => state.resetFilters(false), + onConfirmed: () { + state.filtering = state.startDate != null || + state.endDate != null || + state.selectedCats.isNotEmpty; + state.fetchMonthlyList(); + }, + content: StatefulBuilder( + builder: (context, setState) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ItemTitle( + title: 'تاریخ ایجاد', + style: Theme.of(context).textTheme.bodyMedium, + icon: DidvanIcons.calendar_range_regular, + ), + const SizedBox(height: 8), + Row( + children: [ + DatePickerButton( + initialValue: state.startDate, + emptyText: 'از تاریخ', + onPicked: (date) => setState(() => state.startDate = date), + lastDate: state.endDate, + ), + const SizedBox(width: 8), + DatePickerButton( + initialValue: state.endDate, + emptyText: 'تا تاریخ', + onPicked: (date) => setState(() => state.endDate = date), + firstDate: state.startDate, + ), + ], + ), + const SizedBox(height: 28), + if (state.categories.isNotEmpty) ...[ + ItemTitle( + title: 'دسته بندی', + icon: DidvanIcons.category_regular, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Wrap( + children: [ + for (var category in state.categories) + SizedBox( + width: (MediaQuery.of(context).size.width - 40) / 2, + child: DidvanCheckbox( + title: category.label, + value: state.selectedCats + .any((cat) => cat.id == category.id), + onChanged: (value) { + setState(() { + if (value) { + state.selectedCats.add(category); + } else { + state.selectedCats.removeWhere( + (cat) => cat.id == category.id); + } + }); + }, + ), + ), + ], + ), + ], + ], + ), + ), + ), + ); + } + @override void dispose() { _timer?.cancel(); @@ -67,7 +157,9 @@ class _MonthlyListPageState extends State { showBackButton: false, showSearchField: state.appState != AppState.failed, onSearchChanged: _onChanged, + onFilterPressed: _showFilterBottomSheet, searchFocusNode: _focusNode, + isFiltered: state.filtering, searchValue: state.searchQuery, ), ), diff --git a/lib/views/monthly/monthly_list_state.dart b/lib/views/monthly/monthly_list_state.dart index 0acc276..328f679 100644 --- a/lib/views/monthly/monthly_list_state.dart +++ b/lib/views/monthly/monthly_list_state.dart @@ -1,4 +1,6 @@ import 'dart:convert'; +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/category.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/monthly/monthly_model.dart'; import 'package:didvan/providers/core.dart'; @@ -11,6 +13,48 @@ class MonthlyListState extends CoreProvier { List monthlyItems = []; List _allMonthlyItems = []; String searchQuery = ''; + String? startDate; + String? endDate; + final List selectedCats = []; + List categories = []; + bool filtering = false; + + void initCategories() { + categories = [ + CategoryData( + id: 1, + label: 'اقتصادی', + asset: Assets.economicCategoryIcon, + ), + CategoryData( + id: 2, + label: 'سیاسی', + asset: Assets.politicalCategoryIcon, + ), + CategoryData( + id: 3, + label: 'فناوری', + asset: Assets.techCategoryIcon, + ), + CategoryData( + id: 4, + label: 'کسب و کار', + asset: Assets.businessCategoryIcon, + ), + CategoryData( + id: 5, + label: 'زیست‌محیطی', + asset: Assets.enviromentalCategoryIcon, + ), + CategoryData( + id: 6, + label: 'اجتماعی', + asset: Assets.socialCategoryIcon, + ), + ]; + } + + bool get searching => searchQuery.isNotEmpty; Future fetchMonthlyList() async { appState = AppState.busy; @@ -38,12 +82,13 @@ class MonthlyListState extends CoreProvier { final List resultList = jsonData['result'] as List; debugPrint('Found ${resultList.length} items'); - monthlyItems = + _allMonthlyItems = resultList.map((item) => MonthlyItem.fromJson(item)).toList(); - _allMonthlyItems = List.from(monthlyItems); + monthlyItems = _applyFilters(_allMonthlyItems); - debugPrint('Successfully parsed ${monthlyItems.length} monthly items'); + debugPrint( + 'Successfully parsed ${monthlyItems.length} monthly items after filters'); appState = AppState.idle; } else { debugPrint('Request failed with status code: ${response.statusCode}'); @@ -59,15 +104,17 @@ class MonthlyListState extends CoreProvier { void search(String query) { searchQuery = query; - if (query.isEmpty) { - monthlyItems = List.from(_allMonthlyItems); - } else { - monthlyItems = _allMonthlyItems + var filtered = _allMonthlyItems; + + if (query.isNotEmpty) { + filtered = filtered .where((item) => item.title.toLowerCase().contains(query.toLowerCase()) || item.description.toLowerCase().contains(query.toLowerCase())) .toList(); } + + monthlyItems = _applyFilters(filtered); notifyListeners(); } @@ -148,4 +195,45 @@ class MonthlyListState extends CoreProvier { } } } + + void resetFilters([bool notify = true]) { + startDate = null; + endDate = null; + selectedCats.clear(); + filtering = false; + if (notify) { + fetchMonthlyList(); + } + } + + List _applyFilters(List items) { + var filtered = items; + + if (startDate != null || endDate != null) { + filtered = filtered.where((item) { + final itemDate = DateTime.parse(item.publishedAt); + + if (startDate != null) { + final start = DateTime.parse(startDate!); + if (itemDate.isBefore(start)) return false; + } + + if (endDate != null) { + final end = DateTime.parse(endDate!).add(const Duration(days: 1)); + if (itemDate.isAfter(end)) return false; + } + + return true; + }).toList(); + } + + if (selectedCats.isNotEmpty) { + filtered = filtered.where((item) { + return item.tags + .any((tag) => selectedCats.any((cat) => cat.id == tag.id)); + }).toList(); + } + + return filtered; + } } diff --git a/lib/views/news/news.dart b/lib/views/news/news.dart index 3b60462..6778808 100644 --- a/lib/views/news/news.dart +++ b/lib/views/news/news.dart @@ -8,6 +8,7 @@ import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/news/news_state.dart'; import 'package:didvan/views/widgets/date_picker_button.dart'; +import 'package:didvan/views/widgets/didvan/checkbox.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/home_app_bar.dart'; import 'package:didvan/views/widgets/item_title.dart'; @@ -68,10 +69,9 @@ class _NewsState extends State { title: Text( 'دنیای فولاد', style: theme.textTheme.headlineSmall?.copyWith( - color: const Color.fromARGB(255, 0, 53, 70), - fontWeight: FontWeight.bold, - fontSize: 19 - ), + color: const Color.fromARGB(255, 0, 53, 70), + fontWeight: FontWeight.bold, + fontSize: 19), ), actions: [ IconButton( @@ -99,7 +99,8 @@ class _NewsState extends State { childCount: min(state.visibleCount, state.news.length) + (_hasMoreItems(state) ? 1 : 0), builder: (context, state, index) { - final currentDisplayCount = min(state.visibleCount, state.news.length); + final currentDisplayCount = + min(state.visibleCount, state.news.length); if (index == currentDisplayCount && _hasMoreItems(state)) { return _buildLoadMoreButton(context, state); @@ -122,7 +123,8 @@ class _NewsState extends State { onLikedChanged: state.onLikedChanged, ); }, - enableEmptyState: state.news.isEmpty && state.appState != AppState.busy, + enableEmptyState: + state.news.isEmpty && state.appState != AppState.busy, emptyState: EmptyResult( onNewSearch: () => _focusNode.requestFocus(), ), @@ -134,17 +136,19 @@ class _NewsState extends State { } bool _hasMoreItems(NewsState state) { - return state.news.length > state.visibleCount || state.page < state.lastPage; + return state.news.length > state.visibleCount || + state.page < state.lastPage; } Widget _buildLoadMoreButton(BuildContext context, NewsState state) { - if (state.appState == AppState.busy && state.news.length <= state.visibleCount) { - return const Padding( + if (state.appState == AppState.busy && + state.news.length <= state.visibleCount) { + return const Padding( padding: EdgeInsets.all(16.0), child: Center(child: CircularProgressIndicator()), ); } - + return Padding( padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), child: Center( @@ -166,7 +170,8 @@ class _NewsState extends State { children: [ SvgPicture.asset( 'lib/assets/icons/element-plus.svg', - colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + colorFilter: + const ColorFilter.mode(Colors.white, BlendMode.srcIn), width: 20, ), const SizedBox(width: 8), @@ -209,37 +214,69 @@ class _NewsState extends State { confrimTitle: 'نمایش نتایج', onDismissed: state.resetFilters, onConfirmed: () => state.getNews(page: 1), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ItemTitle( - title: 'تاریخ خبر', - style: Theme.of(context).textTheme.bodyMedium, - icon: DidvanIcons.calendar_range_regular, - ), - const SizedBox(height: 8), - StatefulBuilder( - builder: (context, setState) => Row( - children: [ - DatePickerButton( - initialValue: state.startDate, - emptyText: 'از تاریخ', - onPicked: (date) { - setState(() => state.startDate = date); - }, - lastDate: state.endDate, - ), - const SizedBox(width: 8), - DatePickerButton( - initialValue: state.endDate, - emptyText: 'تا تاریخ', - onPicked: (date) => setState(() => state.endDate = date), - firstDate: state.startDate, - ), - ], - ), - ), - ], + content: StatefulBuilder( + builder: (context, setState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ItemTitle( + title: 'تاریخ خبر', + style: Theme.of(context).textTheme.bodyMedium, + icon: DidvanIcons.calendar_range_regular, + ), + const SizedBox(height: 8), + Row( + children: [ + DatePickerButton( + initialValue: state.startDate, + emptyText: 'از تاریخ', + onPicked: (date) { + setState(() => state.startDate = date); + }, + lastDate: state.endDate, + ), + const SizedBox(width: 8), + DatePickerButton( + initialValue: state.endDate, + emptyText: 'تا تاریخ', + onPicked: (date) => setState(() => state.endDate = date), + firstDate: state.startDate, + ), + ], + ), + const SizedBox(height: 28), + ItemTitle( + title: 'دسته بندی', + icon: DidvanIcons.category_regular, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Wrap( + children: [ + for (var i = 0; i < state.categories.length; i++) + SizedBox( + width: (MediaQuery.of(context).size.width - 40) / 2, + child: DidvanCheckbox( + title: state.categories[i].label, + value: state.selectedCats + .any((cat) => cat.id == state.categories[i].id), + onChanged: (value) { + setState(() { + if (value) { + state.selectedCats.add(state.categories[i]); + } else { + state.selectedCats.removeWhere( + (cat) => cat.id == state.categories[i].id); + } + }); + }, + ), + ), + ], + ), + ], + ); + }, ), ), ); diff --git a/lib/views/news/news_state.dart b/lib/views/news/news_state.dart index 6e00a1b..a7e01d4 100644 --- a/lib/views/news/news_state.dart +++ b/lib/views/news/news_state.dart @@ -1,3 +1,5 @@ +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/category.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/news.dart'; @@ -13,7 +15,8 @@ class NewsState extends CoreProvier { int page = 1; int lastPage = 0; int visibleCount = 4; - + final List selectedCats = []; + List categories = []; final List news = []; void init() { @@ -25,6 +28,39 @@ class NewsState extends CoreProvier { getNews(page: 1); }); visibleCount = 4; + + categories = [ + CategoryData( + id: 1, + label: 'اقتصادی', + asset: Assets.economicCategoryIcon, + ), + CategoryData( + id: 2, + label: 'سیاسی', + asset: Assets.politicalCategoryIcon, + ), + CategoryData( + id: 3, + label: 'فناوری', + asset: Assets.techCategoryIcon, + ), + CategoryData( + id: 4, + label: 'کسب و کار', + asset: Assets.businessCategoryIcon, + ), + CategoryData( + id: 5, + label: 'زیست‌محیطی', + asset: Assets.enviromentalCategoryIcon, + ), + CategoryData( + id: 6, + label: 'اجتماعی', + asset: Assets.socialCategoryIcon, + ), + ]; } void resetFilters() { @@ -63,6 +99,7 @@ class NewsState extends CoreProvier { startDate: startDate?.split(' ').first, endDate: endDate?.split(' ').first, search: search == '' ? null : search, + categories: selectedCats.map((e) => e.id).toList(), ), ), ); @@ -93,5 +130,6 @@ class NewsState extends CoreProvier { } } - bool get isFiltering => startDate != null || endDate != null; + bool get isFiltering => + startDate != null || endDate != null || selectedCats.isNotEmpty; } diff --git a/lib/views/notification_settings/notification_settings.dart b/lib/views/notification_settings/notification_settings.dart index e66cdda..947b09c 100644 --- a/lib/views/notification_settings/notification_settings.dart +++ b/lib/views/notification_settings/notification_settings.dart @@ -56,7 +56,7 @@ class _NotificationSettingsState extends State } void _startAnimationIfNeeded() { - if (!_hasAnimated && + if (!_hasAnimated && customizeState.appState == AppState.idle && notificationTimeState.appState == AppState.idle) { _hasAnimated = true; @@ -137,7 +137,7 @@ class _NotificationSettingsState extends State builder: (context, state, child) { if (state.appState == AppState.idle) { _startAnimationIfNeeded(); - + final mediaKeywords = [ 'پادکست', 'ویدئوکست', @@ -281,8 +281,7 @@ class _NotificationSettingsState extends State isDisabled: state.isAnytime, onTimeChanged: (newTime) { state.selectedTime = newTime; - state - .update(); // به‌روزرسانی state برای اعمال تغییرات در انیمیشن و UI + state.update(); }, ), // Padding( diff --git a/lib/views/pdf_viewer/pdf_viewer_page.dart b/lib/views/pdf_viewer/pdf_viewer_page.dart index a17b8d2..2abda31 100644 --- a/lib/views/pdf_viewer/pdf_viewer_page.dart +++ b/lib/views/pdf_viewer/pdf_viewer_page.dart @@ -27,12 +27,10 @@ class _PdfViewerPageState extends State { @override void initState() { super.initState(); - // Add access token to URL if needed _fullPdfUrl = widget.pdfUrl.contains('?') ? 'https://api.didvan.app${widget.pdfUrl}' : 'https://api.didvan.app${widget.pdfUrl}'; - // Debug: Print the PDF URL if (kDebugMode) { print('Original PDF URL: ${widget.pdfUrl}'); print('Full PDF URL : $_fullPdfUrl'); @@ -103,9 +101,9 @@ class _PdfViewerPageState extends State { color: Colors.red, ), const SizedBox(height: 16), - DidvanText( + const DidvanText( 'خطا در بارگذاری PDF', - style: const TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), diff --git a/lib/views/profile/direct_list/direct_list.dart b/lib/views/profile/direct_list/direct_list.dart index 495be85..6e4f3bc 100644 --- a/lib/views/profile/direct_list/direct_list.dart +++ b/lib/views/profile/direct_list/direct_list.dart @@ -80,126 +80,128 @@ class _DirectListState extends State return Consumer( builder: (context, state, child) { _startAnimationIfNeeded(state); - + return DidvanScaffold( padding: const EdgeInsets.symmetric(vertical: 16), appBarData: null, showSliversFirst: true, slivers: [ - SliverAppBar( - pinned: true, - backgroundColor: Theme.of(context).colorScheme.surface, - automaticallyImplyLeading: false, - leadingWidth: 200, - leading: Padding( - padding: const EdgeInsetsDirectional.only(start: 0.0), - child: SvgPicture.asset( - Assets.horizontalLogoWithText, - fit: BoxFit.contain, - height: 80, + SliverAppBar( + pinned: true, + backgroundColor: Theme.of(context).colorScheme.surface, + automaticallyImplyLeading: false, + leadingWidth: 200, + leading: Padding( + padding: const EdgeInsetsDirectional.only(start: 0.0), + child: SvgPicture.asset( + Assets.horizontalLogoWithText, + fit: BoxFit.contain, + height: 80, + ), ), + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).pushNamed(Routes.bookmarks); + }, + icon: SvgPicture.asset( + 'lib/assets/icons/hugeicons_telescope-01.svg')), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: SvgPicture.asset( + 'lib/assets/icons/arrow-left.svg', + color: const Color.fromARGB(255, 102, 102, 102), + )), + const SizedBox(width: 8), + ], ), - actions: [ - IconButton( - onPressed: () { - Navigator.of(context).pushNamed(Routes.bookmarks); - }, - icon: SvgPicture.asset( - 'lib/assets/icons/hugeicons_telescope-01.svg')), - IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: SvgPicture.asset( - 'lib/assets/icons/arrow-left.svg', - color: const Color.fromARGB(255, 102, 102, 102), - )), - const SizedBox(width: 8), - ], - ), - SliverToBoxAdapter( - child: _buildAnimatedSection( - index: 0, - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - TextButton.icon( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - const Color.fromARGB(255, 0, 126, 167), + SliverToBoxAdapter( + child: _buildAnimatedSection( + index: 0, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextButton.icon( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + const Color.fromARGB(255, 0, 126, 167), + ), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), ), - shape: - MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + onPressed: () { + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': 'پشتیبانی اپلیکیشن'}, + ).then((value) { + if (mounted) { + context + .read() + .getDirectsList(); + } + }); + }, + icon: SvgPicture.asset( + 'lib/assets/icons/add.svg', + height: 20, + color: Theme.of(context).colorScheme.surface, + ), + label: Text( + 'تیکت جدید', + style: TextStyle( + color: Theme.of(context).colorScheme.surface, + fontWeight: FontWeight.bold, ), ), ), - onPressed: () { - Navigator.of(context).pushNamed( - Routes.direct, - arguments: {'type': 'پشتیبانی اپلیکیشن'}, - ).then((value) { - if (mounted) { - context.read().getDirectsList(); - } - }); - }, - icon: SvgPicture.asset( - 'lib/assets/icons/add.svg', - height: 20, - color: Theme.of(context).colorScheme.surface, - ), - label: Text( - 'تیکت جدید', - style: TextStyle( - color: Theme.of(context).colorScheme.surface, - fontWeight: FontWeight.bold, - ), - ), - ), - // if (state.unreadCount > 0) - // Padding( - // padding: const EdgeInsetsDirectional.only(start: 8), - // child: DidvanBadge( - // text: state.unreadCount.toString(), - // ), - // ), - ], - ), - ], + // if (state.unreadCount > 0) + // Padding( + // padding: const EdgeInsetsDirectional.only(start: 8), + // child: DidvanBadge( + // text: state.unreadCount.toString(), + // ), + // ), + ], + ), + ], + ), ), ), ), - ), - SliverStateHandler( - onRetry: state.getDirectsList, - itemPadding: const EdgeInsets.symmetric(horizontal: 16), - state: state, - placeholder: const _ChatRoomPlaceholder(), - builder: (context, state, index) => _buildAnimatedSection( - index: index + 1, - child: ChatRoomItem( - chatRoom: state.chatRooms[index], - onBackButtonPressed: () { - state.chatRooms.clear(); - state.getDirectsList(); - }, + SliverStateHandler( + onRetry: state.getDirectsList, + itemPadding: const EdgeInsets.symmetric(horizontal: 16), + state: state, + placeholder: const _ChatRoomPlaceholder(), + builder: (context, state, index) => _buildAnimatedSection( + index: index + 1, + child: ChatRoomItem( + chatRoom: state.chatRooms[index], + onBackButtonPressed: () { + state.chatRooms.clear(); + state.getDirectsList(); + }, + ), + ), + childCount: state.chatRooms.length, + enableEmptyState: state.chatRooms.isEmpty, + emptyState: EmptyState( + asset: Assets.emptyBookmark, + title: 'پیامی وجود ندارد', ), ), - childCount: state.chatRooms.length, - enableEmptyState: state.chatRooms.isEmpty, - emptyState: EmptyState( - asset: Assets.emptyBookmark, - title: 'پیامی وجود ندارد', - ), - ), - ], + ], ); }, ); diff --git a/lib/views/widgets/home_app_bar.dart b/lib/views/widgets/home_app_bar.dart index 97c507c..1ed589d 100644 --- a/lib/views/widgets/home_app_bar.dart +++ b/lib/views/widgets/home_app_bar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'package:didvan/constants/assets.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/routes/routes.dart'; @@ -5,7 +7,6 @@ import 'package:didvan/views/widgets/search_field.dart'; import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/views/widgets/didvan/checkbox.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/date_picker_button.dart'; @@ -148,7 +149,7 @@ class HomeAppBar extends StatelessWidget { if (onFilterPressed != null) { onFilterPressed!(); } else { - _showFilterBottomSheet(context); + showFilterBottomSheet(context); } }, isFiltered: isFiltered ?? homeState?.filtering ?? false, @@ -167,12 +168,17 @@ class HomeAppBar extends StatelessWidget { ); } - Future _showFilterBottomSheet(BuildContext context) async { + Future showFilterBottomSheet(BuildContext context) async { final state = Provider.of(context, listen: false); ActionSheetUtils(context).showBottomSheet( data: ActionSheetData( - titleIcon: DidvanIcons.filter_regular, + title: 'فیلتر جستجو', + titleIconWidget: SvgPicture.asset( + 'lib/assets/icons/document-filter.svg', + width: 24, + height: 24, + ), dismissTitle: 'حذف فیلتر', confrimTitle: 'نمایش نتایج', onDismissed: () => state.resetFilters(false), @@ -180,11 +186,15 @@ class HomeAppBar extends StatelessWidget { content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (state.currentPageIndex != 3) ...[ + if (state.currentPageIndex != 9) ...[ ItemTitle( - title: 'تاریخ ایجاد', - style: Theme.of(context).textTheme.bodyMedium, - icon: DidvanIcons.calendar_range_regular, + 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( @@ -211,8 +221,12 @@ class HomeAppBar extends StatelessWidget { ], ItemTitle( title: 'دسته بندی', - icon: DidvanIcons.category_regular, - style: Theme.of(context).textTheme.bodyMedium, + 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( diff --git a/lib/views/widgets/hoshan_home_app_bar.dart b/lib/views/widgets/hoshan_home_app_bar.dart index ea9f474..c2d3c24 100644 --- a/lib/views/widgets/hoshan_home_app_bar.dart +++ b/lib/views/widgets/hoshan_home_app_bar.dart @@ -1,5 +1,5 @@ import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; // <-- این خط اضافه شد +import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/ai/ai_chat_args.dart'; import 'package:didvan/providers/user.dart'; @@ -276,18 +276,18 @@ class _HistoryDrawerContentState extends State { String _getMonthName(int month) { const monthNames = [ - 'فروردین', // 1 - 'اردیبهشت', // 2 - 'خرداد', // 3 - 'تیر', // 4 - 'مرداد', // 5 - 'شهریور', // 6 - 'مهر', // 7 - 'آبان', // 8 - 'آذر', // 9 - 'دی', // 10 - 'بهمن', // 11 - 'اسفند', // 12 + 'فروردین', + 'اردیبهشت', + 'خرداد', + 'تیر', + 'مرداد', + 'شهریور', + 'مهر', + 'آبان', + 'آذر', + 'دی', + 'بهمن', + 'اسفند', ]; if (month >= 1 && month <= 12) { @@ -298,9 +298,7 @@ class _HistoryDrawerContentState extends State { @override Widget build(BuildContext context) { - // --- اضافه شد --- final theme = Theme.of(context); - // --- --- return Align( alignment: Alignment.centerLeft, @@ -429,21 +427,20 @@ class _HistoryDrawerContentState extends State { ], ), ), - // --- شروع کد اضافه شده --- Padding( padding: const EdgeInsets.only(top: 30.0), child: InkWell( onTap: () { - // بستن دراور - Navigator.pop(context); - // نویگیت به صفحه آرشیو - Navigator.of(context).pushNamed(Routes.aiArchivedHistory); + Navigator.pop(context); + Navigator.of(context) + .pushNamed(Routes.aiArchivedHistory); }, child: Row( children: [ SvgPicture.asset( 'lib/assets/icons/direct-inbox.svg', - color: const Color.fromARGB(255, 61, 61, 61), + color: + const Color.fromARGB(255, 61, 61, 61), height: 20, ), const SizedBox(width: 8), @@ -459,7 +456,6 @@ class _HistoryDrawerContentState extends State { ), ), ), - // --- پایان کد اضافه شده --- ], ), ), @@ -723,8 +719,8 @@ class _HistoryDrawerContentState extends State { const PopupMenuDivider(height: 10), ], ), - ], - ), + ], + ), ], ], ), @@ -797,4 +793,4 @@ class _HistoryDrawerContentState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/views/widgets/item_title.dart b/lib/views/widgets/item_title.dart index be2fc13..34fca06 100644 --- a/lib/views/widgets/item_title.dart +++ b/lib/views/widgets/item_title.dart @@ -1,12 +1,14 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class ItemTitle extends StatelessWidget { final String title; final TextStyle? style; final IconData? icon; final Color? color; + final String? iconWidget; const ItemTitle({ Key? key, @@ -14,6 +16,7 @@ class ItemTitle extends StatelessWidget { this.icon, this.color, this.style, + this.iconWidget, }) : super(key: key); @override @@ -23,9 +26,11 @@ class ItemTitle extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ + if (iconWidget != null) + SvgPicture.asset(iconWidget!, color: color), if (icon != null) Icon(icon, color: color ?? Theme.of(context).colorScheme.title), - if (icon != null) const SizedBox(width: 4), + if (iconWidget != null || icon != null) const SizedBox(width: 4), DidvanText( title, style: style ?? Theme.of(context).textTheme.titleMedium, diff --git a/lib/views/widgets/search_app_bar.dart b/lib/views/widgets/search_app_bar.dart index c158f32..c92428a 100644 --- a/lib/views/widgets/search_app_bar.dart +++ b/lib/views/widgets/search_app_bar.dart @@ -68,7 +68,6 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget { void _onChanged(String value, BuildContext context) { final state = context.read(); - // Use the improved search functionality from HomeState if (value.length >= 2) { state.onSearchChanged(value); } else if (value.isEmpty) { @@ -90,7 +89,7 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget { content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (state.currentPageIndex != 3) ...[ + if (state.currentPageIndex != 9) ...[ ItemTitle( title: 'تاریخ ایجاد', style: Theme.of(context).textTheme.bodyMedium,