From 241a3d8f78978d774d5bb8b4dfa1c8800f4c0d32 Mon Sep 17 00:00:00 2001 From: "Mr.Jebelli" Date: Thu, 6 Nov 2025 14:36:46 +0330 Subject: [PATCH] redesign explore --- lib/assets/icons/Nabz_Sanat.svg | 7 + lib/assets/icons/Pouyesh Ofogh.svg | 3 + lib/assets/icons/global.svg | 7 + lib/assets/icons/heart2.svg | 3 + lib/assets/icons/hugeicons_telescope-01.svg | 6 + .../icons/ion_extension-puzzle-outline.svg | 3 + lib/assets/icons/location.svg | 4 + lib/services/ai_rag_service.dart | 58 ++ lib/services/ai_voice_service.dart | 52 + lib/views/ai/ai_chat_page.dart | 30 +- lib/views/ai_section/ai_section_page.dart | 3 +- lib/views/home/explore/explore.dart | 944 +++++++++++++++++- lib/views/home/main/main_page.dart | 565 ++++++----- .../home/main/widgets/infography_item.dart | 4 +- .../home/main/widgets/swot_item_card.dart | 226 +++-- .../home/media/widgets/podcast_list_card.dart | 95 +- lib/views/news/news.dart | 139 ++- lib/views/news/news_state.dart | 11 + .../studio_details/studio_details.web.dart | 14 +- lib/views/widgets/ai_chat_dialog.dart | 927 +++++++++++++++++ lib/views/widgets/bookmark_button.dart | 17 +- lib/views/widgets/didvan/bnb.dart | 171 ++-- lib/views/widgets/didvan/card.dart | 2 +- lib/views/widgets/home_app_bar.dart | 58 +- lib/views/widgets/liked_button.dart | 10 +- lib/views/widgets/overview/news.dart | 64 +- lib/views/widgets/text_divider.dart | 18 +- .../.plugin_symlinks/app_links_linux | 1 - .../.plugin_symlinks/device_info_plus | 1 - .../ephemeral/.plugin_symlinks/file_picker | 1 - .../.plugin_symlinks/file_selector_linux | 1 - .../flutter_local_notifications_linux | 1 - .../flutter_secure_storage_linux | 1 - linux/flutter/ephemeral/.plugin_symlinks/gtk | 1 - .../.plugin_symlinks/image_picker_linux | 1 - .../.plugin_symlinks/package_info_plus | 1 - .../.plugin_symlinks/path_provider_linux | 1 - .../ephemeral/.plugin_symlinks/record_linux | 1 - .../ephemeral/.plugin_symlinks/sentry_flutter | 1 - .../.plugin_symlinks/url_launcher_linux | 1 - .../ephemeral/.plugin_symlinks/wakelock_plus | 1 - .../ephemeral/Flutter-Generated.xcconfig | 4 +- .../ephemeral/flutter_export_environment.sh | 4 +- pubspec.lock | 34 +- pubspec.yaml | 7 +- 45 files changed, 2801 insertions(+), 703 deletions(-) create mode 100644 lib/assets/icons/Nabz_Sanat.svg create mode 100644 lib/assets/icons/Pouyesh Ofogh.svg create mode 100644 lib/assets/icons/global.svg create mode 100644 lib/assets/icons/heart2.svg create mode 100644 lib/assets/icons/hugeicons_telescope-01.svg create mode 100644 lib/assets/icons/ion_extension-puzzle-outline.svg create mode 100644 lib/assets/icons/location.svg create mode 100644 lib/services/ai_rag_service.dart create mode 100644 lib/services/ai_voice_service.dart create mode 100644 lib/views/widgets/ai_chat_dialog.dart delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/app_links_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/device_info_plus delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/file_picker delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/flutter_local_notifications_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/gtk delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/package_info_plus delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/record_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux delete mode 120000 linux/flutter/ephemeral/.plugin_symlinks/wakelock_plus diff --git a/lib/assets/icons/Nabz_Sanat.svg b/lib/assets/icons/Nabz_Sanat.svg new file mode 100644 index 0000000..b8fdbb2 --- /dev/null +++ b/lib/assets/icons/Nabz_Sanat.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/assets/icons/Pouyesh Ofogh.svg b/lib/assets/icons/Pouyesh Ofogh.svg new file mode 100644 index 0000000..fac25e6 --- /dev/null +++ b/lib/assets/icons/Pouyesh Ofogh.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/assets/icons/global.svg b/lib/assets/icons/global.svg new file mode 100644 index 0000000..6b24acb --- /dev/null +++ b/lib/assets/icons/global.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/assets/icons/heart2.svg b/lib/assets/icons/heart2.svg new file mode 100644 index 0000000..1912ecb --- /dev/null +++ b/lib/assets/icons/heart2.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/assets/icons/hugeicons_telescope-01.svg b/lib/assets/icons/hugeicons_telescope-01.svg new file mode 100644 index 0000000..9e12a32 --- /dev/null +++ b/lib/assets/icons/hugeicons_telescope-01.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/assets/icons/ion_extension-puzzle-outline.svg b/lib/assets/icons/ion_extension-puzzle-outline.svg new file mode 100644 index 0000000..cd3ed40 --- /dev/null +++ b/lib/assets/icons/ion_extension-puzzle-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/assets/icons/location.svg b/lib/assets/icons/location.svg new file mode 100644 index 0000000..8a20ec3 --- /dev/null +++ b/lib/assets/icons/location.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/services/ai_rag_service.dart b/lib/services/ai_rag_service.dart new file mode 100644 index 0000000..9124455 --- /dev/null +++ b/lib/services/ai_rag_service.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class AiRagService { + static const String _baseUrl = + 'https://n8n-didvan.liara.run/webhook/didvan-rag'; + static const String _authHeader = + 'Basic dXNlcjpkYXNqZHBuM3BjdTQzcDM0aWpyaA=='; + + static Future sendMessage(String message) async { + try { + final response = await http.post( + Uri.parse(_baseUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': _authHeader, + 'Accept': '*/*', + }, + body: jsonEncode({ + 'chatInput': message, + }), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return AiRagResponse( + output: data['output'] ?? '', + sources: List.from(data['sources'] ?? []), + isSuccess: true, + ); + } else { + return AiRagResponse( + output: 'خطا در دریافت پاسخ از سرور', + sources: [], + isSuccess: false, + ); + } + } catch (e) { + return AiRagResponse( + output: 'خطا در ارتباط با سرور: ${e.toString()}', + sources: [], + isSuccess: false, + ); + } + } +} + +class AiRagResponse { + final String output; + final List sources; + final bool isSuccess; + + AiRagResponse({ + required this.output, + required this.sources, + required this.isSuccess, + }); +} diff --git a/lib/services/ai_voice_service.dart b/lib/services/ai_voice_service.dart new file mode 100644 index 0000000..4ee799d --- /dev/null +++ b/lib/services/ai_voice_service.dart @@ -0,0 +1,52 @@ +import 'dart:io'; +import 'package:http/http.dart' as http; + +class AiVoiceService { + static const String _uploadUrl = 'https://n8n-didvan.liara.run/webhook/upload-mp3'; + static const String _authHeader = 'Basic dXNlcjpkYXNqZHBuM3BjdTQzcDM0aWpyaA=='; + + static Future uploadVoice(String filePath) async { + try { + final file = File(filePath); + final bytes = await file.readAsBytes(); + + final response = await http.post( + Uri.parse(_uploadUrl), + headers: { + 'Content-Type': 'application/octet-stream', + 'Authorization': _authHeader, + 'Accept': '*/*', + }, + body: bytes, + ); + + if (response.statusCode == 200) { + // فرض می‌کنیم سرور متن رو برمی‌گردونه + return VoiceUploadResponse( + text: response.body, + isSuccess: true, + ); + } else { + return VoiceUploadResponse( + text: 'خطا در آپلود فایل صوتی', + isSuccess: false, + ); + } + } catch (e) { + return VoiceUploadResponse( + text: 'خطا در ارتباط با سرور: ${e.toString()}', + isSuccess: false, + ); + } + } +} + +class VoiceUploadResponse { + final String text; + final bool isSuccess; + + VoiceUploadResponse({ + required this.text, + required this.isSuccess, + }); +} diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index 6fcdc65..0d98c9a 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -867,28 +867,14 @@ class _AiChatPageState extends State with TickerProviderStateMixin { ? Padding( padding: const EdgeInsets.fromLTRB( 16, 16, 16, 0), - child: Column( - children: [ - Container( - height: 50, - color: Colors.green, - child: Center( - child: Text( - 'VIDEO PLAYER TEST', - style: TextStyle(color: Colors.white), - ), - ), - ), - ClipRRect( - borderRadius: - DesignConfig.lowBorderRadius, - child: ChatVideoPlayer( - src: RequestHelper.baseUrl + - file.path, - custome: const CustomControls(), - ), - ), - ], + child: ClipRRect( + borderRadius: + DesignConfig.lowBorderRadius, + child: ChatVideoPlayer( + src: RequestHelper.baseUrl + + file.path, + custome: const CustomControls(), + ), ), ) : file.isImage() diff --git a/lib/views/ai_section/ai_section_page.dart b/lib/views/ai_section/ai_section_page.dart index fdac253..efa9fea 100644 --- a/lib/views/ai_section/ai_section_page.dart +++ b/lib/views/ai_section/ai_section_page.dart @@ -251,7 +251,7 @@ class _AiSectionPageState extends State with TickerProviderStateM child: FadeTransition( opacity: _fadeAnimation, child: SizedBox( - height: MediaQuery.of(context).size.height - 100, + height: 500, child: _buildAiGrid(context, aiState), ), ), @@ -391,6 +391,7 @@ class _AnimatedGridCardState extends State<_AnimatedGridCard> { ), boxShadow: [ BoxShadow( + // ignore: deprecated_member_use color: Colors.black.withOpacity(_isHovered ? 0.15 : 0.05), blurRadius: _isHovered ? 20 : 10, offset: Offset(0, _isHovered ? 8 : 4), diff --git a/lib/views/home/explore/explore.dart b/lib/views/home/explore/explore.dart index 7fd6239..681e587 100644 --- a/lib/views/home/explore/explore.dart +++ b/lib/views/home/explore/explore.dart @@ -5,7 +5,6 @@ import 'package:didvan/models/home_page_content/home_page_list.dart'; import 'package:didvan/models/home_page_content/swot.dart'; import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/views/home/main/main_page_state.dart'; -import 'package:didvan/views/home/main/widgets/general_item.dart'; import 'package:didvan/views/home/main/widgets/podcast_item.dart'; import 'package:didvan/views/widgets/didvan/slider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -20,6 +19,10 @@ import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform; import 'package:universal_html/html.dart' as html; import 'package:didvan/views/widgets/home_app_bar.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:persian_number_utility/persian_number_utility.dart'; +import 'package:didvan/views/widgets/text_divider.dart'; + bool isAnyMobile() { if (kIsWeb) { final userAgent = html.window.navigator.userAgent.toLowerCase(); @@ -45,12 +48,47 @@ class ExplorePage extends StatelessWidget { builder: (context, state) { final List pageContent = []; - pageContent.add(const HomeAppBar( - showSearchField: true, + pageContent.add(const StaggeredEntry( + index: 0, + child: HomeAppBar( + showSearchField: true, + ), )); if (state.content != null && state.content!.lists.isNotEmpty) { - final lists = state.content!.lists; + final List lists = List.from(state.content!.lists); + + MainPageList? trendItem, riskItem, startupItem, techItem; + try { + trendItem = lists.firstWhere((e) => e.type == 'trend'); + } catch (_) {} + try { + riskItem = lists.firstWhere((e) => e.type == 'risk'); + } catch (_) {} + try { + startupItem = lists.firstWhere((e) => e.type == 'startup'); + } catch (_) {} + try { + techItem = lists.firstWhere((e) => e.type == 'technology'); + } catch (_) {} + + if (trendItem != null) { + lists.removeWhere((e) => + e.type == 'risk' || + e.type == 'startup' || + e.type == 'technology'); + + final newTrendIndex = lists.indexOf(trendItem); + + final itemsToInsert = []; + if (riskItem != null) itemsToInsert.add(riskItem); + if (startupItem != null) itemsToInsert.add(startupItem); + if (techItem != null) itemsToInsert.add(techItem); + + if (newTrendIndex != -1) { + lists.insertAll(newTrendIndex + 1, itemsToInsert); + } + } for (int i = 0; i < lists.length; i++) { final currentList = lists[i]; @@ -60,17 +98,67 @@ class ExplorePage extends StatelessWidget { continue; } - pageContent.add(MainPageSection( - list: currentList, - isLast: i == lists.length - 1, + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: MainPageSection( + list: currentList, + isLast: i == lists.length - 1, + ), )); - if (currentList.type == 'startup') { - pageContent.add(SwotSection(swotItems: state.swotItems)); + if (currentList.type == 'radar') { + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: const Column( + children: [ + SizedBox( + height: 10, + ), + TextDivider(text: 'رادارهای استراتژیک'), + ], + ), + )); + } + + if (currentList.type == 'technology') { + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: SwotSection(swotItems: state.swotItems), + )); + + if (lists.any((e) => e.type == 'survey')) { + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: const Column( + children: [ + SizedBox(height: 10), + TextDivider(text: 'سامانه هم‌اندیشی آنلاین'), + ], + ), + )); + } + } else if (currentList.type == 'startup' && techItem == null) { + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: SwotSection(swotItems: state.swotItems), + )); + + if (lists.any((e) => e.type == 'survey')) { + pageContent.add(StaggeredEntry( + index: pageContent.length, + child: const Column( + children: [ + SizedBox(height: 10), + TextDivider(text: 'سامانه هم‌اندیشی آنلاین'), + ], + ), + )); + } } } } return ListView( + padding: const EdgeInsets.only(bottom: 30), children: pageContent, ); }, @@ -80,6 +168,77 @@ class ExplorePage extends StatelessWidget { } } +class StaggeredEntry extends StatefulWidget { + final Widget child; + final int index; + final Duration duration; + final double verticalOffset; + + const StaggeredEntry({ + super.key, + required this.child, + required this.index, + this.duration = const Duration(milliseconds: 500), + this.verticalOffset = 50.0, + }); + + @override + State createState() => _StaggeredEntryState(); +} + +class _StaggeredEntryState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: widget.duration, + ); + + _fadeAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + ); + + _slideAnimation = Tween( + begin: Offset(0, widget.verticalOffset / 100), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + )); + + final delay = (widget.index * 100).clamp(0, 1000); + Future.delayed(Duration(milliseconds: delay), () { + if (mounted) { + _controller.forward(); + } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: widget.child, + ), + ); + } +} + class SwotSection extends StatelessWidget { final List swotItems; const SwotSection({super.key, required this.swotItems}); @@ -109,9 +268,9 @@ class SwotSection extends StatelessWidget { ), const SizedBox(width: 5), DidvanText( - "ماژول فرصت و تهدید", - style: Theme.of(context).textTheme.titleMedium, - color: Theme.of(context).colorScheme.title, + "باید ها و نباید ها", + style: Theme.of(context).textTheme.titleSmall, + color: const Color.fromARGB(255, 0, 89, 119), ), ], ), @@ -124,7 +283,7 @@ class SwotSection extends StatelessWidget { ); }, child: Padding( - padding: EdgeInsets.only(left: 20), + padding: const EdgeInsets.only(left: 20), child: Row( children: [ DidvanText( @@ -144,17 +303,18 @@ class SwotSection extends StatelessWidget { /// Swot Items Slider DidvanSlider( - height: 330, - itemCount: 7, // TODO: This should probably be swotItems.length - viewportFraction: isAnyMobile() ? 0.65 : 0.55, + height: 200, + itemCount: swotItems.length, + viewportFraction: isAnyMobile() ? 0.80 : 0.75, itemBuilder: (context, index, realIndex) => Padding( padding: const EdgeInsets.symmetric(horizontal: 0.0), child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(5.0), child: SwotItemCard(item: swotItems[index]), ), ), ), + const SizedBox(height: 16), ], ), ); @@ -165,7 +325,7 @@ class MainPageSection extends StatelessWidget { final MainPageList list; final bool isLast; - const MainPageSection({required this.list, required this.isLast}); + const MainPageSection({super.key, required this.list, required this.isLast}); void _moreHandler(BuildContext context) { if (list.link.startsWith('http')) { @@ -197,21 +357,65 @@ class MainPageSection extends StatelessWidget { return DidvanIcons.exclamation_triangle_solid; case 'startup': return DidvanIcons.startup_solid; - case 'delphi': + case 'survey': return DidvanIcons.saha_solid; default: return null; } } - int _maxSublistCount() { - int max = 1; - for (var i = 0; i < list.contents.length; i++) { - if (list.contents[i].subtitles.length > max) { - max = list.contents[i].subtitles.length; - } + double _calculateSliderHeight() { + switch (list.type) { + case 'news': + return 225; + case 'radar': + return 225; + case 'trend': + return 150; + case 'technology': + return 230; + case 'risk': + return 225; + case 'startup': + return 145; + case 'delphi': + return 220; + default: + return 220; } - return max - 1; + } + + double _calculateFixedViewportFraction(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + double targetWidth; + + switch (list.type) { + case 'news': + targetWidth = 265.0; + break; + case 'radar': + targetWidth = 310.0; + break; + case 'trend': + targetWidth = 350.0; + break; + case 'technology': + targetWidth = 250.0; + break; + case 'risk': + targetWidth = 310.0; + break; + case 'startup': + targetWidth = 360.0; + break; + case 'delphi': + targetWidth = 250.0; + break; + default: + targetWidth = 250.0; + } + + return (targetWidth / screenWidth).clamp(0.1, 1.0); } @override @@ -222,15 +426,6 @@ class MainPageSection extends StatelessWidget { return const SizedBox(); } - if (list.type == 'delphi') { - return Column( - children: [ - _buildSectionHeader(context, icon), - _buildSectionSlider(context), - ], - ); - } - return Column( children: [ _buildSectionHeader(context, icon), @@ -240,6 +435,17 @@ class MainPageSection extends StatelessWidget { } Padding _buildSectionHeader(BuildContext context, IconData? icon) { + String headerText = list.header; + if (list.type == 'news') { + headerText = 'دنیای فولاد'; + } + if (list.type == 'radar') { + headerText = 'پویش افق'; + } + if (list.type == 'survey') { + headerText = 'سها'; + } + return Padding( padding: const EdgeInsets.only( left: 16, @@ -259,9 +465,10 @@ class MainPageSection extends StatelessWidget { ), const SizedBox(width: 4), DidvanText( - list.header, - style: Theme.of(context).textTheme.titleMedium, - color: Theme.of(context).colorScheme.title, + headerText, + style: + const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + color: const Color.fromARGB(255, 0, 89, 119), ), ], ), @@ -306,16 +513,659 @@ class MainPageSection extends StatelessWidget { } return DidvanSlider( - height: 260 + (_maxSublistCount() - (list.type == 'radar' ? 1 : 0)) * 20, + key: ValueKey('${list.type}_slider'), + height: _calculateSliderHeight(), + viewportFraction: _calculateFixedViewportFraction(context), itemCount: list.contents.length, - viewportFraction: isAnyMobile() ? 0.65 : 0.55, - itemBuilder: (context, index, realIndex) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: MainPageGeneralItem( - content: list.contents[index], - type: list.type, - ), - ), + itemBuilder: (context, index, realIndex) { + Widget cardContent; + final item = list.contents[index]; + + switch (list.type) { + case 'news': + cardContent = GestureDetector( + key: ValueKey('news_$index'), + onTap: () => context.read().navigationHandler( + list.type, + item.id, + item.link, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SkeletonImage( + imageUrl: item.image, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0)), + height: 120, + ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 20, 12, 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox( + height: 15, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + 'lib/assets/icons/calendar.svg'), + const SizedBox( + width: 5, + ), + Text( + DateTime.parse(item.subtitles.first) + .toPersianDateStr(), + style: const TextStyle( + color: Color.fromARGB(255, 102, 102, 102), + fontSize: 12), + ), + ], + ) + ], + ), + ), + ], + ), + ), + ), + ); + break; + case 'radar': + cardContent = GestureDetector( + key: ValueKey('radar_$index'), + onTap: () => context.read().navigationHandler( + list.type, + item.id, + item.link, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184))), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(9.5), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset( + 'lib/assets/icons/Pouyesh Ofogh.svg'), + const SizedBox( + width: 5, + ), + Expanded( + child: Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + 'lib/assets/icons/calendar.svg'), + const SizedBox( + width: 5, + ), + Text( + DateTime.parse(item.subtitles.first) + .toPersianDateStr(), + style: const TextStyle( + color: Color.fromARGB(255, 102, 102, 102), + fontSize: 12), + ), + ], + ), + ], + ), + ), + const SizedBox( + height: 5, + ), + SkeletonImage( + imageUrl: item.image, + height: 130, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + topRight: Radius.circular(0), + topLeft: Radius.circular(0)), + ), + ], + ), + ), + ), + ); + break; + case 'trend': + cardContent = GestureDetector( + key: ValueKey('trend_$index'), + onTap: () { + if (item.link.startsWith('http')) { + AppInitializer.openWebLink( + context, + '${item.link}?accessToken=${RequestService.token}', + mode: LaunchMode.inAppWebView, + ); + } else { + context.read().navigationHandler( + list.type, + item.id, + item.link, + ); + } + }, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + SvgPicture.asset( + 'lib/assets/icons/ion_extension-puzzle-outline.svg'), + const SizedBox( + width: 5, + ), + Text( + item.subtitles.last, + style: const TextStyle( + color: + Color.fromARGB(255, 102, 102, 102)), + ), + ], + ) + ], + ), + ), + ), + SizedBox( + width: 150, + child: SkeletonImage( + imageUrl: item.image, + height: 10, + width: 150, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(16), + topRight: Radius.circular(0), + bottomRight: Radius.circular(0)), + ), + ), + ], + ), + ), + ); + break; + case 'technology': + cardContent = GestureDetector( + key: ValueKey('technology_$index'), + onTap: () { + if (item.link.startsWith('http')) { + AppInitializer.openWebLink( + context, + '${item.link}?accessToken=${RequestService.token}', + mode: LaunchMode.inAppWebView, + ); + } else { + context.read().navigationHandler( + list.type, + item.id, + item.link, + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SkeletonImage( + imageUrl: item.image, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0)), + height: 110, + ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 20, 12, 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + 'lib/assets/icons/ion_extension-puzzle-outline.svg'), + const SizedBox( + width: 5, + ), + Text(item.subtitles.first) + ], + ) + ], + ), + ), + ], + ), + ), + ), + ); + break; + case 'risk': + cardContent = GestureDetector( + key: ValueKey('risk_$index'), + onTap: () { + if (item.link.startsWith('http')) { + AppInitializer.openWebLink( + context, + '${item.link}?accessToken=${RequestService.token}', + mode: LaunchMode.inAppWebView, + ); + } else { + context.read().navigationHandler( + list.type, + item.id, + item.link, + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184))), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(9.5), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox( + height: 13, + ), + const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: 19, + ) + ], + ), + ], + ), + ), + const SizedBox( + height: 5, + ), + SkeletonImage( + imageUrl: item.image, + height: 130, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + topRight: Radius.circular(0), + topLeft: Radius.circular(0)), + ), + ], + ), + ), + )); + break; + + case 'startup': + cardContent = GestureDetector( + key: ValueKey('startup_$index'), + onTap: () { + if (item.link.startsWith('http')) { + AppInitializer.openWebLink( + context, + '${item.link}?accessToken=${RequestService.token}', + mode: LaunchMode.inAppWebView, + ); + } else { + context.read().navigationHandler( + list.type, + item.id, + item.link, + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + width: 150, + child: SkeletonImage( + imageUrl: item.image, + height: 10, + width: 150, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(0), + bottomLeft: Radius.circular(0), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16)), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + const SizedBox( + height: 10, + ), + Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset( + 'lib/assets/icons/location.svg'), + const SizedBox( + width: 5, + ), + Text( + item.subtitles.first, + style: const TextStyle( + color: Color.fromARGB( + 255, 102, 102, 102)), + ), + ], + ), + const SizedBox( + height: 10, + ), + Container( + decoration: const BoxDecoration( + color: Color.fromARGB(255, 235, 235, 235), + borderRadius: + BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + item.subtitles.last, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color.fromARGB( + 255, 102, 102, 102), + fontSize: 12), + ), + ), + ) + ], + ) + ], + ), + ), + ), + ], + ), + ), + ), + ); + break; + case 'delphi': + cardContent = GestureDetector( + key: ValueKey('delphi_$index'), + onTap: () { + if (item.link.startsWith('http')) { + AppInitializer.openWebLink( + context, + '${item.link}?accessToken=${RequestService.token}', + mode: LaunchMode.inAppWebView, + ); + } else { + context.read().navigationHandler( + list.type, + item.id, + item.link, + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SkeletonImage( + imageUrl: item.image, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0)), + height: 135, + ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 15, 12, 15), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + ), + ); + break; + default: + cardContent = GestureDetector( + key: ValueKey('survey_$index'), + onTap: () => context.read().navigationHandler( + list.type, + item.id, + item.link, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 0), + decoration: BoxDecoration( + color: const Color.fromARGB(255, 27, 60, 89), + borderRadius: BorderRadius.circular(12), + ), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SkeletonImage( + imageUrl: item.image, + height: 165, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0)), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + item.title, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ); + } + return cardContent; + }, ); } } diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart index 8cffada..f025ec5 100644 --- a/lib/views/home/main/main_page.dart +++ b/lib/views/home/main/main_page.dart @@ -1,22 +1,17 @@ import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/home_page_content/home_page_list.dart'; import 'package:didvan/models/home_page_content/swot.dart'; -import 'package:didvan/models/new_statistic/new_statistics_model.dart'; -import 'package:didvan/providers/user.dart'; -import 'package:didvan/routes/routes.dart'; +import 'package:didvan/views/home/explore/explore.dart'; import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/main/widgets/main_content.dart'; import 'package:didvan/views/home/main/widgets/story_section.dart'; import 'package:didvan/views/home/main/widgets/simple_explore_card.dart'; import 'package:didvan/views/home/new_statistic/new_statistics_state.dart'; -import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/carousel_3d.dart'; import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/widgets/text_divider.dart'; -import 'package:didvan/views/widgets/mini_chart.dart'; import 'package:didvan/views/widgets/home_app_bar.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -79,9 +74,9 @@ class _MainPageState extends State { const TextDivider(text: 'دیده‌بان') .animate() .fadeIn(delay: 400.ms, duration: 500.ms), - const _DidvanSignalsTitle() - .animate() - .fadeIn(delay: 500.ms, duration: 500.ms), + // const _DidvanSignalsTitle() + // .animate() + // .fadeIn(delay: 500.ms, duration: 500.ms), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: StorySection(stories: state.stories), @@ -105,12 +100,16 @@ class _MainPageState extends State { swotItems: state.swotItems, ).animate().fadeIn(delay: 1000.ms, duration: 500.ms), ], - const _IndustryPulseTitle() - .animate() - .fadeIn(delay: 1100.ms, duration: 500.ms), - const _IndustryPulseCards() - .animate() - .fadeIn(delay: 1200.ms, duration: 500.ms), + if (state.swotItems.isNotEmpty) + SwotSection(swotItems: state.swotItems) + .animate() + .fadeIn(delay: 1100.ms, duration: 500.ms), + // const _IndustryPulseTitle() + // .animate() + // .fadeIn(delay: 1100.ms, duration: 500.ms), + // const _IndustryPulseCards() + // .animate() + // .fadeIn(delay: 1200.ms, duration: 500.ms), ], ), ), @@ -121,38 +120,38 @@ class _MainPageState extends State { } } -class _DidvanSignalsTitle extends StatelessWidget { - const _DidvanSignalsTitle(); +// class _DidvanSignalsTitle extends StatelessWidget { +// const _DidvanSignalsTitle(); - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 16, - top: 0, - ), - child: Row( - children: [ - SvgPicture.asset( - 'lib/assets/icons/voice-square.svg', - color: Theme.of(context).colorScheme.title, - width: 30, - height: 30, - ), - const SizedBox(width: 5), - DidvanText( - "سیگنال‌های دیدوان", - style: Theme.of(context).textTheme.titleMedium, - color: const Color.fromARGB(255, 0, 89, 119), - fontSize: 13, - ), - ], - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Padding( +// padding: const EdgeInsets.only( +// left: 16, +// right: 16, +// bottom: 16, +// top: 0, +// ), +// child: Row( +// children: [ +// SvgPicture.asset( +// 'lib/assets/icons/voice-square.svg', +// color: Theme.of(context).colorScheme.title, +// width: 30, +// height: 30, +// ), +// const SizedBox(width: 5), +// DidvanText( +// "سیگنال‌های دیدوان", +// style: Theme.of(context).textTheme.titleMedium, +// color: const Color.fromARGB(255, 0, 89, 119), +// fontSize: 13, +// ), +// ], +// ), +// ); +// } +// } class _ExploreLatestTitle extends StatelessWidget { const _ExploreLatestTitle(); @@ -264,257 +263,257 @@ class _ExploreLatestSlider extends StatelessWidget { } } -class _IndustryPulseTitle extends StatelessWidget { - const _IndustryPulseTitle(); +// class _IndustryPulseTitle extends StatelessWidget { +// const _IndustryPulseTitle(); - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 16, - top: 16, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - 'lib/assets/icons/chart 2.svg', - color: Theme.of(context).colorScheme.title, - width: 30, - height: 30, - ), - const SizedBox(width: 5), - DidvanText( - "نبض صنعت", - style: Theme.of(context).textTheme.titleMedium, - color: const Color.fromARGB(255, 0, 89, 119), - fontSize: 13, - ), - ], - ), - GestureDetector( - onTap: () { - context.read().tabController.animateTo(2); - }, - child: const Row( - children: [ - DidvanText( - "مشاهده همه", - color: Color.fromARGB(255, 0, 126, 167), - fontWeight: FontWeight.normal, - fontSize: 12, - ), - ], - ), - ) - ], - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Padding( +// padding: const EdgeInsets.only( +// left: 16, +// right: 16, +// bottom: 16, +// top: 16, +// ), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Row( +// children: [ +// SvgPicture.asset( +// 'lib/assets/icons/chart 2.svg', +// color: Theme.of(context).colorScheme.title, +// width: 30, +// height: 30, +// ), +// const SizedBox(width: 5), +// DidvanText( +// "نبض صنعت", +// style: Theme.of(context).textTheme.titleMedium, +// color: const Color.fromARGB(255, 0, 89, 119), +// fontSize: 13, +// ), +// ], +// ), +// GestureDetector( +// onTap: () { +// context.read().tabController.animateTo(2); +// }, +// child: const Row( +// children: [ +// DidvanText( +// "مشاهده همه", +// color: Color.fromARGB(255, 0, 126, 167), +// fontWeight: FontWeight.normal, +// fontSize: 12, +// ), +// ], +// ), +// ) +// ], +// ), +// ); +// } +// } -class _IndustryPulseCards extends StatefulWidget { - const _IndustryPulseCards(); +// class _IndustryPulseCards extends StatefulWidget { +// const _IndustryPulseCards(); - @override - State<_IndustryPulseCards> createState() => _IndustryPulseCardsState(); -} +// @override +// State<_IndustryPulseCards> createState() => _IndustryPulseCardsState(); +// } -class _IndustryPulseCardsState extends State<_IndustryPulseCards> { - late PageController _pageController; +// class _IndustryPulseCardsState extends State<_IndustryPulseCards> { +// late PageController _pageController; - @override - void initState() { - super.initState(); - _pageController = PageController(viewportFraction: 0.45); - } +// @override +// void initState() { +// super.initState(); +// _pageController = PageController(viewportFraction: 0.45); +// } - @override - void dispose() { - _pageController.dispose(); - super.dispose(); - } +// @override +// void dispose() { +// _pageController.dispose(); +// super.dispose(); +// } - @override - Widget build(BuildContext context) { - return StateHandler( - state: context.watch(), - placeholder: const Center(child: CircularProgressIndicator()), - onRetry: () => context.read().init(), - builder: (context, statisticState) { - if (statisticState.contents.isEmpty) { - return const SizedBox.shrink(); - } +// @override +// Widget build(BuildContext context) { +// return StateHandler( +// state: context.watch(), +// placeholder: const Center(child: CircularProgressIndicator()), +// onRetry: () => context.read().init(), +// builder: (context, statisticState) { +// if (statisticState.contents.isEmpty) { +// return const SizedBox.shrink(); +// } - final List allItems = []; - statisticState.contents.forEach((category) { - allItems.addAll(category.contents); - }); +// final List allItems = []; +// statisticState.contents.forEach((category) { +// allItems.addAll(category.contents); +// }); - final List desiredTitles = [ - 'دلار', - 'بیت کوین', - 'نیکل', - 'نفت خام' - ]; - final List itemsToShow = allItems - .where((item) => desiredTitles.contains(item.title)) - .toList(); +// final List desiredTitles = [ +// 'دلار', +// 'بیت کوین', +// 'نیکل', +// 'نفت خام' +// ]; +// final List itemsToShow = allItems +// .where((item) => desiredTitles.contains(item.title)) +// .toList(); - if (itemsToShow.isEmpty) { - return const SizedBox.shrink(); - } +// if (itemsToShow.isEmpty) { +// return const SizedBox.shrink(); +// } - return Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Align( - alignment: Alignment.centerLeft, - child: SizedBox( - height: 165, - child: PageView.builder( - padEnds: false, - controller: _pageController, - itemCount: itemsToShow.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: AspectRatio( - aspectRatio: 1, - child: _IndustryPulseCard(statistic: itemsToShow[index]), - ), - ) - .animate() - .fadeIn(delay: (200 * index).ms, duration: 500.ms) - .slideX( - begin: -0.5, duration: 500.ms, curve: Curves.easeOut); - }, - ), - ), - ), - ); - }, - ); - } -} +// return Padding( +// padding: const EdgeInsets.only(left: 16.0), +// child: Align( +// alignment: Alignment.centerLeft, +// child: SizedBox( +// height: 165, +// child: PageView.builder( +// padEnds: false, +// controller: _pageController, +// itemCount: itemsToShow.length, +// itemBuilder: (context, index) { +// return Padding( +// padding: const EdgeInsets.only(right: 8.0), +// child: AspectRatio( +// aspectRatio: 1, +// child: _IndustryPulseCard(statistic: itemsToShow[index]), +// ), +// ) +// .animate() +// .fadeIn(delay: (200 * index).ms, duration: 500.ms) +// .slideX( +// begin: -0.5, duration: 500.ms, curve: Curves.easeOut); +// }, +// ), +// ), +// ), +// ); +// }, +// ); +// } +// } -class _IndustryPulseCard extends StatelessWidget { - final Content statistic; +// class _IndustryPulseCard extends StatelessWidget { +// final Content statistic; - const _IndustryPulseCard({required this.statistic}); +// const _IndustryPulseCard({required this.statistic}); - Color _diffColor(BuildContext context) => statistic.data.dt == 'high' - ? Theme.of(context).colorScheme.success - : Theme.of(context).colorScheme.error; +// Color _diffColor(BuildContext context) => statistic.data.dt == 'high' +// ? Theme.of(context).colorScheme.success +// : Theme.of(context).colorScheme.error; - bool get _hasDiff => statistic.data.dp != 0; +// bool get _hasDiff => statistic.data.dp != 0; - @override - Widget build(BuildContext context) { - final state = context.read(); - return GestureDetector( - onTap: () => - Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: { - 'onMarkChanged': (value) => onMarkChanged(statistic.id, value), - 'label': statistic.label, - 'title': statistic.title, - 'marked': statistic.marked, - }).then( - (value) => state.getStatistic(), - ), - child: DidvanCard( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Row( - children: [ - Expanded( - child: MiniChart( - label: statistic.label, - width: double.infinity, - height: 38, - lineColor: _hasDiff - ? _diffColor(context) - : Theme.of(context).colorScheme.primary, - changePercent: statistic.data.dp.toDouble(), - trend: statistic.data.dt, - ), - ), - ], - ), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (statistic.marked) - Icon( - Icons.star, - color: Theme.of(context).colorScheme.yellow, - size: 16, - ), - if (statistic.marked) const SizedBox(width: 4), - DidvanText( - statistic.title, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - const SizedBox(height: 6), - DidvanText( - statistic.data.p, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.w500, - ), - ), - if (_hasDiff) const SizedBox(height: 6), - if (_hasDiff) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: statistic.data.dt == 'high' - ? const Color.fromRGBO(245, 255, 252, 1) - : const Color.fromRGBO(255, 248, 248, 1), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - statistic.data.dt == 'high' - ? DidvanIcons.angle_up_regular - : DidvanIcons.angle_down_regular, - size: 16, - color: _diffColor(context), - ), - const SizedBox(width: 4), - DidvanText( - '${statistic.data.dp}%', - style: Theme.of(context).textTheme.bodySmall, - color: _diffColor(context), - ), - ], - ), - ), - ], - ), - ), - ); - } +// @override +// Widget build(BuildContext context) { +// final state = context.read(); +// return GestureDetector( +// onTap: () => +// Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: { +// 'onMarkChanged': (value) => onMarkChanged(statistic.id, value), +// 'label': statistic.label, +// 'title': statistic.title, +// 'marked': statistic.marked, +// }).then( +// (value) => state.getStatistic(), +// ), +// child: DidvanCard( +// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9), +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Padding( +// padding: const EdgeInsets.symmetric(horizontal: 8.0), +// child: Row( +// children: [ +// Expanded( +// child: MiniChart( +// label: statistic.label, +// width: double.infinity, +// height: 38, +// lineColor: _hasDiff +// ? _diffColor(context) +// : Theme.of(context).colorScheme.primary, +// changePercent: statistic.data.dp.toDouble(), +// trend: statistic.data.dt, +// ), +// ), +// ], +// ), +// ), +// const SizedBox(height: 20), +// Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// if (statistic.marked) +// Icon( +// Icons.star, +// color: Theme.of(context).colorScheme.yellow, +// size: 16, +// ), +// if (statistic.marked) const SizedBox(width: 4), +// DidvanText( +// statistic.title, +// style: Theme.of(context).textTheme.bodyMedium?.copyWith( +// fontWeight: FontWeight.w600, +// ), +// maxLines: 1, +// overflow: TextOverflow.ellipsis, +// ), +// ], +// ), +// const SizedBox(height: 6), +// DidvanText( +// statistic.data.p, +// style: Theme.of(context).textTheme.bodySmall?.copyWith( +// fontWeight: FontWeight.w500, +// ), +// ), +// if (_hasDiff) const SizedBox(height: 6), +// if (_hasDiff) +// Container( +// padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), +// decoration: BoxDecoration( +// color: statistic.data.dt == 'high' +// ? const Color.fromRGBO(245, 255, 252, 1) +// : const Color.fromRGBO(255, 248, 248, 1), +// borderRadius: BorderRadius.circular(4), +// ), +// child: Row( +// mainAxisSize: MainAxisSize.min, +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// Icon( +// statistic.data.dt == 'high' +// ? DidvanIcons.angle_up_regular +// : DidvanIcons.angle_down_regular, +// size: 16, +// color: _diffColor(context), +// ), +// const SizedBox(width: 4), +// DidvanText( +// '${statistic.data.dp}%', +// style: Theme.of(context).textTheme.bodySmall, +// color: _diffColor(context), +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ); +// } - void onMarkChanged(int id, bool value) { - UserProvider.changeStatisticMark(id, value); - } -} \ No newline at end of file +// void onMarkChanged(int id, bool value) { +// UserProvider.changeStatisticMark(id, value); +// } +// } diff --git a/lib/views/home/main/widgets/infography_item.dart b/lib/views/home/main/widgets/infography_item.dart index bb6ef96..d836fdc 100644 --- a/lib/views/home/main/widgets/infography_item.dart +++ b/lib/views/home/main/widgets/infography_item.dart @@ -186,8 +186,10 @@ class InfographyItem extends StatelessWidget { type: 'infography', gestureSize: 32, value: liked, - onMarkChanged: (value) => onLikedChanged(id, value, true), + onMarkChanged: (value) => + onLikedChanged(id, value, true), likes: likes, + unlikedColor: Colors.white, ), ), ], diff --git a/lib/views/home/main/widgets/swot_item_card.dart b/lib/views/home/main/widgets/swot_item_card.dart index 977aa3a..2cada35 100644 --- a/lib/views/home/main/widgets/swot_item_card.dart +++ b/lib/views/home/main/widgets/swot_item_card.dart @@ -1,13 +1,9 @@ -// lib/views/home/main/widgets/swot_item_card.dart - import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; -import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/home_page_content/swot.dart'; import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/network/request.dart'; -import 'package:didvan/views/home/main/widgets/bookmark.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -17,7 +13,8 @@ import 'package:url_launcher/url_launcher_string.dart'; class SwotItemCard extends StatefulWidget { final SwotItem item; - const SwotItemCard({super.key, required this.item, this.onBookmarkChangedInList}); + const SwotItemCard( + {super.key, required this.item, this.onBookmarkChangedInList}); final void Function(int postId, bool isBookmarked)? onBookmarkChangedInList; @override @@ -44,6 +41,7 @@ class _SwotItemCardState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return GestureDetector( onTap: () { AppInitializer.openWebLink( @@ -53,123 +51,135 @@ class _SwotItemCardState extends State { ); }, child: Container( - height: 500 , - width: kIsWeb ? 350 : 250, - margin: const EdgeInsets.only(right: 0), - padding: const EdgeInsets.all(0), + height: 200, + margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 5), decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(9), + color: theme.cardColor, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color.fromARGB(255, 184, 184, 184), + ), ), - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - topRight: Radius.circular(12), - ), - child: CachedNetworkImage( - errorWidget: (context, url, error) { - if (kDebugMode) { - print('image fetch complete with Error: $error'); - } - return Container( - height: 150, - width: kIsWeb ? 350 : 300, - decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.disabledBackground, - ), - child: const Icon(Icons.image_not_supported_outlined), - ); - }, - errorListener: (value) {}, - fit: BoxFit.cover, - imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, - httpHeaders: { - 'Authorization': 'Bearer ${RequestService.token}' - }, - width: kIsWeb ? 350 : 300, - height: 150, - imageUrl: widget.item.imageUrl, - placeholder: (context, _) => ShimmerPlaceholder( - width: kIsWeb ? 350 : 300, - height: 150, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // === بخش تصویر (سمت راست) === + CachedNetworkImage( + imageUrl: widget.item.imageUrl, + width: 135, + height: double.infinity, + fit: BoxFit.cover, + imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, + httpHeaders: { + 'Authorization': 'Bearer ${RequestService.token}' + }, + placeholder: (context, url) => const ShimmerPlaceholder( + width: 135, + height: double.infinity, + ), + errorWidget: (context, url, error) { + if (kDebugMode) { + print('image fetch complete with Error: $error'); + } + return Container( + width: 135, + height: double.infinity, + color: theme.colorScheme.surfaceContainerHighest, + child: Icon( + Icons.image_not_supported_outlined, + color: theme.colorScheme.onSurfaceVariant, ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 0), - child: Text( - widget.item.title, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), + ); + }, + ), + + // === بخش جزئیات (سمت چپ) === + Expanded( + child: Padding( + padding: const EdgeInsets.all(12.0), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ + // عنوان + Text( + widget.item.title, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + fontSize: 15, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + + // بج نوع (تهدید/فرصت) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 3), + decoration: BoxDecoration( + color: (widget.item.type == "THREAT" + ? Colors.red + : Colors.green) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + widget.item.type == "THREAT" + ? "lib/assets/images/features/Badge.svg" + : "lib/assets/images/features/Badge-Green.svg", + height: 9, + ), + const SizedBox(width: 4), + Text( + "عدد ${widget.item.type == "THREAT" + ? "تهدید" + : "فرصت"} : ${(widget.item.x1 * widget.item.y1).toStringAsFixed(1)}", + style: theme.textTheme.bodySmall?.copyWith( + color: widget.item.type == "THREAT" + ? Colors.red + : Colors.green, + fontWeight: FontWeight.normal, + fontSize: 12, + ), + ) + ], + ), + ), + const SizedBox(height: 4), + + // دسته‌بندی Row( children: [ - widget.item.type == "THREAT" - ? SvgPicture.asset( - "lib/assets/images/features/Badge.svg") - : SvgPicture.asset( - "lib/assets/images/features/Badge-Green.svg"), - const SizedBox( - width: 5, + const Icon( + DidvanIcons.puzzle_light, + size: 23, + color: Color.fromARGB(255, 102, 102, 102), ), - Text( - widget.item.type == "THREAT" ? "تهدید" : "فرصت", - ) - ], - ), - const SizedBox( - height: 10, - ), - Row( - children: [ - SvgPicture.asset( - "lib/assets/images/features/ant-design_dot-chart-outlined.svg"), - const SizedBox( - width: 5, - ), - Text( - 'عدد ${widget.item.type == "THREAT" ? "تهدید" : "فرصت"}: ${((widget.item.x1 ?? 0.0) * (widget.item.y1 ?? 0.0)).toStringAsFixed(1)}', - style: Theme.of(context).textTheme.bodyMedium, - ) - ], - ), - const SizedBox(height: 10), - Row( - children: [ - const Icon(DidvanIcons.puzzle_light,size: 17,), - const SizedBox(width: 5,), - Text( - _getCategoryName(widget.item.category), - style: Theme.of(context).textTheme.bodyMedium, + const SizedBox(width: 6), + Expanded( + child: Text( + _getCategoryName(widget.item.category), + style: theme.textTheme.bodySmall?.copyWith( + color: const Color.fromARGB(255, 102, 102, 102), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ], ), - const SizedBox(height: 30), ], ), ), - ], - ), - Positioned( - bottom: 3, - left: 0, - child: BookmarkIcon(postId: widget.item.id), - ) - ], + ), + ], + ), ), ), ); diff --git a/lib/views/home/media/widgets/podcast_list_card.dart b/lib/views/home/media/widgets/podcast_list_card.dart index c076b6d..89a6312 100644 --- a/lib/views/home/media/widgets/podcast_list_card.dart +++ b/lib/views/home/media/widgets/podcast_list_card.dart @@ -47,7 +47,6 @@ class PodcastListCard extends StatelessWidget { right: 16, ), decoration: BoxDecoration( - color: const Color.fromRGBO(235, 255, 255, 255), borderRadius: BorderRadius.circular(20), ), child: ClipRRect( @@ -73,51 +72,59 @@ class PodcastListCard extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(12, 3, 12, 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - DidvanText( - podcast.title, - style: textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - color: Colors.black87, + child: Container( + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + DidvanText( + podcast.title, + style: textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Spacer(), - Row( - children: [ - SvgPicture.asset('lib/assets/icons/calendar.svg'), - const SizedBox(width: 4), - DidvanText( - _formatDate(podcast.createdAt), - style: textTheme.bodySmall?.copyWith( - color: const Color.fromARGB(255, 102, 102, 102), + const Spacer(), + Row( + children: [ + SvgPicture.asset( + 'lib/assets/icons/calendar.svg'), + const SizedBox(width: 4), + DidvanText( + _formatDate(podcast.createdAt), + style: textTheme.bodySmall?.copyWith( + color: const Color.fromARGB( + 255, 102, 102, 102), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - const SizedBox(height: 7), - Row( - children: [ - SvgPicture.asset( - 'lib/assets/icons/clock.svg', - color: const Color.fromARGB(255, 102, 102, 102), - ), - const SizedBox(width: 4), - DidvanText( - _formatDuration(podcast.duration).toPersianDigit(), - style: textTheme.bodySmall?.copyWith( - color: const Color.fromARGB(255, 102, 102, 102), + ], + ), + const SizedBox(height: 7), + Row( + children: [ + SvgPicture.asset( + 'lib/assets/icons/clock.svg', + color: + const Color.fromARGB(255, 102, 102, 102), ), - ), - ], - ), - ], + const SizedBox(width: 4), + DidvanText( + _formatDuration(podcast.duration) + .toPersianDigit(), + style: textTheme.bodySmall?.copyWith( + color: const Color.fromARGB( + 255, 102, 102, 102), + ), + ), + ], + ), + ], + ), ), ), ), @@ -135,4 +142,4 @@ class PodcastListCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/views/news/news.dart b/lib/views/news/news.dart index eb24e9d..3b60462 100644 --- a/lib/views/news/news.dart +++ b/lib/views/news/news.dart @@ -1,20 +1,21 @@ import 'dart:async'; +import 'dart:math'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/models/view/app_bar_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/scaffold.dart'; +import 'package:didvan/views/widgets/home_app_bar.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/overview/news.dart'; -import 'package:didvan/views/widgets/search_field.dart'; import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; class News extends StatefulWidget { @@ -36,31 +37,56 @@ class _NewsState extends State { @override Widget build(BuildContext context) { final state = context.watch(); + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + return DidvanScaffold( padding: EdgeInsets.zero, - appBarData: AppBarData( - title: 'دنیای فولاد', - hasBack: true, - hasElevation: false, - ), + appBarData: null, slivers: [ - if (state.appState != AppState.failed) - SliverAppBar( - backgroundColor: Theme.of(context).colorScheme.surface, - scrolledUnderElevation: 0, - automaticallyImplyLeading: false, - pinned: true, - flexibleSpace: Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), - child: SearchField( - focusNode: _focusNode, - title: 'دنیای فولاد', - onChanged: _onChanged, - onFilterButtonPressed: _showFilterBottomSheet, - isFiltered: state.isFiltering, - ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 12.0), + child: HomeAppBar( + showBackButton: false, + showSearchField: state.appState != AppState.failed, + onSearchChanged: _onChanged, + onFilterPressed: _showFilterBottomSheet, + searchFocusNode: _focusNode, + isFiltered: state.isFiltering, + searchValue: state.search, ), ), + ), + SliverAppBar( + pinned: true, + automaticallyImplyLeading: false, + backgroundColor: colorScheme.surface, + elevation: 0, + centerTitle: false, + titleSpacing: 16, + title: Text( + 'دنیای فولاد', + style: theme.textTheme.headlineSmall?.copyWith( + color: const Color.fromARGB(255, 0, 53, 70), + fontWeight: FontWeight.bold, + fontSize: 19 + ), + ), + actions: [ + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: SvgPicture.asset( + 'lib/assets/icons/arrow-left.svg', + width: 30, + height: 30, + colorFilter: const ColorFilter.mode( + Color.fromARGB(255, 102, 102, 102), BlendMode.srcIn), + ), + ), + const SizedBox(width: 8), + ], + ), const SliverToBoxAdapter( child: SizedBox( height: 16, @@ -70,15 +96,19 @@ class _NewsState extends State { centerEmptyState: false, onRetry: () => state.getNews(page: state.page), state: state, + childCount: min(state.visibleCount, state.news.length) + + (_hasMoreItems(state) ? 1 : 0), builder: (context, state, index) { - index += 2; - if (index % 15 == 0 && state.lastPage != state.page) { - state.getNews(page: state.page + 1); + final currentDisplayCount = min(state.visibleCount, state.news.length); + + if (index == currentDisplayCount && _hasMoreItems(state)) { + return _buildLoadMoreButton(context, state); } - index -= 2; + if (index >= state.news.length) { return NewsOverview.placeholder; } + final news = state.news[index]; return NewsOverview( news: news, @@ -92,12 +122,10 @@ class _NewsState extends State { onLikedChanged: state.onLikedChanged, ); }, - enableEmptyState: state.news.isEmpty, + enableEmptyState: state.news.isEmpty && state.appState != AppState.busy, emptyState: EmptyResult( onNewSearch: () => _focusNode.requestFocus(), ), - childCount: - state.news.length + (state.lastPage == state.page ? 0 : 3), itemPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), placeholder: NewsOverview.placeholder, ), @@ -105,6 +133,59 @@ class _NewsState extends State { ); } + bool _hasMoreItems(NewsState state) { + 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( + padding: EdgeInsets.all(16.0), + child: Center(child: CircularProgressIndicator()), + ); + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + child: Center( + child: GestureDetector( + onTap: () { + state.loadMore(); + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 50.0, + vertical: 12.0, + ), + decoration: BoxDecoration( + color: const Color.fromARGB(255, 0, 126, 167), + borderRadius: BorderRadius.circular(15), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + 'lib/assets/icons/element-plus.svg', + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + width: 20, + ), + const SizedBox(width: 8), + const Text( + 'بارگذاری بیشتر', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ), + ); + } + void _onChanged(String value) { final state = context.read(); if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { diff --git a/lib/views/news/news_state.dart b/lib/views/news/news_state.dart index 7deca54..6e00a1b 100644 --- a/lib/views/news/news_state.dart +++ b/lib/views/news/news_state.dart @@ -12,6 +12,7 @@ class NewsState extends CoreProvier { String? endDate; int page = 1; int lastPage = 0; + int visibleCount = 4; final List news = []; @@ -23,14 +24,24 @@ class NewsState extends CoreProvier { Future.delayed(Duration.zero, () { getNews(page: 1); }); + visibleCount = 4; } void resetFilters() { startDate = null; endDate = null; + visibleCount = 4; getNews(page: 1); } + void loadMore() { + visibleCount += 4; + if (visibleCount >= news.length && page < lastPage) { + getNews(page: page + 1); + } + notifyListeners(); + } + Future getNews({ required int page, }) async { diff --git a/lib/views/podcasts/studio_details/studio_details.web.dart b/lib/views/podcasts/studio_details/studio_details.web.dart index 266ba21..99ebb64 100644 --- a/lib/views/podcasts/studio_details/studio_details.web.dart +++ b/lib/views/podcasts/studio_details/studio_details.web.dart @@ -1,4 +1,4 @@ -// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use, undefined_prefixed_name import 'dart:ui' as ui; @@ -17,7 +17,7 @@ import 'package:universal_html/html.dart' as html; class StudioDetails extends StatefulWidget { final Map pageData; - const StudioDetails({Key? key, required this.pageData}) : super(key: key); + const StudioDetails({super.key, required this.pageData}); @override State createState() => _StudioDetailsState(); @@ -43,11 +43,13 @@ class _StudioDetailsState extends State { state: state, onRetry: () => state.getStudioDetails(state.studio.id), builder: (context, state) { + final String viewType = 'video-iframe-${state.studio.id}'; + if (state.studio.type == 'video') { debugPrint("Playing video from URL: ${state.studio.link}"); - // ignore: undefined_prefixed_name + ui.platformViewRegistry.registerViewFactory( - "video", + viewType, (int viewId) => html.IFrameElement() ..allowFullscreen = true ..src = Uri.dataFromString( @@ -98,7 +100,7 @@ class _StudioDetailsState extends State { AspectRatio( aspectRatio: 16 / 9, child: HtmlElementView( - viewType: 'video', + viewType: viewType, key: ValueKey(state.studio.id), ), ), @@ -121,4 +123,4 @@ class _StudioDetailsState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/views/widgets/ai_chat_dialog.dart b/lib/views/widgets/ai_chat_dialog.dart new file mode 100644 index 0000000..736d16e --- /dev/null +++ b/lib/views/widgets/ai_chat_dialog.dart @@ -0,0 +1,927 @@ +import 'package:didvan/services/ai_rag_service.dart'; +import 'package:didvan/services/ai_voice_service.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/providers/user.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:provider/provider.dart'; +import 'package:record/record.dart'; +import 'package:path_provider/path_provider.dart'; +import 'dart:ui'; +import 'dart:io'; + +class AiChatDialog extends StatefulWidget { + const AiChatDialog({super.key}); + + @override + State createState() => _AiChatDialogState(); +} + +class _AiChatDialogState extends State + with TickerProviderStateMixin { + final TextEditingController _messageController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + final List _messages = []; + bool _isLoading = false; + bool _isRecording = false; + late AnimationController _animationController; + late AnimationController _pulseController; + final AudioRecorder _audioRecorder = AudioRecorder(); + String? _recordingPath; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 400), + ); + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1500), + )..repeat(reverse: true); + + _animationController.forward(); + + Future.delayed(const Duration(milliseconds: 500), () { + if (mounted) { + setState(() { + _messages.add(ChatMessage( + text: + 'سلام! 👋\n\nمن دستیار هوشمند دیدوان هستم. می‌تونم در مورد اخبار، تحلیل‌ها و محتوای دیدوان بهتون کمک کنم.\n\nچه سوالی دارید؟ 😊', + isUser: false, + timestamp: DateTime.now(), + )); + }); + _scrollToBottom(); + } + }); + } + + @override + void dispose() { + _messageController.dispose(); + _scrollController.dispose(); + _animationController.dispose(); + _pulseController.dispose(); + _audioRecorder.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + + Future _sendMessage() async { + final message = _messageController.text.trim(); + if (message.isEmpty) return; + + setState(() { + _messages.add(ChatMessage( + text: message, + isUser: true, + timestamp: DateTime.now(), + )); + _isLoading = true; + _messageController.clear(); + }); + + _scrollToBottom(); + + final response = await AiRagService.sendMessage(message); + + setState(() { + _messages.add(ChatMessage( + text: response.output, + isUser: false, + timestamp: DateTime.now(), + sources: response.sources, + )); + _isLoading = false; + }); + + _scrollToBottom(); + } + + Future _startRecording() async { + try { + if (await _audioRecorder.hasPermission()) { + setState(() { + _isRecording = true; + }); + + final directory = await getTemporaryDirectory(); + final timestamp = DateTime.now().millisecondsSinceEpoch; + _recordingPath = '${directory.path}/voice_$timestamp.m4a'; + + await _audioRecorder.start( + const RecordConfig( + encoder: AudioEncoder.aacLc, + ), + path: _recordingPath!, + ); + + await Future.delayed(const Duration(milliseconds: 200)); + } + } catch (e) { + setState(() { + _isRecording = false; + }); + debugPrint('Error starting recording: $e'); + } + } + + Future _stopRecording() async { + try { + final path = await _audioRecorder.stop(); + + setState(() { + _isRecording = false; + }); + + if (path != null) { + setState(() { + _messages.add(ChatMessage( + text: '🎤 پیام صوتی', + isUser: true, + timestamp: DateTime.now(), + )); + _isLoading = true; + }); + + _scrollToBottom(); + + final response = await AiVoiceService.uploadVoice(path); + + if (response.isSuccess && response.text.isNotEmpty) { + final ragResponse = await AiRagService.sendMessage(response.text); + + setState(() { + _messages.add(ChatMessage( + text: ragResponse.output, + isUser: false, + timestamp: DateTime.now(), + sources: ragResponse.sources, + )); + _isLoading = false; + }); + } else { + setState(() { + _messages.add(ChatMessage( + text: 'خطا در پردازش پیام صوتی', + isUser: false, + timestamp: DateTime.now(), + )); + _isLoading = false; + }); + } + + _scrollToBottom(); + + try { + await File(path).delete(); + } catch (e) { + debugPrint('Error deleting temp file: $e'); + } + } + } catch (e) { + setState(() { + _isRecording = false; + _isLoading = false; + }); + debugPrint('Error stopping recording: $e'); + } + } + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: ScaleTransition( + scale: CurvedAnimation( + parent: _animationController, + curve: Curves.elasticOut, + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + constraints: const BoxConstraints(maxWidth: 500, maxHeight: 600), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.95), + const Color(0xFFF8F9FF).withOpacity(0.95), + ], + ), + borderRadius: BorderRadius.circular(24), + border: Border.all( + color: Colors.white.withOpacity(0.6), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFF0066AA).withOpacity(0.15), + blurRadius: 30, + spreadRadius: 0, + offset: const Offset(0, 15), + ), + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 15, + spreadRadius: -3, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Column( + children: [ + _buildHeader(context), + Expanded( + child: _buildMessageList(), + ), + if (_isLoading) _buildLoadingIndicator(), + if (_isRecording) _buildRecordingIndicator(), + _buildInputField(), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0066AA), + Color(0xFF0088DD), + Color(0xFF00AAFF), + ], + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFF0066AA).withOpacity(0.2), + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: Row( + children: [ + Stack( + children: [ + AnimatedBuilder( + animation: _pulseController, + builder: (context, child) { + return Container( + width: 44, + height: 44, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.white + .withOpacity(0.25 * _pulseController.value), + blurRadius: 15 + (8 * _pulseController.value), + spreadRadius: 1 + (2 * _pulseController.value), + ), + ], + ), + ); + }, + ), + // Avatar + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + padding: const EdgeInsets.all(10), + child: SvgPicture.asset( + 'lib/assets/icons/live ai.svg', + colorFilter: const ColorFilter.mode( + Color(0xFF0066AA), + BlendMode.srcIn, + ), + ), + ), + // Online indicator + Positioned( + bottom: 1, + left: 1, + child: Container( + width: 11, + height: 11, + decoration: BoxDecoration( + color: const Color(0xFF00FF88), + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.5), + boxShadow: [ + BoxShadow( + color: const Color(0xFF00FF88).withOpacity(0.4), + blurRadius: 6, + spreadRadius: 0.5, + ), + ], + ), + ), + ), + ], + ), + const SizedBox(width: 12), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + 'دستیار هوشمند دیدوان', + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + SizedBox(height: 3), + Row( + children: [ + SizedBox(width: 4), + DidvanText( + 'آنلاین', + fontSize: 11, + color: Colors.white70, + ), + ], + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: IconButton( + icon: const Icon(Icons.close_rounded, + color: Colors.white, size: 20), + onPressed: () => Navigator.pop(context), + splashRadius: 20, + ), + ), + ], + ), + ); + } + + Widget _buildMessageList() { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.5), + const Color(0xFFF8F9FF).withOpacity(0.3), + ], + ), + ), + child: ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.fromLTRB(14, 14, 14, 10), + itemCount: _messages.length, + itemBuilder: (context, index) { + final message = _messages[index]; + return _buildMessageBubble(message); + }, + ), + ); + } + + Widget _buildMessageBubble(ChatMessage message) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: const Duration(milliseconds: 400), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 20 * (1 - value)), + child: Opacity( + opacity: value, + child: child, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: + message.isUser ? MainAxisAlignment.start : MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (message.isUser) _buildUserAvatar(), + if (message.isUser) const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: message.isUser + ? CrossAxisAlignment.start + : CrossAxisAlignment.end, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + decoration: BoxDecoration( + gradient: !message.isUser + ? const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0066AA), + Color(0xFF0088DD), + ], + ) + : null, + color: !message.isUser ? null : const Color(0xFFF5F7FA), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(!message.isUser ? 4 : 16), + topRight: const Radius.circular(16), + bottomLeft: const Radius.circular(16), + bottomRight: Radius.circular(!message.isUser ? 16 : 4), + ), + border: !message.isUser + ? null + : Border.all( + color: const Color(0xFFE0E5EC).withOpacity(0.5), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: !message.isUser + ? const Color(0xFF0066AA).withOpacity(0.2) + : Colors.black.withOpacity(0.03), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: DidvanText( + message.text, + color: !message.isUser + ? Colors.white + : const Color(0xFF1A1A1A), + fontSize: 13.5, + ), + ), + const SizedBox(height: 4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: DidvanText( + _formatTime(message.timestamp), + fontSize: 10, + color: Colors.grey.shade400, + ), + ), + if (message.sources.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 6), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFF0066AA).withOpacity(0.08), + const Color(0xFF0088DD).withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: const Color(0xFF0066AA).withOpacity(0.2), + width: 0.8, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: const Color(0xFF0066AA).withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.bookmark_rounded, + size: 12, + color: Color(0xFF0066AA), + ), + ), + const SizedBox(width: 6), + DidvanText( + 'منابع: ${message.sources.join(", ")}', + fontSize: 11, + color: const Color(0xFF0066AA), + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ], + ), + ), + if (!message.isUser) const SizedBox(width: 8), + if (!message.isUser) _buildAiAvatar(), + ], + ), + ), + ); + } + + Widget _buildAiAvatar() { + return Container( + width: 32, + height: 32, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0066AA), + Color(0xFF00AAFF), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: const Color(0xFF0066AA).withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + padding: const EdgeInsets.all(7), + child: SvgPicture.asset( + 'lib/assets/icons/live ai.svg', + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), + ), + ); + } + + Widget _buildUserAvatar() { + return Consumer( + builder: (context, userProvider, _) { + if (userProvider.user.photo != null && + userProvider.user.photo!.isNotEmpty) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.25), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Image.network( + userProvider.user.photo!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildDefaultUserAvatar(); + }, + ), + ), + ); + } + return _buildDefaultUserAvatar(); + }, + ); + } + + Widget _buildDefaultUserAvatar() { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade400, + Colors.grey.shade500, + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.25), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: const Icon( + Icons.person_rounded, + size: 18, + color: Colors.white, + ), + ); + } + + Widget _buildLoadingIndicator() { + return Padding( + padding: const EdgeInsets.fromLTRB(14, 6, 14, 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: const Color(0xFFF5F7FA), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFE0E5EC).withOpacity(0.5), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'در حال تایپ...', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + const SizedBox(width: 10), + const SpinKitThreeBounce( + color: Color(0xFF0066AA), + size: 14, + ), + ], + ), + ), + const SizedBox(width: 8), + _buildAiAvatar(), + ], + ), + ); + } + + Widget _buildRecordingIndicator() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + margin: const EdgeInsets.only(bottom: 6), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFFFF3366).withOpacity(0.1), + const Color(0xFFFF6699).withOpacity(0.05), + ], + ), + border: Border.all( + color: const Color(0xFFFF3366).withOpacity(0.3), + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: const Color(0xFFFF3366), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: const Color(0xFFFF3366).withOpacity(0.4), + blurRadius: 6, + spreadRadius: 1, + ), + ], + ), + ), + const SizedBox(width: 10), + const Expanded( + child: DidvanText( + '🎙️ در حال ضبط...', + fontSize: 12, + color: Color(0xFFFF3366), + fontWeight: FontWeight.w600, + ), + ), + const Icon( + Icons.mic_rounded, + color: Color(0xFFFF3366), + size: 16, + ), + ], + ), + ); + } + + Widget _buildInputField() { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.95), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 15, + offset: const Offset(0, -3), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + constraints: const BoxConstraints(maxHeight: 100), + decoration: BoxDecoration( + color: const Color(0xFFF5F7FA), + borderRadius: BorderRadius.circular(22), + border: Border.all( + color: const Color(0xFFE0E5EC).withOpacity(0.5), + width: 1.2, + ), + ), + child: TextField( + controller: _messageController, + maxLines: null, + textInputAction: TextInputAction.send, + onSubmitted: (_) => _sendMessage(), + style: const TextStyle( + fontSize: 13.5, + color: Color(0xFF1A1A1A), + ), + decoration: InputDecoration( + hintText: 'پیام خود را بنویسید...', + hintStyle: TextStyle( + color: Colors.grey.shade400, + fontSize: 13, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + ), + const SizedBox(width: 10), + GestureDetector( + onTap: () { + if (!_isLoading) { + if (_isRecording) { + _stopRecording(); + } else { + _startRecording(); + } + } + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 44, + height: 44, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: _isRecording + ? [const Color(0xFFFF3366), const Color(0xFFFF6699)] + : [const Color(0xFF6B7280), const Color(0xFF9CA3AF)], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: (_isRecording + ? const Color(0xFFFF3366) + : const Color(0xFF6B7280)) + // ignore: deprecated_member_use + .withOpacity(0.35), + blurRadius: _isRecording ? 16 : 10, + offset: const Offset(0, 3), + ), + ], + ), + child: Icon( + _isRecording ? Icons.stop_rounded : Icons.mic_rounded, + color: Colors.white, + size: 20, + ), + ), + ), + const SizedBox(width: 10), + // دکمه ارسال + GestureDetector( + onTap: _isLoading || _isRecording ? null : _sendMessage, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 44, + height: 44, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: (_isLoading || _isRecording) + ? [Colors.grey.shade300, Colors.grey.shade400] + : [const Color(0xFF0066AA), const Color(0xFF00AAFF)], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: ((_isLoading || _isRecording) + ? Colors.grey.shade400 + : const Color(0xFF0066AA)) + // ignore: deprecated_member_use + .withOpacity(0.35), + blurRadius: 12, + offset: const Offset(0, 3), + ), + ], + ), + child: _isLoading + ? const Padding( + padding: EdgeInsets.all(11), + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Icon( + Icons.arrow_upward_rounded, + color: Colors.white, + size: 20, + ), + ), + ), + ], + ), + ); + } + + String _formatTime(DateTime time) { + final hour = time.hour.toString().padLeft(2, '0'); + final minute = time.minute.toString().padLeft(2, '0'); + return '$hour:$minute'; + } +} + +class ChatMessage { + final String text; + final bool isUser; + final DateTime timestamp; + final List sources; + + ChatMessage({ + required this.text, + required this.isUser, + required this.timestamp, + this.sources = const [], + }); +} diff --git a/lib/views/widgets/bookmark_button.dart b/lib/views/widgets/bookmark_button.dart index f671bac..f0af966 100644 --- a/lib/views/widgets/bookmark_button.dart +++ b/lib/views/widgets/bookmark_button.dart @@ -9,6 +9,7 @@ import 'package:flutter_svg/flutter_svg.dart'; class BookmarkButton extends StatefulWidget { final bool value; final Color? color; + final Color? unbookmarkedColor; final void Function(bool value) onMarkChanged; final bool askForConfirmation; final double gestureSize; @@ -26,6 +27,7 @@ class BookmarkButton extends StatefulWidget { required this.itemId, this.askForConfirmation = false, this.color, + this.unbookmarkedColor, this.svgIconOn, this.svgIconOff, }) : super(key: key); @@ -73,13 +75,21 @@ class _BookmarkButtonState extends State { } } + Color? _getColor(BuildContext context) { + if (_value) { + return widget.color; + } else { + return widget.unbookmarkedColor; + } + } + @override Widget build(BuildContext context) { print("BookmarkButton build - value: $_value"); - + final iconOn = widget.svgIconOn ?? 'lib/assets/icons/bookmark_on.svg'; final iconOff = widget.svgIconOff ?? 'lib/assets/icons/bookmark_off.svg'; - + return IconButton( iconSize: widget.gestureSize, onPressed: _handleTap, @@ -93,8 +103,9 @@ class _BookmarkButtonState extends State { key: ValueKey('bookmark_$_value'), width: widget.gestureSize, height: widget.gestureSize, + color: _getColor(context), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/views/widgets/didvan/bnb.dart b/lib/views/widgets/didvan/bnb.dart index fe95083..89c75c6 100644 --- a/lib/views/widgets/didvan/bnb.dart +++ b/lib/views/widgets/didvan/bnb.dart @@ -18,8 +18,7 @@ class DidvanBNB extends StatelessWidget { height: 72, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, - borderRadius: - const BorderRadius.vertical(top: Radius.circular(0)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(0)), border: const Border( top: BorderSide( color: Color.fromARGB(255, 224, 224, 224), @@ -27,55 +26,61 @@ class DidvanBNB extends StatelessWidget { ), ), ), - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - _NavBarItem( - isSelected: currentTabIndex == 1, - title: 'رسانه', - selectedIconPath: 'lib/assets/icons/media selected.svg', // Selected SVG icon - unselectedIconPath: 'lib/assets/icons/media.svg', // Unselected SVG icon - onTap: () => onTabChanged(1), - ), - _NavBarItem( - isSelected: currentTabIndex == 4, - title: 'هوشان', - selectedIconPath: 'lib/assets/icons/houshan_selected.svg', // Selected SVG icon - unselectedIconPath: 'lib/assets/icons/bot.svg', // Unselected SVG icon - onTap: () => onTabChanged(4), - ), - _NavBarItem( - isSelected: currentTabIndex == 0, - title: 'خانه', - selectedIconPath: DesignConfig.isDark - ? 'lib/assets/icons/selected home.svg' - : 'lib/assets/icons/selected home.svg', - unselectedIconPath: DesignConfig.isDark - ? 'assets/images/logos/logo-vertical-dark.svg' - : 'lib/assets/icons/home2.svg', // Unselected SVG icon - onTap: () => onTabChanged(0), - ), - _NavBarItem( - isSelected: currentTabIndex == 2, - title: 'نبض صنعت', - selectedIconPath: DesignConfig.isDark - ? 'lib/assets/icons/stats_nav_icon_dark_solid.svg' - : 'lib/assets/icons/chart 2_solid.svg', // Selected SVG icon - unselectedIconPath: DesignConfig.isDark - ? 'lib/assets/icons/stats_nav_icon_dark.svg' - : 'lib/assets/icons/chart 2.svg', // Unselected SVG icon - onTap: () => onTabChanged(2), - ), - _NavBarItem( - isSelected: currentTabIndex == 3, - title: 'کاوش', - selectedIconPath: 'lib/assets/icons/explore select.svg', // Selected SVG icon - unselectedIconPath: 'lib/assets/icons/discover.svg', // Unselected SVG icon - onTap: () => onTabChanged(3), - ), - ], - ), - ); + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + _NavBarItem( + isSelected: currentTabIndex == 1, + title: 'رسانه', + selectedIconPath: + 'lib/assets/icons/media selected.svg', // Selected SVG icon + unselectedIconPath: + 'lib/assets/icons/media.svg', // Unselected SVG icon + onTap: () => onTabChanged(1), + ), + _NavBarItem( + isSelected: currentTabIndex == 4, + title: 'هوشان', + selectedIconPath: + 'lib/assets/icons/houshan_selected.svg', // Selected SVG icon + unselectedIconPath: + 'lib/assets/icons/bot.svg', // Unselected SVG icon + onTap: () => onTabChanged(4), + ), + _NavBarItem( + isSelected: currentTabIndex == 0, + title: 'خانه', + selectedIconPath: DesignConfig.isDark + ? 'lib/assets/icons/selected home.svg' + : 'lib/assets/icons/selected home.svg', + unselectedIconPath: DesignConfig.isDark + ? 'lib/assets/icons/home2.svg' + : 'lib/assets/icons/home2.svg', // Unselected SVG icon + onTap: () => onTabChanged(0), + ), + _NavBarItem( + isSelected: currentTabIndex == 2, + title: 'نبض صنعت', + selectedIconPath: DesignConfig.isDark + ? 'lib/assets/icons/Nabz_Sanat.svg' + : 'lib/assets/icons/Nabz_Sanat.svg', // Selected SVG icon + unselectedIconPath: DesignConfig.isDark + ? 'lib/assets/icons/chart 2.svg' + : 'lib/assets/icons/chart 2.svg', // Unselected SVG icon + onTap: () => onTabChanged(2), + ), + _NavBarItem( + isSelected: currentTabIndex == 3, + title: 'کاوش', + selectedIconPath: + 'lib/assets/icons/explore select.svg', // Selected SVG icon + unselectedIconPath: + 'lib/assets/icons/discover.svg', // Unselected SVG icon + onTap: () => onTabChanged(3), + ), + ], + ), + ); } } @@ -115,22 +120,22 @@ class _NavBarItemState extends State<_NavBarItem> @override void initState() { super.initState(); - + _scaleController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); - + _bounceController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); - + _rippleController = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, ); - + _scaleAnimation = Tween( begin: 1.0, end: 0.85, @@ -138,7 +143,7 @@ class _NavBarItemState extends State<_NavBarItem> parent: _scaleController, curve: Curves.easeInOut, )); - + _bounceAnimation = Tween( begin: 1.0, end: 1.2, @@ -146,7 +151,7 @@ class _NavBarItemState extends State<_NavBarItem> parent: _bounceController, curve: Curves.elasticOut, )); - + _rippleAnimation = Tween( begin: 0.0, end: 1.0, @@ -178,13 +183,13 @@ class _NavBarItemState extends State<_NavBarItem> }); _scaleController.reverse(); _rippleController.reverse(); - + if (widget.isSelected) { _bounceController.forward().then((_) { _bounceController.reverse(); }); } - + widget.onTap(); } @@ -223,9 +228,10 @@ class _NavBarItemState extends State<_NavBarItem> height: 60 * _rippleAnimation.value, decoration: BoxDecoration( shape: BoxShape.circle, - color: Theme.of(context).colorScheme.primary.withOpacity( - 0.1 * (1 - _rippleAnimation.value), - ), + color: + Theme.of(context).colorScheme.primary.withOpacity( + 0.1 * (1 - _rippleAnimation.value), + ), ), ); }, @@ -234,27 +240,33 @@ class _NavBarItemState extends State<_NavBarItem> children: [ const SizedBox(height: 4), AnimatedBuilder( - animation: Listenable.merge([_scaleAnimation, _bounceAnimation]), + animation: + Listenable.merge([_scaleAnimation, _bounceAnimation]), builder: (context, child) { - final scale = _scaleAnimation.value * + final scale = _scaleAnimation.value * (widget.isSelected ? _bounceAnimation.value : 1.0); - + return Transform.scale( scale: scale, child: AnimatedContainer( - padding: EdgeInsets.all(widget.isHomeButton ? 8 : 4), + padding: + EdgeInsets.all(widget.isHomeButton ? 8 : 4), duration: DesignConfig.lowAnimationDuration, - - decoration: - BoxDecoration( + decoration: BoxDecoration( shape: BoxShape.circle, color: widget.isSelected - ? Theme.of(context).colorScheme.primary.withOpacity(0.1) + ? Theme.of(context) + .colorScheme + .primary + .withOpacity(0.1) : Colors.transparent, boxShadow: widget.isSelected ? [ BoxShadow( - color: Theme.of(context).colorScheme.primary.withOpacity(0.3), + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3), blurRadius: 8, spreadRadius: 1, ) @@ -262,21 +274,28 @@ class _NavBarItemState extends State<_NavBarItem> : null, ), child: SizedBox( - width: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32), - height: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32), + width: widget.isHomeButton + ? 50 + : (widget.isSelected ? 50 : 32), + height: widget.isHomeButton + ? 50 + : (widget.isSelected ? 50 : 32), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, animation) { return ScaleTransition( scale: animation, child: RotationTransition( - turns: Tween(begin: 0.8, end: 1.0).animate(animation), + turns: Tween(begin: 0.8, end: 1.0) + .animate(animation), child: child, ), ); }, child: SvgPicture.asset( - widget.isSelected ? widget.selectedIconPath : widget.unselectedIconPath, + widget.isSelected + ? widget.selectedIconPath + : widget.unselectedIconPath, key: ValueKey(widget.isSelected), ), ), @@ -310,4 +329,4 @@ class _NavBarItemState extends State<_NavBarItem> ), ); } -} \ No newline at end of file +} diff --git a/lib/views/widgets/didvan/card.dart b/lib/views/widgets/didvan/card.dart index 5be4aa4..9b7e7da 100644 --- a/lib/views/widgets/didvan/card.dart +++ b/lib/views/widgets/didvan/card.dart @@ -24,7 +24,7 @@ class DidvanCard extends StatelessWidget { padding: padding, margin: margin, decoration: BoxDecoration( - borderRadius: DesignConfig.mediumBorderRadius, + borderRadius: DesignConfig.highBorderRadius, color: Theme.of(context).colorScheme.surface, border: enableBorder ? DesignConfig.cardBorder : null, ), diff --git a/lib/views/widgets/home_app_bar.dart b/lib/views/widgets/home_app_bar.dart index 283e8bd..97c507c 100644 --- a/lib/views/widgets/home_app_bar.dart +++ b/lib/views/widgets/home_app_bar.dart @@ -9,6 +9,7 @@ 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'; +import 'package:didvan/views/widgets/ai_chat_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_animate/flutter_animate.dart'; @@ -18,16 +19,29 @@ class HomeAppBar extends StatelessWidget { final bool showBackButton; final String? title; final bool showSearchField; + final Function(String)? onSearchChanged; + final VoidCallback? onFilterPressed; + final FocusNode? searchFocusNode; + final bool? isFiltered; + final String? searchValue; const HomeAppBar({ super.key, this.showBackButton = false, this.title, this.showSearchField = false, + this.onSearchChanged, + this.onFilterPressed, + this.searchFocusNode, + this.isFiltered, + this.searchValue, }); @override Widget build(BuildContext context) { + final homeState = + (onSearchChanged == null) ? context.watch() : null; + return Column( children: [ Padding( @@ -81,9 +95,10 @@ class HomeAppBar extends StatelessWidget { Row( children: [ IconButton( - icon: const Icon(DidvanIcons.bookmark_regular), // + icon: SvgPicture.asset( + 'lib/assets/icons/hugeicons_telescope-01.svg'), onPressed: () { - Navigator.of(context).pushNamed(Routes.bookmarks); // + Navigator.of(context).pushNamed(Routes.bookmarks); }, ), GestureDetector( @@ -114,22 +129,37 @@ class HomeAppBar extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: SearchField( - title: 'دیدوان', - focusNode: context.read().searchFieldFocusNode, + title: title ?? 'دیدوان', + focusNode: searchFocusNode ?? + context.read().searchFieldFocusNode, onChanged: (value) { - final homeState = context.read(); - if (value.length >= 2) { - homeState.onSearchChanged(value); - } else if (value.isEmpty) { - homeState.clearSearch(); + if (onSearchChanged != null) { + onSearchChanged!(value); + } else { + final state = context.read(); + if (value.length >= 2) { + state.onSearchChanged(value); + } else if (value.isEmpty) { + state.clearSearch(); + } } }, - onFilterButtonPressed: () => _showFilterBottomSheet(context), - isFiltered: context.watch().filtering, - value: context.watch().search, - extraIconPath: 'lib/assets/icons/profile.svg', + onFilterButtonPressed: () { + if (onFilterPressed != null) { + onFilterPressed!(); + } else { + _showFilterBottomSheet(context); + } + }, + isFiltered: isFiltered ?? homeState?.filtering ?? false, + value: searchValue ?? homeState?.search, + extraIconPath: 'lib/assets/icons/live ai.svg', onExtraIconPressed: () { - print('Extra icon pressed!'); + showDialog( + context: context, + barrierDismissible: true, + builder: (context) => const AiChatDialog(), + ); }, ), ).animate().fadeIn(delay: 200.ms, duration: 500.ms), diff --git a/lib/views/widgets/liked_button.dart b/lib/views/widgets/liked_button.dart index b42b3ed..d8401f5 100644 --- a/lib/views/widgets/liked_button.dart +++ b/lib/views/widgets/liked_button.dart @@ -9,6 +9,7 @@ import 'package:flutter_svg/svg.dart'; class LikedButton extends StatefulWidget { final bool value; final Color? color; + final Color? unlikedColor; final void Function(bool value) onMarkChanged; final bool askForConfirmation; final double gestureSize; @@ -25,6 +26,7 @@ class LikedButton extends StatefulWidget { required this.likes, this.askForConfirmation = false, this.color, + this.unlikedColor, }) : super(key: key); @override @@ -92,10 +94,14 @@ class _LikedButtonState extends State { } }, child: SvgPicture.asset( - _value ? 'lib/assets/icons/heart_fill.svg':'lib/assets/icons/heart.svg', + _value + ? 'lib/assets/icons/heart_fill.svg' + : 'lib/assets/icons/heart2.svg', height: 24, color: widget.color ?? - (!_value ? Colors.white : Theme.of(context).colorScheme.error), + (_value + ? Theme.of(context).colorScheme.error + : widget.unlikedColor), ), ), ], diff --git a/lib/views/widgets/overview/news.dart b/lib/views/widgets/overview/news.dart index 4f67df2..9b64b04 100644 --- a/lib/views/widgets/overview/news.dart +++ b/lib/views/widgets/overview/news.dart @@ -9,6 +9,7 @@ import 'package:didvan/views/widgets/liked_button.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; class NewsOverview extends StatelessWidget { @@ -46,39 +47,57 @@ class NewsOverview extends StatelessWidget { children: [ SkeletonImage( imageUrl: news.image, - width: 64, - height: 64, + width: 100, + height: 100, + borderRadius: const BorderRadius.all(Radius.circular(16)), ), - const SizedBox(width: 8), + const SizedBox(width: 12), Expanded( - child: SizedBox( - height: 64, - child: DidvanText( - news.title, - style: Theme.of(context).textTheme.bodyLarge, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + news.title, + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(fontWeight: FontWeight.bold, fontSize: 16), + ), + const SizedBox(height: 8), + DidvanText( + news.description, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ], ), ), ], ), - const SizedBox(height: 8), - DidvanText( - news.description, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), const DidvanDivider(verticalPadding: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - DidvanText( - news.reference!, - style: Theme.of(context).textTheme.bodySmall, + SvgPicture.asset('lib/assets/icons/calendar.svg'), + const SizedBox( + width: 5, ), DidvanText( - ' - ${DateTime.parse(news.createdAt).toPersianDateStr()}', + DateTime.parse(news.createdAt).toPersianDateStr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox( + width: 20, + ), + SvgPicture.asset('lib/assets/icons/global.svg'), + const SizedBox( + width: 5, + ), + DidvanText( + news.reference!, style: Theme.of(context).textTheme.bodySmall, ), ], @@ -94,6 +113,7 @@ class NewsOverview extends StatelessWidget { onLikedChanged(news.id, value, false), askForConfirmation: hasUnmarkConfirmation, likes: news.likes, + unlikedColor: const Color.fromARGB(255, 102, 102, 102), ), const SizedBox( width: 4.0, @@ -101,11 +121,15 @@ class NewsOverview extends StatelessWidget { BookmarkButton( itemId: news.id, type: 'news', - gestureSize: 32, + gestureSize: 22, value: news.marked, onMarkChanged: (value) => onMarkChanged(news.id, value, false), askForConfirmation: hasUnmarkConfirmation, + svgIconOn: 'lib/assets/icons/bookmark_fill.svg', + svgIconOff: 'lib/assets/icons/archive-tick.svg', + color: const Color.fromARGB(255, 102, 102, 102), + unbookmarkedColor: const Color.fromARGB(255, 102, 102, 102), ), ], ) diff --git a/lib/views/widgets/text_divider.dart b/lib/views/widgets/text_divider.dart index dce93e9..978afbf 100644 --- a/lib/views/widgets/text_divider.dart +++ b/lib/views/widgets/text_divider.dart @@ -21,12 +21,13 @@ class TextDivider extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: + padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Row( children: [ Expanded( child: Divider( - color: lineColor ?? Theme.of(context).dividerColor, + color: const Color.fromARGB(255, 184, 184, 184), thickness: lineThickness ?? 1.0, ), ), @@ -34,15 +35,16 @@ class TextDivider extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( text, - style: textStyle ?? Theme.of(context).textTheme.bodyMedium?.copyWith( - color: textColor ?? Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), + style: textStyle ?? + Theme.of(context).textTheme.bodyLarge?.copyWith( + color: const Color.fromARGB(255, 0, 53, 70), + fontWeight: FontWeight.bold, + ), ), ), Expanded( child: Divider( - color: lineColor ?? Theme.of(context).dividerColor, + color: const Color.fromARGB(255, 184, 184, 184), thickness: lineThickness ?? 1.0, ), ), @@ -50,4 +52,4 @@ class TextDivider extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/linux/flutter/ephemeral/.plugin_symlinks/app_links_linux b/linux/flutter/ephemeral/.plugin_symlinks/app_links_linux deleted file mode 120000 index b321ae5..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/app_links_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/app_links_linux-1.0.3/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/device_info_plus b/linux/flutter/ephemeral/.plugin_symlinks/device_info_plus deleted file mode 120000 index 2b7b373..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/device_info_plus +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/device_info_plus-11.5.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/file_picker b/linux/flutter/ephemeral/.plugin_symlinks/file_picker deleted file mode 120000 index e54ac3b..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/file_picker +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/file_picker-8.3.7/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux b/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux deleted file mode 120000 index 1240f60..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_linux-0.9.3+2/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/flutter_local_notifications_linux b/linux/flutter/ephemeral/.plugin_symlinks/flutter_local_notifications_linux deleted file mode 120000 index 1cc3363..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/flutter_local_notifications_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_local_notifications_linux-6.0.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux b/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux deleted file mode 120000 index 5b6e7d8..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.2/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/gtk b/linux/flutter/ephemeral/.plugin_symlinks/gtk deleted file mode 120000 index b3b9973..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/gtk +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/gtk-2.1.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux b/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux deleted file mode 120000 index 88259ea..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_linux-0.2.1+2/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus b/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus deleted file mode 120000 index 7851378..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/package_info_plus-8.3.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux deleted file mode 120000 index bbbc6b4..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/record_linux b/linux/flutter/ephemeral/.plugin_symlinks/record_linux deleted file mode 120000 index b2433fb..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/record_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/record_linux-0.7.2/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter b/linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter deleted file mode 120000 index 6356c8a..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/sentry_flutter-8.14.2/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux b/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux deleted file mode 120000 index 5f54b9d..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.2.1/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/wakelock_plus b/linux/flutter/ephemeral/.plugin_symlinks/wakelock_plus deleted file mode 120000 index 67419bb..0000000 --- a/linux/flutter/ephemeral/.plugin_symlinks/wakelock_plus +++ /dev/null @@ -1 +0,0 @@ -C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/wakelock_plus-1.3.1/ \ No newline at end of file diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index ae25c99..42e7b43 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,6 +1,6 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=C:\Users\UI-UX\AppData\Local\flutter -FLUTTER_APPLICATION_PATH=C:\Users\UI-UX\Desktop\projects\didvan-app +FLUTTER_ROOT=C:\flutter +FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build FLUTTER_BUILD_NAME=4.0.1 diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index 9e7da27..4ca9df9 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,7 +1,7 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=C:\Users\UI-UX\AppData\Local\flutter" -export "FLUTTER_APPLICATION_PATH=C:\Users\UI-UX\Desktop\projects\didvan-app" +export "FLUTTER_ROOT=C:\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=4.0.1" diff --git a/pubspec.lock b/pubspec.lock index ebab0ac..ce7740e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -277,10 +277,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -857,10 +857,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" js: dependency: "direct main" description: @@ -897,26 +897,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1145,10 +1145,10 @@ packages: dependency: "direct main" description: name: persian_datetime_picker - sha256: "7ccbfd3a68dc89d405550f624e9fa590c914fed2aa2d48973c4f4400baab2e06" + sha256: "6a5ae6b9f717a6619ae29e65e4c8074285865a88d339dd05c91b9a5b6f8f47d7" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" persian_number_utility: dependency: "direct main" description: @@ -1462,10 +1462,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timezone: dependency: transitive description: @@ -1614,10 +1614,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: "direct main" description: @@ -1763,5 +1763,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index e5416d6..19770a4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 4.0.1+6000 environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -35,7 +35,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - intl: ^0.19.0 + intl: 0.20.2 animated_toggle_switch: ^0.8.2 toggle_switch: ^2.3.0 provider: ^6.0.1 @@ -104,7 +104,7 @@ dependencies: flutter_downloader: ^1.11.8 # win32: ^5.8.0 sentry_flutter: ^8.12.0 - persian_datetime_picker: ^3.1.0 + persian_datetime_picker: ^3.2.0 just_audio_web: ^0.4.13 image_cropper: ^9.0.0 package_info_plus: ^8.3.0 @@ -118,6 +118,7 @@ dependencies: flutter_animate: ^4.5.2 # image_gallery_saver: ^2.0.3 # fading_edge_scrollview: ^4.1.1 + dev_dependencies: flutter_test: sdk: flutter