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