diff --git a/.gitignore b/.gitignore
index db39db3..976a66d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+android/app/.cxx
+android/app/.cxx/Debug
+
# Miscellaneous
*.class
*.log
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 566d09b..071c50d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -36,6 +36,8 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
+
+ coreLibraryDesugaringEnabled true
}
kotlinOptions {
@@ -58,10 +60,10 @@ android {
signingConfigs {
release {
- keyAlias keystoreProperties['keyAlias']
- keyPassword keystoreProperties['keyPassword']
- storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
- storePassword keystoreProperties['storePassword']
+ storeFile file("keystore.jks")
+ storePassword "12799721"
+ keyAlias "upload"
+ keyPassword "12799721"
}
}
buildTypes {
@@ -69,6 +71,7 @@ android {
signingConfig signingConfigs.release
minifyEnabled false
shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@@ -109,4 +112,5 @@ dependencies {
implementation "androidx.room:room-runtime:2.2.5"
implementation "androidx.sqlite:sqlite-framework:2.1.0"
implementation "androidx.sqlite:sqlite:2.1.0"
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}
diff --git a/android/settings.gradle b/android/settings.gradle
index 481d674..3204677 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,7 +18,7 @@ repositories {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.1.0" apply false
+ id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
}
diff --git a/lib/assets/images/categories/Vector.svg b/lib/assets/images/categories/Vector.svg
new file mode 100644
index 0000000..3148968
--- /dev/null
+++ b/lib/assets/images/categories/Vector.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/Badge-Green.svg b/lib/assets/images/features/Badge-Green.svg
new file mode 100644
index 0000000..121c33d
--- /dev/null
+++ b/lib/assets/images/features/Badge-Green.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/Badge.svg b/lib/assets/images/features/Badge.svg
new file mode 100644
index 0000000..1546d11
--- /dev/null
+++ b/lib/assets/images/features/Badge.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/BadgeBookmark.svg b/lib/assets/images/features/BadgeBookmark.svg
new file mode 100644
index 0000000..0885f94
--- /dev/null
+++ b/lib/assets/images/features/BadgeBookmark.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/assets/images/features/MFT.svg b/lib/assets/images/features/MFT.svg
new file mode 100644
index 0000000..46c95ab
--- /dev/null
+++ b/lib/assets/images/features/MFT.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/Saha Solid.svg b/lib/assets/images/features/Saha Solid.svg
new file mode 100644
index 0000000..f3d552a
--- /dev/null
+++ b/lib/assets/images/features/Saha Solid.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/ant-design_dot-chart-outlined.svg b/lib/assets/images/features/ant-design_dot-chart-outlined.svg
new file mode 100644
index 0000000..461600f
--- /dev/null
+++ b/lib/assets/images/features/ant-design_dot-chart-outlined.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/fluent_arrow-swap-24-filled.svg b/lib/assets/images/features/fluent_arrow-swap-24-filled.svg
new file mode 100644
index 0000000..5475d49
--- /dev/null
+++ b/lib/assets/images/features/fluent_arrow-swap-24-filled.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/images/features/hugeicons_idea-dark.svg b/lib/assets/images/features/hugeicons_idea-dark.svg
new file mode 100644
index 0000000..ea5fbf8
--- /dev/null
+++ b/lib/assets/images/features/hugeicons_idea-dark.svg
@@ -0,0 +1,20 @@
+
diff --git a/lib/assets/images/features/hugeicons_idea-light.svg b/lib/assets/images/features/hugeicons_idea-light.svg
new file mode 100644
index 0000000..9d29984
--- /dev/null
+++ b/lib/assets/images/features/hugeicons_idea-light.svg
@@ -0,0 +1,6 @@
+
diff --git a/lib/constants/assets.dart b/lib/constants/assets.dart
index bf28018..aaf4899 100644
--- a/lib/constants/assets.dart
+++ b/lib/constants/assets.dart
@@ -84,6 +84,7 @@ class Assets {
static String get risk => '$_baseFeaturesPath/risk-$_themeSuffix.svg';
static String get saha => '$_baseFeaturesPath/saha-$_themeSuffix.svg';
static String get ai => '$_baseFeaturesPath/ai-$_themeSuffix.svg';
+ static String get hugeideas => '$_baseFeaturesPath/hugeicons_idea-$_themeSuffix.svg';
static String get startup => '$_baseFeaturesPath/startup-$_themeSuffix.svg';
static String get stats => '$_baseFeaturesPath/stats-$_themeSuffix.svg';
static String get tech => '$_baseFeaturesPath/tech-$_themeSuffix.svg';
diff --git a/lib/models/home_page_content/swot.dart b/lib/models/home_page_content/swot.dart
new file mode 100644
index 0000000..46ebaa6
--- /dev/null
+++ b/lib/models/home_page_content/swot.dart
@@ -0,0 +1,37 @@
+class SwotItem {
+ final int id;
+ final String title;
+ final String description;
+ final String category;
+ final String type;
+ final String imageUrl;
+ final double x1;
+ final double y1;
+ final String createdAt;
+
+ SwotItem({
+ required this.id,
+ required this.title,
+ required this.description,
+ required this.category,
+ required this.type,
+ required this.imageUrl,
+ required this.x1,
+ required this.y1,
+ required this.createdAt,
+ });
+
+ factory SwotItem.fromJson(Map json) {
+ return SwotItem(
+ id: json['id'],
+ title: json['title'],
+ description: json['description'],
+ category: json['category'],
+ type: json['type'],
+ imageUrl: json["url"],
+ x1: json["scoreX1"],
+ y1: json["scoreY1"],
+ createdAt: json["createdAt"]
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/models/note.dart b/lib/models/note.dart
new file mode 100644
index 0000000..cd87b0c
--- /dev/null
+++ b/lib/models/note.dart
@@ -0,0 +1,18 @@
+class Note {
+ final int postId;
+ final String content;
+
+ Note({required this.postId, required this.content});
+
+ factory Note.fromJson(Map json) {
+ return Note(
+ postId: json['postId'],
+ content: json['content'],
+ );
+ }
+
+ Map toJson() => {
+ 'postId': postId,
+ 'content': content,
+ };
+}
diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart
index 9846adc..01394e7 100644
--- a/lib/routes/route_generator.dart
+++ b/lib/routes/route_generator.dart
@@ -299,16 +299,21 @@ class RouteGenerator {
),
);
case Routes.filteredBookmarks:
- return _createRoute(
- ChangeNotifierProvider(
- create: (context) => FilteredBookmarksState(
- (settings.arguments as Map)['type'],
- ),
- child: FilteredBookmarks(
- onDeleted:
- (settings.arguments as Map)['onDeleted']),
+ final args = settings.arguments as Map;
+ final type = args['type'] as int;
+ final onDeleted = args['onDeleted'] as void Function(int id)?;
+
+ return _createRoute(
+ ChangeNotifierProvider(
+ create: (context) => FilteredBookmarksState(
+ type,
),
- );
+ child: FilteredBookmarks(
+ onDeleted: onDeleted,
+ type: type,
+ ),
+ ),
+ );
case Routes.aiChat:
return _createRoute(ChangeNotifierProvider(
diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart
index cce8a18..fbac640 100644
--- a/lib/services/network/request_helper.dart
+++ b/lib/services/network/request_helper.dart
@@ -6,6 +6,7 @@ import 'package:didvan/models/requests/studio.dart';
class RequestHelper {
static const String baseUrl = 'https://api.didvan.app';
+ static const String baseUrl2 = 'http://opportunity-threat.didvan.com';
static const String _baseUserUrl = '$baseUrl/user';
static const String _baseRadarUrl = '$baseUrl/radar';
static const String _baseNewsUrl = '$baseUrl/news';
diff --git a/lib/services/note_service.dart b/lib/services/note_service.dart
new file mode 100644
index 0000000..fe4e046
--- /dev/null
+++ b/lib/services/note_service.dart
@@ -0,0 +1,128 @@
+import 'dart:convert';
+
+import 'package:didvan/models/note.dart';
+import 'package:didvan/services/network/request.dart';
+import 'package:didvan/services/network/request_helper.dart';
+import 'package:http/http.dart' as RequestServices;
+
+class NoteService {
+ static Future getNoteByPostId(int postId) async {
+ try {
+ final response = await RequestServices.get(
+ Uri.parse('${RequestHelper.baseUrl2}/api/notes'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ },
+ );
+ print(
+ 'Get note - PostId: $postId, Status: ${response.statusCode}, Body: ${response.body}');
+ if (response.statusCode == 200) {
+ final List data = jsonDecode(response.body);
+ print('Notes list: $data');
+ final note = data.firstWhere(
+ (e) => e['postId'] == postId,
+ orElse: () => null,
+ );
+ print('Found note for PostId $postId: $note');
+ return note != null ? Note.fromJson(note) : null;
+ }
+ print('Failed to get note: ${response.statusCode}, ${response.body}');
+ return null;
+ } catch (e) {
+ print('Error in getNoteByPostId: $e');
+ return null;
+ }
+ }
+
+ static Future saveNote(int postId, String content) async {
+ try {
+ final response = await RequestServices.post(
+ Uri.parse('${RequestHelper.baseUrl2}/api/notes'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json'
+ },
+ body: jsonEncode({
+ 'postId': postId,
+ 'content': content,
+ }),
+ );
+ print(
+ 'Save note - PostId: $postId, Status: ${response.statusCode}, Body: ${response.body}');
+
+ if (response.statusCode == 200 || response.statusCode == 201) {
+ try {
+ final responseBody = jsonDecode(response.body);
+ if (responseBody['postId'] == postId &&
+ responseBody['content'] == content) {
+ return true;
+ } else {
+ print(
+ 'Server returned success status but data mismatch: ${response.body}');
+ return false;
+ }
+ } catch (e) {
+ print('Error parsing response body: $e');
+ return response.statusCode == 200 || response.statusCode == 201;
+ }
+ } else {
+ print('Failed to save note: ${response.statusCode}, ${response.body}');
+ return false;
+ }
+ } catch (e) {
+ print('Error in saveNote: $e');
+ return false;
+ }
+ }
+
+ static Future deleteNote(int postId) async {
+ final response = await RequestServices.delete(
+ Uri.parse('${RequestHelper.baseUrl2}/api/notes/post/$postId'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ },
+ );
+ return response.statusCode == 200;
+ }
+
+ static Future updateNote(int postId, String content) async {
+ try {
+ final response = await RequestServices.put(
+ Uri.parse('${RequestHelper.baseUrl2}/api/notes/post/$postId'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ body: jsonEncode({
+ 'postId': postId,
+ 'content': content,
+ }),
+ );
+ print(
+ 'Update note - PostId: $postId, Status: ${response.statusCode}, Body: ${response.body}');
+
+ if (response.statusCode == 200 || response.statusCode == 201) {
+ try {
+ final responseBody = jsonDecode(response.body);
+ if (responseBody['postId'] == postId &&
+ responseBody['content'] == content) {
+ return true;
+ } else {
+ print(
+ 'Server returned success status but data mismatch: ${response.body}');
+ return false;
+ }
+ } catch (e) {
+ print('Error parsing response body: $e');
+ return response.statusCode == 200 || response.statusCode == 201;
+ }
+ } else {
+ print('Failed to update note: ${response.statusCode}, ${response.body}');
+ return false;
+ }
+ } catch (e) {
+ print('Error in updateNote: $e');
+ return false;
+ }
+}
+}
diff --git a/lib/services/swot_service.dart b/lib/services/swot_service.dart
new file mode 100644
index 0000000..cbe282a
--- /dev/null
+++ b/lib/services/swot_service.dart
@@ -0,0 +1,35 @@
+import 'dart:convert';
+import 'package:didvan/models/home_page_content/swot.dart';
+import 'package:didvan/services/network/request.dart';
+import 'package:didvan/services/network/request_helper.dart';
+import 'package:http/http.dart' as http;
+
+class SwotService {
+ static Future> fetchSwotItems() async {
+ final response = await http.get(
+ Uri.parse('${RequestHelper.baseUrl2}/api/swot'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ },
+ );
+
+ print("Status Code: ${response.statusCode}");
+
+ // decode manually using utf8.decode on bodyBytes
+ final decodedBody = utf8.decode(response.bodyBytes);
+ print("Decoded Response Body: $decodedBody");
+
+ if (response.statusCode == 200) {
+ final jsonData = json.decode(decodedBody);
+
+ if (jsonData['content'] == null || jsonData['content'] is! List) {
+ throw Exception('The data structure is wrong.');
+ }
+
+ final List items = jsonData['content'];
+ return items.map((e) => SwotItem.fromJson(e)).toList();
+ } else {
+ throw Exception('Error receiving data - statusCode: ${response.statusCode}');
+ }
+ }
+}
diff --git a/lib/views/home/bookmarks/bookmark_service.dart b/lib/views/home/bookmarks/bookmark_service.dart
new file mode 100644
index 0000000..5f00e8c
--- /dev/null
+++ b/lib/views/home/bookmarks/bookmark_service.dart
@@ -0,0 +1,127 @@
+import 'dart:convert';
+import 'package:didvan/models/home_page_content/swot.dart';
+import 'package:didvan/services/network/request.dart';
+import 'package:didvan/services/network/request_helper.dart';
+import 'package:http/http.dart' as http;
+import 'package:http/http.dart' as RequestServicess;
+
+class BookmarkService {
+ static Future> fetchBookmarks() async {
+ final response = await http.get(
+ Uri.parse('${RequestHelper.baseUrl2}/api/bookmarks'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ },
+ );
+ print("fetchBookmarks: ${response.statusCode} - ${response.body}");
+ if (response.statusCode == 200) {
+ final data = jsonDecode(response.body) as List;
+ return data.map((e) => e['postId'] as int).toList();
+ } else {
+ throw Exception("Failed to fetch bookmarks");
+ }
+ }
+
+ static Future> fetchSwotBookmarks() async {
+ final response = await http.get(
+ Uri.parse('${RequestHelper.baseUrl2}/api/swot_bookmarks'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ );
+ print("fetchSwotBookmarks: ${response.statusCode} - ${response.body}");
+ if (response.statusCode == 200) {
+ final data = jsonDecode(response.body) as List;
+ return data.map((e) => e['swotId'] as int).toList();
+ } else {
+ throw Exception("Failed to fetch swot bookmarks");
+ }
+ }
+
+ static Future addSwotBookmark(int swotId) async {
+ final response = await http.post(
+ Uri.parse('${RequestHelper.baseUrl2}/api/swot_bookmarks'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ body: jsonEncode({'swotId': swotId}),
+ );
+
+ if (response.statusCode != 200 && response.statusCode != 201) {
+ throw Exception("Failed to add swot bookmark");
+ }
+ }
+
+ static Future removeSwotBookmark(int swotId) async {
+ final response = await http.delete(
+ Uri.parse('${RequestHelper.baseUrl2}/api/swot_bookmarks/swot/$swotId'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ );
+
+ if (response.statusCode != 200 && response.statusCode != 204) {
+ throw Exception("Failed to remove swot bookmark");
+ }
+ }
+
+ static Future> fetchBookmarkedSwotItems() async {
+ final postIds = await fetchBookmarks(); // استفاده از همان لیست بوکمارکها
+ final List items = [];
+
+ for (final postId in postIds) {
+ try {
+ final response = await http.get(
+ Uri.parse("${RequestHelper.baseUrl2}/api/swot_items/$postId"),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ );
+
+ if (response.statusCode == 200) {
+ final postData = jsonDecode(response.body);
+ items.add(SwotItem.fromJson(postData));
+ } else {
+ print('Error fetching swot item $postId: ${response.statusCode}');
+ }
+ } catch (e) {
+ print('Error fetching swot item $postId: $e');
+ }
+ }
+
+ return items;
+ }
+
+ static Future addBookmark(int postId) async {
+ final response = await http.post(
+ Uri.parse('${RequestHelper.baseUrl2}/api/bookmarks'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ body: jsonEncode({'postId': postId}),
+ );
+
+ if (response.statusCode != 200 && response.statusCode != 201) {
+ throw Exception("Failed to add bookmark");
+ }
+ }
+
+ static Future removeBookmark(int postId) async {
+ final response = await http.delete(
+ Uri.parse('${RequestHelper.baseUrl2}/api/bookmarks/post/$postId'),
+ headers: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ 'Content-Type': 'application/json',
+ },
+ );
+
+ if (response.statusCode != 200 && response.statusCode != 204) {
+ throw Exception("Failed to remove bookmark");
+ }
+ }
+}
diff --git a/lib/views/home/bookmarks/bookmark_state.dart b/lib/views/home/bookmarks/bookmark_state.dart
index 67f0a8d..62eee14 100644
--- a/lib/views/home/bookmarks/bookmark_state.dart
+++ b/lib/views/home/bookmarks/bookmark_state.dart
@@ -1,29 +1,55 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
+import 'package:didvan/models/home_page_content/swot.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
+import 'package:didvan/views/home/bookmarks/bookmark_service.dart';
+import 'package:didvan/services/swot_service.dart';
class BookmarksState extends CoreProvier {
- final List bookmarks = [];
+ final List bookmarks = [];
+ List bookmarkedSwotItems = [];
+
String search = '';
String lastSearch = '';
- int page = 1;
+ int page = 1;
int lastPage = 1;
- bool get searching => search != '';
+ bool _swotItemsLoading = false;
+ bool get searching => search.isNotEmpty;
+ bool get swotItemsLoading => _swotItemsLoading;
- Future getBookmarks({required int page}) async {
- if (search != '') {
- lastSearch = search;
- }
- if (page == 1) {
- bookmarks.clear();
- }
- this.page = page;
+ Future loadInitialData() async {
appState = AppState.busy;
+ _swotItemsLoading = true;
+ bookmarks.clear();
+ bookmarkedSwotItems.clear();
+ notifyListeners();
+
+ await Future.wait([
+ _fetchGeneralBookmarks(page: 1),
+ _fetchSwotBookmarks(),
+ ]);
+
+
+ if (appState != AppState.failed && !searching) {
+ appState = AppState.idle;
+ } else if (appState != AppState.failed && searching && bookmarks.isEmpty && bookmarkedSwotItems.isEmpty) {
+
+ appState = AppState.idle;
+ }
+ _swotItemsLoading = false;
+ notifyListeners();
+ }
+
+ Future _fetchGeneralBookmarks({required int page}) async {
+ if (page == 1) bookmarks.clear();
+ this.page = page;
+ if (!searching) lastSearch = search;
+
final service = RequestService(
- RequestHelper.searchMarks(page: page, search: search),
+ RequestHelper.searchMarks(page: page, search: search.isNotEmpty ? search : null),
);
await service.httpGet();
if (service.isSuccess) {
@@ -32,15 +58,56 @@ class BookmarksState extends CoreProvier {
for (var i = 0; i < marks.length; i++) {
bookmarks.add(OverviewData.fromJson(marks[i]));
}
- appState = AppState.idle;
- return;
+ } else {
+ appState = AppState.failed;
}
- appState = AppState.failed;
+ }
+
+ Future searchAndLoadData({required int page}) async {
+ appState = AppState.busy;
+ _swotItemsLoading = true;
+ bookmarks.clear();
+ notifyListeners();
+
+ await Future.wait([
+ _fetchGeneralBookmarks(page: page),
+ _fetchSwotBookmarks(),
+ ]);
+
+ if (appState != AppState.failed) {
+ appState = AppState.idle;
+ }
+ _swotItemsLoading = false;
+ notifyListeners();
}
- void onMarkChanged(int id, bool value) {
- if (value) return;
+
+ Future _fetchSwotBookmarks() async {
+ try {
+ final postIds = await BookmarkService.fetchBookmarks();
+ if (postIds.isNotEmpty) {
+ final allSwots = await SwotService.fetchSwotItems();
+
+ bookmarkedSwotItems = allSwots.where((swot) {
+ final isBookmarked = postIds.contains(swot.id);
+ if (search.isEmpty) return isBookmarked;
+ return isBookmarked && swot.title.toLowerCase().contains(search.toLowerCase());
+ }).toList();
+ } else {
+ bookmarkedSwotItems = [];
+ }
+ } catch (e) {
+ bookmarkedSwotItems = [];
+ }
+ }
+
+ void onMarkChanged(int id, bool value) {
bookmarks.removeWhere((element) => element.id == id);
notifyListeners();
}
-}
+
+ void onSwotMarkChanged(int swotId) {
+ bookmarkedSwotItems.removeWhere((element) => element.id == swotId);
+ notifyListeners();
+ }
+}
\ No newline at end of file
diff --git a/lib/views/home/bookmarks/bookmarks.dart b/lib/views/home/bookmarks/bookmarks.dart
index 06ff760..0f324a8 100644
--- a/lib/views/home/bookmarks/bookmarks.dart
+++ b/lib/views/home/bookmarks/bookmarks.dart
@@ -4,14 +4,15 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
+import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/home/bookmarks/bookmark_state.dart';
+import 'package:didvan/views/home/main/widgets/swot_bookmark.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/menu_item.dart';
import 'package:didvan/views/widgets/overview/multitype.dart';
-// import 'package:didvan/views/widgets/search_field.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
@@ -19,6 +20,7 @@ import 'package:didvan/views/widgets/item_title.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/svg.dart';
import 'package:provider/provider.dart';
class Bookmarks extends StatefulWidget {
@@ -28,16 +30,36 @@ class Bookmarks extends StatefulWidget {
State createState() => _BookmarksState();
}
+
class _BookmarksState extends State {
final _focuseNode = FocusNode();
- // Timer? _timer;
+ Timer? _timer;
@override
void initState() {
- Future.delayed(Duration.zero, () {
- context.read().getBookmarks(page: 1);
- });
super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ context.read().loadInitialData();
+ });
+ }
+
+
+ void _onSearchChanged(String value) {
+ final state = context.read();
+ if (value.length < 3 && value.isNotEmpty) {
+ if (state.search.isNotEmpty && value.isEmpty) {
+ state.search = value;
+ state.loadInitialData();
+ }
+ return;
+ }
+ if (state.lastSearch == value && value.isNotEmpty) return;
+
+ _timer?.cancel();
+ _timer = Timer(const Duration(milliseconds: 700), () {
+ state.search = value;
+ state.searchAndLoadData(page: 1);
+ });
}
@override
@@ -54,13 +76,12 @@ class _BookmarksState extends State {
sliver: SliverToBoxAdapter(
child: Column(
children: [
- const SizedBox(height: 16),
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
- isVisible: !state.searching,
+ isVisible: !state.searching,
child: DidvanCard(
child: Column(
- children: [
+ children: [
MenuOption(
onTap: () => _onCategorySelected(5),
title: 'رادارهای استراتژیک',
@@ -109,85 +130,121 @@ class _BookmarksState extends State {
icon: DidvanIcons.infography_regular,
iconSize: 24,
),
+ const DidvanDivider(),
+ MenuOption(
+ onTap: () => _onCategorySelected(8),
+ title: 'فرصت و تهدید',
+ iconWidget: SvgPicture.asset("lib/assets/images/features/Saha Solid.svg",width: 24,),
+ iconSize: 24,
+ ),
],
),
),
),
- Align(
- alignment: Alignment.centerRight,
- child: AnimatedVisibility(
- duration: DesignConfig.lowAnimationDuration,
- isVisible: !state.searching,
- child: const ItemTitle(title: 'نشان شدهها'),
+ if (!state.searching || (state.bookmarks.isNotEmpty || state.bookmarkedSwotItems.isNotEmpty))
+ Align(
+ alignment: Alignment.centerRight,
+ child: AnimatedVisibility(
+ duration: DesignConfig.lowAnimationDuration,
+ isVisible: !state.searching,
+ child: const ItemTitle(title: 'نشان شدهها'),
+ ),
),
- ),
],
),
),
),
- SliverPadding(
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- ),
- sliver: SliverStateHandler(
- state: state,
- centerEmptyState: state.searching,
- builder: (context, state, index) {
- index++;
- if (index % 15 == 0 && state.lastPage != state.page) {
- state.getBookmarks(page: state.page + 1);
- }
- index--;
- return MultitypeOverview(
- item: state.bookmarks[index],
- onMarkChanged: state.onMarkChanged,
- hasUnmarkConfirmation: true,
- enableCaption: true,
- enableBookmark: true,
- );
- },
- placeholder: MultitypeOverview.placeholder,
- itemPadding: const EdgeInsets.only(bottom: 8),
- paddingEmptyState: 0,
- emptyState: state.searching
- ? EmptyResult(onNewSearch: _focuseNode.requestFocus)
- : Column(
- children: [
- DidvanText(
- 'در قسمت رصدخانه من، تمامی مطالبی که در قسمتهای مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشاندار) کردهاید، به تفکیک نمایش داده میشوند. همچنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.',
- fontSize: 14,
- color: Theme.of(context).colorScheme.title,
- textAlign: TextAlign.justify,
- ),
- Image.asset(
- Assets.bookmarkAnimation,
- width: MediaQuery.sizeOf(context).width,
- height: 180,
- ),
- ],
- ),
- enableEmptyState: state.bookmarks.isEmpty,
- childCount:
- state.bookmarks.length + (state.page != state.lastPage ? 1 : 0),
- onRetry: () => state.getBookmarks(page: state.page),
+
+ SliverStateHandler(
+ state: state,
+ enableEmptyState: state.bookmarks.isEmpty && state.bookmarkedSwotItems.isEmpty && !state.searching && !state.swotItemsLoading,
+ emptyState: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ DidvanText(
+ 'در قسمت رصدخانه من، تمامی مطالبی که در قسمتهای مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشاندار) کردهاید، به تفکیک نمایش داده میشوند. همچنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.',
+ fontSize: 14,
+ color: Theme.of(context).colorScheme.title,
+ textAlign: TextAlign.justify,
+ ),
+ Image.asset(
+ Assets.bookmarkAnimation,
+ width: MediaQuery.sizeOf(context).width,
+ height: 180,
+ ),
+ ],
),
+ placeholder: state.searching && state.bookmarks.isEmpty && state.bookmarkedSwotItems.isEmpty && !state.swotItemsLoading
+ ? EmptyResult(onNewSearch: _focuseNode.requestFocus)
+ : MultitypeOverview.placeholder,
+ builder: (context, state, index) {
+
+ if (index >= state.bookmarks.length) {
+ return const Center(child: CircularProgressIndicator());
+ }
+ return MultitypeOverview(
+ item: state.bookmarks[index],
+ onMarkChanged: state.onMarkChanged,
+ hasUnmarkConfirmation: true,
+ enableCaption: true,
+ enableBookmark: true,
+ );
+ },
+ itemPadding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
+ childCount: state.bookmarks.length + (state.page != state.lastPage && state.bookmarks.isNotEmpty ? 1 : 0),
+ onRetry: () => state.loadInitialData(),
),
+
+ if (state.appState == AppState.idle && state.bookmarkedSwotItems.isNotEmpty)
+ SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, index) {
+ final item = state.bookmarkedSwotItems[index];
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
+ child: SwotBookmark(
+ item: item,
+ onSwotUnbookmarked: (postId) {
+ state.onSwotMarkChanged(postId);
+ },
+ ),
+ );
+ },
+ childCount: state.bookmarkedSwotItems.length,
+ ),
+ )
+ else if (state.swotItemsLoading)
+ SliverToBoxAdapter(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Center(child: MultitypeOverview.placeholder),
+ ),
+ )
],
);
}
- void _onCategorySelected(int type) {
+ void _onCategorySelected(int type) {
FocusScope.of(context).unfocus();
+ final state = context.read();
+
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: {
'type': type,
'onDeleted': (int id) {
- final state = context.read();
state.bookmarks.removeWhere(
(element) => element.id == id && element.typeInteger == type);
+
+ if (type == 8) {
+ state.bookmarkedSwotItems.removeWhere((element) => element.id == id);
+ }
state.update();
},
+ }).then((_) {
+ state.loadInitialData();
});
}
+}
// void _onChanged(String value) {
// final state = context.read();
@@ -200,4 +257,4 @@ class _BookmarksState extends State {
// state.getBookmarks(page: 1);
// });
// }
-}
+
diff --git a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart
index 01bd404..849b8ab 100644
--- a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart
+++ b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart
@@ -1,16 +1,17 @@
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
import 'package:didvan/views/widgets/overview/multitype.dart';
-import 'package:didvan/views/widgets/overview/radar.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import 'package:didvan/views/home/main/widgets/swot_bookmark.dart';
class FilteredBookmarks extends StatefulWidget {
final void Function(int id)? onDeleted;
- const FilteredBookmarks({Key? key, this.onDeleted}) : super(key: key);
+ final int type;
+ const FilteredBookmarks({Key? key, this.onDeleted, required this.type}) : super(key: key);
@override
State createState() => _FilteredBookmarksState();
@@ -19,15 +20,18 @@ class FilteredBookmarks extends StatefulWidget {
class _FilteredBookmarksState extends State {
@override
void initState() {
+ super.initState();
+
+ final state = context.read();
Future.delayed(
Duration.zero,
- () => context.read().getBookmarks(page: 1),
+ () => state.getBookmarks(page: 1),
);
- super.initState();
}
String get _appBarTitle {
- switch (context.read().type) {
+
+ switch (widget.type) {
case 1:
return 'پویش افق';
case 2:
@@ -42,85 +46,83 @@ class _FilteredBookmarksState extends State {
return 'سها';
case 7:
return 'اینفوگرافی';
+ case 8:
+ return 'فرصت و تهدید';
default:
return 'پویش';
}
}
+ Future _onBookmarkChanged(int id, bool value, bool shouldUpdate, String itemType) async {
+ if (value) return;
+ widget.onDeleted?.call(id);
+
+ if (itemType == "swot") {
+ context.read().onSwotMarkChanged(id);
+ } else {
+ context.read().onMarkChanged(id, value);
+ }
+ }
+
+
@override
Widget build(BuildContext context) {
+
+ final state = context.watch();
+
return DidvanScaffold(
- appBarData: AppBarData(title: _appBarTitle),
- padding: const EdgeInsets.all(16),
+ appBarData: AppBarData(title: _appBarTitle, hasBack: true),
+ padding: const EdgeInsets.all(16),
slivers: [
- Consumer(
- builder: (context, state, child) =>
- SliverStateHandler(
+ if (widget.type == 8)
+ SliverStateHandler(
+ state: state,
+ enableEmptyState: state.bookmarkedSwotItems.isEmpty && !state.swotItemsLoading,
+ emptyState: const EmptyList(),
+ placeholder: MultitypeOverview.placeholder,
+ builder: (context, state, index) {
+ final item = state.bookmarkedSwotItems[index];
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8.0),
+ child: SwotBookmark(
+ item: item,
+ onSwotUnbookmarked: (postId) {
+ _onBookmarkChanged(postId, false, true, "swot");
+ },
+ ),
+ );
+ },
+ itemPadding: EdgeInsets.zero,
+ childCount: state.bookmarkedSwotItems.length,
+ onRetry: () => state.getBookmarks(page: 1),
+ )
+ else
+ SliverStateHandler(
state: state,
enableEmptyState: state.bookmarks.isEmpty,
itemPadding: const EdgeInsets.only(bottom: 8),
- placeholder: RadarOverview.placeholder,
+ placeholder: MultitypeOverview.placeholder,
emptyState: const EmptyList(),
builder: (context, state, index) {
- index++;
- if (index % 15 == 0 && state.lastPage != state.page) {
- state.getBookmarks(page: state.page + 1);
+ if (index >= state.bookmarks.length) {
+ if (state.page < state.lastPage) {
+ state.getBookmarks(page: state.page + 1);
+ return MultitypeOverview.placeholder;
+ }
+ return const SizedBox.shrink();
}
- index--;
+ final item = state.bookmarks[index];
return MultitypeOverview(
- item: state.bookmarks[index],
+ item: item,
enableCaption: true,
- onMarkChanged: (id, value) => _onBookmarkChanged(
- id,
- value,
- true,
- ),
+ onMarkChanged: (id, value) => _onBookmarkChanged(id, value, true, item.type),
enableBookmark: true,
);
- // if (state.type == 'radar') {
- // return RadarOverview(
- // radar: state.bookmarks[index],
- // onMarkChanged: _onBookmarkChanged,
- // onCommentsChanged: state.onCommentsChanged,
- // hasUnmarkConfirmation: true,
- // );
- // }
- // if (state.type == 'news') {
- // return NewsOverview(
- // news: state.bookmarks[index],
- // onMarkChanged: _onBookmarkChanged,
- // hasUnmarkConfirmation: true,
- // );
- // }
- // if (state.type == 'podcast') {
- // return PodcastOverview(
- // studioRequestArgs:
- // const StudioRequestArgs(page: 0, type: 'podcast'),
- // podcast: state.bookmarks[index],
- // onMarkChanged: _onBookmarkChanged,
- // hasUnmarkConfirmation: true,
- // );
- // }
- // return VideoOverview(
- // studioRequestArgs:
- // const StudioRequestArgs(page: 0, type: 'video'),
- // video: state.bookmarks[index],
- // onMarkChanged: _onBookmarkChanged,
- // hasUnmarkConfirmation: true,
- // );
},
- childCount: state.bookmarks.length,
+ childCount: state.bookmarks.length + (state.page < state.lastPage ? 1: 0),
onRetry: () => state.getBookmarks(page: state.page),
),
- ),
],
);
}
-
- Future _onBookmarkChanged(int id, bool value, bool shouldUpdate) async {
- if (value) return;
- final state = context.read();
- state.onMarkChanged(id, false);
- widget.onDeleted?.call(id);
- }
-}
+}
\ No newline at end of file
diff --git a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart
index a35c44e..edc3563 100644
--- a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart
+++ b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart
@@ -1,19 +1,36 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
+import 'package:didvan/models/home_page_content/swot.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
+import 'package:didvan/views/home/bookmarks/bookmark_service.dart';
+import 'package:didvan/services/swot_service.dart';
class FilteredBookmarksState extends CoreProvier {
final List bookmarks = [];
+ List bookmarkedSwotItems = [];
final int type;
int page = 1;
int lastPage = 1;
+ bool _swotItemsLoading = false;
+ bool get swotItemsLoading => _swotItemsLoading;
FilteredBookmarksState(this.type);
Future getBookmarks({required int page}) async {
+ if (type == 8) {
+ await _fetchSwotBookmarksFiltered();
+ } else {
+ await _fetchGeneralBookmarks(page: page);
+ }
+ }
+
+ Future _fetchGeneralBookmarks({required int page}) async {
this.page = page;
+ appState = AppState.busy;
+ notifyListeners();
+
final service = RequestService(
RequestHelper.searchMarks(
types: [type],
@@ -25,22 +42,63 @@ class FilteredBookmarksState extends CoreProvier {
if (service.isSuccess) {
lastPage = service.result['lastPage'];
final marks = service.result['contents'];
+ bookmarks.clear();
for (var i = 0; i < marks.length; i++) {
bookmarks.add(OverviewData.fromJson(marks[i]));
}
appState = AppState.idle;
- return;
+ } else {
+ appState = AppState.failed;
+ }
+ notifyListeners();
+ }
+
+ Future _fetchSwotBookmarksFiltered() async {
+ appState = AppState.busy;
+ _swotItemsLoading = true;
+ bookmarkedSwotItems.clear();
+ notifyListeners();
+
+ try {
+ final bookmarkedPostIds = await BookmarkService.fetchBookmarks();
+ if (bookmarkedPostIds.isNotEmpty) {
+ final allSwotItems = await SwotService.fetchSwotItems();
+ bookmarkedSwotItems = allSwotItems.where((swot) => bookmarkedPostIds.contains(swot.id)).toList();
+ } else {
+ bookmarkedSwotItems = [];
+ }
+ appState = AppState.idle;
+ } catch (e) {
+ appState = AppState.failed;
+ bookmarkedSwotItems = [];
+ } finally {
+ _swotItemsLoading = false;
+ notifyListeners();
}
- appState = AppState.failed;
}
void onMarkChanged(int id, bool value) {
- bookmarks.removeWhere((element) => element.id == id);
+
+ if (type != 8) {
+ bookmarks.removeWhere((element) => element.id == id);
+ notifyListeners();
+ }
+ }
+
+ void onSwotMarkChanged(int id) {
+ bookmarkedSwotItems.removeWhere((element) => element.id == id);
notifyListeners();
}
+
void onCommentsChanged(int id, int value) {
- bookmarks.firstWhere((radar) => radar.id == id).comments = value;
- notifyListeners();
+
+ if (type != 8) {
+ final item = bookmarks.firstWhere((radar) => radar.id == id, orElse: () => OverviewData(id: -1, title: '', image: '', description: '', createdAt: '', type: '', marked: false, liked: false, likes: 0, comments: 0, forManagers: false) );
+ if(item.id != -1) {
+ item.comments = value;
+ notifyListeners();
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart
index 5bc2378..586764a 100644
--- a/lib/views/home/home.dart
+++ b/lib/views/home/home.dart
@@ -1,264 +1,264 @@
-// ignore_for_file: deprecated_member_use
+ // ignore_for_file: deprecated_member_use
-import 'package:didvan/config/design_config.dart';
-import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/constants/app_icons.dart';
-import 'package:didvan/main.dart';
-import 'package:didvan/models/notification_message.dart';
-import 'package:didvan/models/view/action_sheet_data.dart';
-import 'package:didvan/providers/theme.dart';
-import 'package:didvan/routes/routes.dart';
-import 'package:didvan/services/app_initalizer.dart';
-import 'package:didvan/utils/action_sheet.dart';
-import 'package:didvan/views/ai/ai.dart';
-import 'package:didvan/views/ai/ai_state.dart';
-import 'package:didvan/views/ai/history_ai_chat_state.dart';
-import 'package:didvan/views/ai/widgets/hoshan_drawer.dart';
-import 'package:didvan/views/home/categories/categories_page.dart';
-import 'package:didvan/views/home/main/main_page.dart';
-import 'package:didvan/views/home/home_state.dart';
-import 'package:didvan/views/home/new_statistic/new_statistic.dart';
-import 'package:didvan/views/home/search/search.dart';
-import 'package:didvan/views/widgets/didvan/text.dart';
-import 'package:didvan/views/widgets/hoshan_app_bar.dart';
-import 'package:didvan/views/widgets/ink_wrapper.dart';
-import 'package:didvan/views/widgets/logo_app_bar.dart';
-import 'package:didvan/views/widgets/didvan/bnb.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:provider/provider.dart';
-import '../../services/app_home_widget/home_widget_repository.dart';
+ import 'package:didvan/config/design_config.dart';
+ import 'package:didvan/config/theme_data.dart';
+ import 'package:didvan/constants/app_icons.dart';
+ import 'package:didvan/main.dart';
+ import 'package:didvan/models/notification_message.dart';
+ import 'package:didvan/models/view/action_sheet_data.dart';
+ import 'package:didvan/providers/theme.dart';
+ import 'package:didvan/routes/routes.dart';
+ import 'package:didvan/services/app_initalizer.dart';
+ import 'package:didvan/utils/action_sheet.dart';
+ import 'package:didvan/views/ai/ai.dart';
+ import 'package:didvan/views/ai/ai_state.dart';
+ import 'package:didvan/views/ai/history_ai_chat_state.dart';
+ import 'package:didvan/views/ai/widgets/hoshan_drawer.dart';
+ import 'package:didvan/views/home/categories/categories_page.dart';
+ import 'package:didvan/views/home/main/main_page.dart';
+ import 'package:didvan/views/home/home_state.dart';
+ import 'package:didvan/views/home/new_statistic/new_statistic.dart';
+ import 'package:didvan/views/home/search/search.dart';
+ import 'package:didvan/views/widgets/didvan/text.dart';
+ import 'package:didvan/views/widgets/hoshan_app_bar.dart';
+ import 'package:didvan/views/widgets/ink_wrapper.dart';
+ import 'package:didvan/views/widgets/logo_app_bar.dart';
+ import 'package:didvan/views/widgets/didvan/bnb.dart';
+ import 'package:flutter/foundation.dart';
+ import 'package:flutter/material.dart';
+ import 'package:flutter/services.dart';
+ import 'package:provider/provider.dart';
+ import '../../services/app_home_widget/home_widget_repository.dart';
-final GlobalKey homeScaffKey = GlobalKey();
+ final GlobalKey homeScaffKey = GlobalKey();
-class Home extends StatefulWidget {
- final bool? showDialogs;
- const Home({Key? key, this.showDialogs}) : super(key: key);
+ class Home extends StatefulWidget {
+ final bool? showDialogs;
+ const Home({Key? key, this.showDialogs}) : super(key: key);
- @override
- State createState() => _HomeState();
-}
-
-class _HomeState extends State
- with SingleTickerProviderStateMixin, WidgetsBindingObserver {
- late final TabController _tabController;
-
- Future _showDialog(BuildContext context) async {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ActionSheetUtils(context)
- .openDialog(
- data: ActionSheetData(
- content: const DidvanText(
- 'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.',
- ),
- onConfirmed: () {
- Future.delayed(
- Duration.zero,
- () => Navigator.of(context)
- .pushNamed(Routes.authenticaion, arguments: true),
- );
- },
- isBackgroundDropBlur: false,
- confrimTitle: 'تغییر رمز عبور',
- dismissTitle: 'بعدا',
- ),
- )
- .then((value) => ActionSheetUtils(context).openDialog(
- data: ActionSheetData(
- backgroundColor: Theme.of(context).colorScheme.background,
- isBackgroundDropBlur: true,
- content: Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- InkWrapper(
- onPressed: () {
- Future.delayed(
- Duration.zero,
- () => Navigator.of(context).pop(),
- );
- },
- child: const Icon(
- DidvanIcons.close_solid,
- size: 24,
- ),
- ),
- DidvanText(
- 'شخصی سازی محتوا',
- style: Theme.of(context).textTheme.displaySmall,
- color: Theme.of(context).colorScheme.text,
- ),
- const InkWrapper(
- child: Icon(
- DidvanIcons.close_regular,
- size: 24,
- color: Colors.transparent,
- ),
- ),
- ],
- ),
- const SizedBox(
- height: 12,
- ),
- const DidvanText(
- "کاربر گرامی\nلطفا جهت شخصیسازی و استفاده بهتر از برنامه، دستهبندیهای مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
- ],
- ),
- // hasDismissButton: false,
- onConfirmed: () {
- Future.delayed(
- Duration.zero,
- () =>
- Navigator.of(navigatorKey.currentContext!).pushNamed(
- Routes.favouritesStep,
- arguments: {"toTimer": true},
- ),
- );
- },
- confrimTitle: 'تایید',
- ),
- ));
- });
+ @override
+ State createState() => _HomeState();
}
- @override
- void initState() {
- if (widget.showDialogs ?? false) {
- _showDialog(context);
- }
- // if (!kIsWeb) {
- // NotificationService.startListeningNotificationEvents();
- // }
+ class _HomeState extends State
+ with SingleTickerProviderStateMixin, WidgetsBindingObserver {
+ late final TabController _tabController;
- final state = context.read();
- DesignConfig.updateSystemUiOverlayStyle();
- _tabController = TabController(length: 4, vsync: this, initialIndex: 0);
- state.tabController = _tabController;
- _tabController.addListener(() {
- state.currentPageIndex = _tabController.index;
- if (_tabController.index == 2) {
- final state = context.read();
-
- state.getBots();
- }
- });
- if (!kIsWeb) {
- Future.delayed(Duration.zero, () {
- HomeWidgetRepository.fetchWidget();
- HomeWidgetRepository.decideWhereToGo();
- NotificationMessage? data = HomeWidgetRepository.data;
- if (data != null) {
- HomeWidgetRepository.decideWhereToGoNotif();
- }
- AppInitializer.handleCLick(state, _tabController);
+ Future _showDialog(BuildContext context) async {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ActionSheetUtils(context)
+ .openDialog(
+ data: ActionSheetData(
+ content: const DidvanText(
+ 'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.',
+ ),
+ onConfirmed: () {
+ Future.delayed(
+ Duration.zero,
+ () => Navigator.of(context)
+ .pushNamed(Routes.authenticaion, arguments: true),
+ );
+ },
+ isBackgroundDropBlur: false,
+ confrimTitle: 'تغییر رمز عبور',
+ dismissTitle: 'بعدا',
+ ),
+ )
+ .then((value) => ActionSheetUtils(context).openDialog(
+ data: ActionSheetData(
+ backgroundColor: Theme.of(context).colorScheme.background,
+ isBackgroundDropBlur: true,
+ content: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ InkWrapper(
+ onPressed: () {
+ Future.delayed(
+ Duration.zero,
+ () => Navigator.of(context).pop(),
+ );
+ },
+ child: const Icon(
+ DidvanIcons.close_solid,
+ size: 24,
+ ),
+ ),
+ DidvanText(
+ 'شخصی سازی محتوا',
+ style: Theme.of(context).textTheme.displaySmall,
+ color: Theme.of(context).colorScheme.text,
+ ),
+ const InkWrapper(
+ child: Icon(
+ DidvanIcons.close_regular,
+ size: 24,
+ color: Colors.transparent,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ const DidvanText(
+ "کاربر گرامی\nلطفا جهت شخصیسازی و استفاده بهتر از برنامه، دستهبندیهای مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
+ ],
+ ),
+ // hasDismissButton: false,
+ onConfirmed: () {
+ Future.delayed(
+ Duration.zero,
+ () =>
+ Navigator.of(navigatorKey.currentContext!).pushNamed(
+ Routes.favouritesStep,
+ arguments: {"toTimer": true},
+ ),
+ );
+ },
+ confrimTitle: 'تایید',
+ ),
+ ));
});
}
- state.refresh();
- context.read().addListener(() {
- state.refresh();
- });
- super.initState();
- }
+ @override
+ void initState() {
+ if (widget.showDialogs ?? false) {
+ _showDialog(context);
+ }
+ // if (!kIsWeb) {
+ // NotificationService.startListeningNotificationEvents();
+ // }
- PreferredSizeWidget getAppBar() {
- PreferredSizeWidget result = const LogoAppBar();
- if (context.watch().tabController.index == 2) {
- result = HoshanAppBar(
- onBack: () {
- final state = context.read();
- if (state.page == 1) {
- state.goToAi();
+ final state = context.read();
+ DesignConfig.updateSystemUiOverlayStyle();
+ _tabController = TabController(length: 4, vsync: this, initialIndex: 0);
+ state.tabController = _tabController;
+ _tabController.addListener(() {
+ state.currentPageIndex = _tabController.index;
+ if (_tabController.index == 2) {
+ final state = context.read();
+
+ state.getBots();
+ }
+ });
+ if (!kIsWeb) {
+ Future.delayed(Duration.zero, () {
+ HomeWidgetRepository.fetchWidget();
+ HomeWidgetRepository.decideWhereToGo();
+ NotificationMessage? data = HomeWidgetRepository.data;
+ if (data != null) {
+ HomeWidgetRepository.decideWhereToGoNotif();
}
- },
- );
+ AppInitializer.handleCLick(state, _tabController);
+ });
+ }
+ state.refresh();
+ context.read().addListener(() {
+ state.refresh();
+ });
+
+ super.initState();
}
- return result;
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- key: homeScaffKey,
- appBar: getAppBar(),
- resizeToAvoidBottomInset: false,
- drawer: context.watch().tabController.index == 2
- ? HoshanDrawer(
- scaffKey: homeScaffKey,
- )
- : null,
- body: WillPopScope(
- onWillPop: () async {
- if (context.read().tabController.index == 0) {
- if (kIsWeb) {
- return true;
+ PreferredSizeWidget getAppBar() {
+ PreferredSizeWidget result = const LogoAppBar();
+ if (context.watch().tabController.index == 2) {
+ result = HoshanAppBar(
+ onBack: () {
+ final state = context.read();
+ if (state.page == 1) {
+ state.goToAi();
}
- ActionSheetUtils(context).openDialog(
- data: ActionSheetData(
- content: const DidvanText(
- 'آیا قصد خروج از برنامه را دارید؟',
- ),
- onConfirmed: () {
- SystemChannels.platform.invokeMethod('SystemNavigator.pop');
- },
- isBackgroundDropBlur: true,
- confrimTitle: 'بله',
- dismissTitle: 'خیر',
- ));
- } else if (context.read().tabController.index == 2) {
- switch (context.read().page) {
- case 1:
- context.read().goToAi();
- break;
+ },
+ );
+ }
- default:
- _tabController.animateTo(0);
+ return result;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ key: homeScaffKey,
+ appBar: getAppBar(),
+ resizeToAvoidBottomInset: false,
+ drawer: context.watch().tabController.index == 2
+ ? HoshanDrawer(
+ scaffKey: homeScaffKey,
+ )
+ : null,
+ body: WillPopScope(
+ onWillPop: () async {
+ if (context.read().tabController.index == 0) {
+ if (kIsWeb) {
+ return true;
+ }
+ ActionSheetUtils(context).openDialog(
+ data: ActionSheetData(
+ content: const DidvanText(
+ 'آیا قصد خروج از برنامه را دارید؟',
+ ),
+ onConfirmed: () {
+ SystemChannels.platform.invokeMethod('SystemNavigator.pop');
+ },
+ isBackgroundDropBlur: true,
+ confrimTitle: 'بله',
+ dismissTitle: 'خیر',
+ ));
+ } else if (context.read().tabController.index == 2) {
+ switch (context.read().page) {
+ case 1:
+ context.read().goToAi();
+ break;
+
+ default:
+ _tabController.animateTo(0);
+ }
+ } else {
+ _tabController.animateTo(0);
}
- } else {
- _tabController.animateTo(0);
- }
- return false;
- },
- child: Consumer(
- builder: (context, state, child) => AnimatedCrossFade(
- duration: DesignConfig.lowAnimationDuration,
- crossFadeState: state.filtering
- ? CrossFadeState.showSecond
- : CrossFadeState.showFirst,
- firstChild: SizedBox(
- height: MediaQuery.of(context).size.height,
- width: MediaQuery.of(context).size.width,
- child: TabBarView(
- physics: const NeverScrollableScrollPhysics(),
- controller: _tabController,
- children: const [
- MainPage(),
- CategoriesPage(),
- Ai(),
- NewStatistic(),
- //Statistic(),
- // Bookmarks(),
- ],
+ return false;
+ },
+ child: Consumer(
+ builder: (context, state, child) => AnimatedCrossFade(
+ duration: DesignConfig.lowAnimationDuration,
+ crossFadeState: state.filtering
+ ? CrossFadeState.showSecond
+ : CrossFadeState.showFirst,
+ firstChild: SizedBox(
+ height: MediaQuery.of(context).size.height,
+ width: MediaQuery.of(context).size.width,
+ child: TabBarView(
+ physics: const NeverScrollableScrollPhysics(),
+ controller: _tabController,
+ children: const [
+ MainPage(),
+ CategoriesPage(),
+ Ai(),
+ NewStatistic(),
+ //Statistic(),
+ // Bookmarks(),
+ ],
+ ),
),
+ secondChild: const SearchPage(),
),
- secondChild: const SearchPage(),
),
),
- ),
- bottomNavigationBar: Consumer(
- builder: (context, state, child) => DidvanBNB(
- currentTabIndex: state.currentPageIndex,
- onTabChanged: (index) {
- state.currentPageIndex = index;
- FocusScope.of(context).unfocus();
- state.resetFilters(false);
- _tabController.animateTo(index);
- },
+ bottomNavigationBar: Consumer(
+ builder: (context, state, child) => DidvanBNB(
+ currentTabIndex: state.currentPageIndex,
+ onTabChanged: (index) {
+ state.currentPageIndex = index;
+ FocusScope.of(context).unfocus();
+ state.resetFilters(false);
+ _tabController.animateTo(index);
+ },
+ ),
),
- ),
- );
+ );
+ }
}
-}
diff --git a/lib/views/home/home_state.dart b/lib/views/home/home_state.dart
index 7c17260..dac6a4f 100644
--- a/lib/views/home/home_state.dart
+++ b/lib/views/home/home_state.dart
@@ -15,7 +15,7 @@ class MenuItemType {
final String asset;
final String link;
- MenuItemType({
+ MenuItemType({
required this.label,
required this.asset,
required this.link,
@@ -202,6 +202,11 @@ class HomeState extends CoreProvier {
asset: Assets.ai,
link: 'tab-2',
),
+ MenuItemType(
+ label: 'فرصت و تهدید',
+ asset: Assets.hugeideas,
+ link: 'http://opportunity-threat.didvan.com/?accessToken=${RequestService.token}',
+ ),
];
categories = [
diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart
index e289355..f59757d 100644
--- a/lib/views/home/main/main_page.dart
+++ b/lib/views/home/main/main_page.dart
@@ -1,6 +1,9 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/main.dart';
import 'package:didvan/models/home_page_content/home_page_list.dart';
+import 'package:didvan/models/home_page_content/swot.dart';
+import 'package:didvan/services/swot_service.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/views/home/main/main_page_state.dart';
@@ -12,13 +15,16 @@ import 'package:didvan/views/widgets/didvan/slider.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
-// import 'package:url_launcher/url_launcher_string.dart';
import 'package:didvan/services/network/request.dart';
import 'package:url_launcher/url_launcher_string.dart';
+import 'package:didvan/views/home/main/widgets/swot_item_card.dart';
class MainPage extends StatefulWidget {
- const MainPage({super.key});
+ const MainPage({
+ super.key,
+ });
@override
State createState() => _MainPageState();
@@ -36,66 +42,163 @@ class _MainPageState extends State {
return StateHandler(
onRetry: context.read().init,
state: context.watch(),
- builder: (context, state) => ListView.builder(
+ builder: (context, state) => ListView(
padding: const EdgeInsets.symmetric(vertical: 16),
- itemBuilder: (context, index) {
- if (index == 0) {
- return const MainPageMainContent();
- }
- index--;
- if (index == 4) {
- return Padding(
- padding: const EdgeInsets.only(top: 32),
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.only(
- left: 16,
- right: 16,
- bottom: 16,
- top: 28,
+ children: [
+ // محتوای اصلی صفحه
+ const MainPageMainContent(),
+
+ // آیتمهای لیستها
+ ...List.generate(state.content.lists.length + 1, (index) {
+ if (index == 4) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 32),
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 16,
+ right: 16,
+ bottom: 16,
+ top: 28,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const InfoTitle(),
+ GestureDetector(
+ onTap: () => {
+ Navigator.of(context).pushNamed(Routes.infography)
+ },
+ child: Row(
+ children: [
+ DidvanText(
+ "همه",
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ Icon(
+ DidvanIcons.angle_left_light,
+ color: Theme.of(context).colorScheme.primary,
+ )
+ ],
+ ),
+ )
+ ],
+ ),
),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- const InfoTitle(),
- GestureDetector(
- onTap: () => {
- Navigator.of(context).pushNamed(Routes.infography)
- },
- child: Row(
+ const MainPageBanner(
+ isFirst: false,
+ ),
+ ],
+ ),
+ );
+ }
+
+ int listIndex = index > 4 ? index - 1 : index;
+ if (listIndex >= state.content.lists.length) {
+ return const SizedBox.shrink();
+ }
+ return _MainPageSection(
+ list: state.content.lists[listIndex],
+ isLast: listIndex == state.content.lists.length - 1,
+ );
+ }),
+
+ // کانتینر زیر کل لیستها
+ FutureBuilder>(
+ future: SwotService.fetchSwotItems(),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const SizedBox(
+ height: 10,
+ );
+ } else if (snapshot.hasError) {
+ return const SizedBox(
+ height: 10,
+ );
+ } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
+ return const SizedBox(
+ height: 10,
+ );
+ }
+
+ final items = snapshot.data!;
+
+ return Padding(
+ padding: const EdgeInsets.all(0.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ /// Title Row
+ Padding(
+ padding: const EdgeInsets.only(right: 20, top: 30),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
children: [
- DidvanText(
- "همه",
- color: Theme.of(context).colorScheme.primary,
+ SvgPicture.asset(
+ "lib/assets/images/features/Saha Solid.svg",
+ ),
+ const SizedBox(width: 5),
+ DidvanText(
+ "ماژول فرصت و تهدید",
+ style: Theme.of(context).textTheme.titleMedium,
+ color: Theme.of(context).colorScheme.title,
),
- Icon(
- DidvanIcons.angle_left_light,
- color: Theme.of(context).colorScheme.primary,
- )
],
),
- )
- ],
+ GestureDetector(
+ onTap: () {
+ AppInitializer.openWebLink(
+ navigatorKey.currentContext!,
+ 'http://opportunity-threat.didvan.com/?accessToken=${RequestService.token}',
+ mode: LaunchMode.inAppWebView,
+ );
+ },
+ child: Padding(
+ padding: const EdgeInsets.only(left: 20),
+ child: Row(
+ children: [
+ DidvanText(
+ "همه",
+ color:
+ Theme.of(context).colorScheme.primary,
+ ),
+ Icon(
+ DidvanIcons.angle_left_light,
+ color:
+ Theme.of(context).colorScheme.primary,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
),
- ),
- const MainPageBanner(
- isFirst: false,
- ),
- ],
- ),
- );
- }
- if (index > 3) {
- index--;
- }
- final list = state.content.lists[index];
- return _MainPageSection(
- list: list,
- isLast: index == state.content.lists.length - 1,
- );
- },
- itemCount: state.content.lists.length + 2,
+
+ const SizedBox(height: 16),
+
+ /// Swot Items Slider
+ DidvanSlider(
+ height: 300,
+ itemCount: items.length,
+ viewportFraction: 0.65,
+ itemBuilder: (context, index, realIndex) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 0.0),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SwotItemCard(item: items[index]),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ )
+ ],
),
);
}
@@ -123,6 +226,7 @@ class InfoTitle extends StatelessWidget {
class _MainPageSection extends StatelessWidget {
final MainPageList list;
final bool isLast;
+
const _MainPageSection({required this.list, required this.isLast});
void _moreHandler(BuildContext context) {
@@ -175,6 +279,11 @@ class _MainPageSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final icon = _generateIcon();
+
+ if (list.contents.isEmpty) {
+ return const SizedBox();
+ }
+
return Column(
children: [
Padding(
diff --git a/lib/views/home/main/widgets/bookmark.dart b/lib/views/home/main/widgets/bookmark.dart
new file mode 100644
index 0000000..4819a47
--- /dev/null
+++ b/lib/views/home/main/widgets/bookmark.dart
@@ -0,0 +1,62 @@
+import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/views/home/bookmarks/bookmark_service.dart';
+import 'package:flutter/material.dart';
+
+class BookmarkIcon extends StatefulWidget {
+ final int postId;
+
+ const BookmarkIcon({super.key, required this.postId});
+
+ @override
+ State createState() => _BookmarkIconState();
+}
+
+class _BookmarkIconState extends State {
+ bool _bookmarked = false;
+ bool _loading = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _loadStatus();
+ }
+
+ Future _loadStatus() async {
+ final bookmarks = await BookmarkService.fetchBookmarks();
+ setState(() => _bookmarked = bookmarks.contains(widget.postId));
+ }
+
+ Future _toggleBookmark() async {
+ setState(() => _loading = true);
+ try {
+ if (_bookmarked) {
+ await BookmarkService.removeBookmark(widget.postId);
+ print("Bookmark removed for post ${widget.postId}");
+ } else {
+ await BookmarkService.addBookmark(widget.postId);
+ print("Bookmark added for post ${widget.postId}");
+ }
+ setState(() {
+ _bookmarked = !_bookmarked;
+ _loading = false;
+ });
+ } catch (e) {
+ print("Error toggling bookmark for post ${widget.postId}: $e");
+ setState(() => _loading = false);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return IconButton(
+ onPressed: _loading ? null : _toggleBookmark,
+ icon: Icon(
+ _bookmarked ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
+ color: _bookmarked
+ ? Theme.of(context).colorScheme.secondary
+ : Theme.of(context).colorScheme.caption,
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/views/home/main/widgets/bookmarked_icon.dart b/lib/views/home/main/widgets/bookmarked_icon.dart
new file mode 100644
index 0000000..40b021b
--- /dev/null
+++ b/lib/views/home/main/widgets/bookmarked_icon.dart
@@ -0,0 +1,103 @@
+import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/models/view/action_sheet_data.dart'; // Ensure this is used or remove if not
+import 'package:didvan/utils/action_sheet.dart';
+import 'package:didvan/views/home/bookmarks/bookmark_service.dart';
+import 'package:didvan/views/widgets/didvan/text.dart';
+import 'package:flutter/material.dart';
+
+class BookmarkedIcon extends StatefulWidget {
+ final int postId;
+ final void Function(bool isBookmarked)? onBookmarkChanged;
+
+ const BookmarkedIcon({
+ super.key,
+ required this.postId,
+ this.onBookmarkChanged,
+ });
+
+ @override
+ State createState() => _BookmarkedIconState();
+}
+
+class _BookmarkedIconState extends State {
+ bool _bookmarked = false;
+ bool _loading = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _loadStatus();
+ }
+
+ Future _loadStatus() async {
+ // setState(() => _loading = true);
+ final bookmarks = await BookmarkService.fetchBookmarks();
+ if (mounted) {
+ setState(() {
+ _bookmarked = bookmarks.contains(widget.postId);
+ // _loading = false;
+ });
+ }
+ }
+
+ Future _toggleBookmark() async {
+ if (_loading) return;
+
+ if (_bookmarked) {
+ bool confirmAction = false;
+
+ await ActionSheetUtils(context).openDialog(
+ data: ActionSheetData(
+ content: const DidvanText(
+ 'آیا میخواهید این محتوا از نشان شدهها حذف شود؟',
+ ),
+ titleIcon: DidvanIcons.bookmark_regular,
+ titleColor: Theme.of(context).colorScheme.secondary,
+ title: 'تایید عملیات',
+ onConfirmed: () => confirmAction = true,
+ ),
+ );
+
+ if (!confirmAction) {
+ return;
+ }
+ }
+
+ setState(() => _loading = true);
+ try {
+ if (_bookmarked) {
+ await BookmarkService.removeBookmark(widget.postId);
+ } else {
+ await BookmarkService.addBookmark(widget.postId);
+ }
+
+ if (mounted) {
+ final newBookmarkStatus = !_bookmarked;
+ setState(() {
+ _bookmarked = newBookmarkStatus;
+ });
+ widget.onBookmarkChanged?.call(newBookmarkStatus);
+ }
+ } catch (e) {
+ print("Error toggling bookmark for post ${widget.postId}: $e");
+ } finally {
+ if (mounted) {
+ setState(() => _loading = false);
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return IconButton(
+ onPressed: _loading ? null : _toggleBookmark,
+ icon: Icon(
+ _bookmarked ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
+ color: _bookmarked
+ ? const Color.fromARGB(255, 0, 126, 167)
+ : Theme.of(context).colorScheme.caption,
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/views/home/main/widgets/swot_bookmark.dart b/lib/views/home/main/widgets/swot_bookmark.dart
new file mode 100644
index 0000000..240d658
--- /dev/null
+++ b/lib/views/home/main/widgets/swot_bookmark.dart
@@ -0,0 +1,305 @@
+import 'dart:async';
+
+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/services/note_service.dart';
+import 'package:didvan/views/home/main/widgets/bookmarked_icon.dart';
+import 'package:didvan/views/widgets/didvan/text.dart';
+import 'package:didvan/views/widgets/didvan/text_field.dart';
+import 'package:didvan/views/widgets/shimmer_placeholder.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:persian_number_utility/persian_number_utility.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+
+class SwotBookmark extends StatefulWidget {
+ final SwotItem item;
+ final void Function(int postId)? onSwotUnbookmarked;
+
+ const SwotBookmark({
+ super.key,
+ required this.item, this.onSwotUnbookmarked,
+ });
+
+ @override
+ State createState() => _SwotBookmark();
+}
+
+class _SwotBookmark extends State {
+ ValueNotifier myBookmarkStatus = ValueNotifier(true);
+ String noteText = '';
+ String tempNoteText = '';
+ bool isLoading = true;
+ bool hasExistingNote = false; // Track if a note exists
+ Timer? _debounce;
+
+ @override
+ void initState() {
+ super.initState();
+ loadNote();
+ }
+
+ Future loadNote() async {
+ final note = await NoteService.getNoteByPostId(widget.item.id);
+ if (mounted) {
+ setState(() {
+ noteText = note?.content ?? '';
+ tempNoteText = noteText;
+ hasExistingNote = note != null; // Set flag if note exists
+ isLoading = false;
+ });
+ }
+ }
+
+ void saveOrUpdateNote(String content) async {
+ print('Processing note for PostId: ${widget.item.id}, Content: $content');
+ setState(() {
+ isLoading = true;
+ });
+
+ try {
+ bool success;
+ if (hasExistingNote) {
+ // Update existing note
+ success = await NoteService.updateNote(widget.item.id, content);
+ } else {
+ // Save new note
+ success = await NoteService.saveNote(widget.item.id, content);
+ }
+
+ if (success) {
+ await loadNote(); // Reload note from server
+ if (mounted) {
+ setState(() {
+ isLoading = false;
+ });
+
+ }
+ } else {
+ if (mounted) {
+ setState(() {
+ isLoading = false;
+ });
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('خطا در ذخیره یادداشت: سرور پاسخ ناموفق داد')),
+ );
+ }
+ }
+ } catch (e) {
+ if (mounted) {
+ setState(() {
+ isLoading = false;
+ });
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text('خطا در ذخیره یادداشت: $e')),
+ );
+ }
+ }
+ }
+
+ void onNoteChanged(String value) {
+ setState(() {
+ tempNoteText = value;
+ });
+
+ if (_debounce?.isActive ?? false) _debounce!.cancel();
+
+ _debounce = Timer(const Duration(milliseconds: 500), () {
+ saveOrUpdateNote(value);
+ });
+ }
+
+ @override
+ void dispose() {
+ _debounce?.cancel();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ AppInitializer.openWebLink(
+ context,
+ 'http://opportunity-threat.didvan.com/posts/${widget.item.id}/?accessToken=${RequestService.token}',
+ mode: LaunchMode.inAppWebView,
+ );
+ },
+ child: Container(
+ margin: const EdgeInsets.only(bottom: 10, right: 2, left: 2),
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Theme.of(context).cardColor,
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Column(
+ children: [
+ // ... (Row with image and text details for SwotItem) ...
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(right: 8, top: 8),
+ child: Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: CachedNetworkImage(
+ imageUrl: widget.item.imageUrl,
+ width: 80,
+ height: 80,
+ fit: BoxFit.cover,
+ httpHeaders: {
+ 'Authorization': 'Bearer ${RequestService.token}',
+ },
+ imageRenderMethodForWeb:
+ ImageRenderMethodForWeb.HttpGet,
+ placeholder: (context, _) => const ShimmerPlaceholder(
+ width: 100,
+ height: 100,
+ ),
+ errorWidget: (context, url, error) => Container(
+ width: 100,
+ height: 100,
+ color: Theme.of(context)
+ .colorScheme
+ .disabledBackground,
+ child:
+ const Icon(Icons.image_not_supported_outlined),
+ ),
+ ),
+ ),
+ Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 4, horizontal: 8),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.secondary,
+ borderRadius: const BorderRadius.horizontal(
+ left: Radius.circular(10),
+ ),
+ ),
+ child: SvgPicture.asset("lib/assets/images/categories/Vector.svg",height: 14,)
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.item.title,
+ style: Theme.of(context).textTheme.bodyLarge,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(
+ height: 18,
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Icon(
+ DidvanIcons.calendar_day_light,
+ size: 16,
+ ),
+ const SizedBox(width: 4),
+ DidvanText(
+ DateTime.parse(widget.item.createdAt)
+ .toPersianDateStr(),
+ style: Theme.of(context).textTheme.labelSmall,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ // Notes Title Column
+ Column(
+ children: [
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Icon(
+ Icons.edit_outlined,
+ size: 16,
+ color: Theme.of(context).colorScheme.caption,
+ ),
+ const SizedBox(width: 4),
+ DidvanText(
+ 'یادداشتهای من',
+ style: Theme.of(context).textTheme.labelSmall,
+ color: Theme.of(context).colorScheme.caption,
+ ),
+ ],
+ ),
+ ],
+ ),
+ // Row for (Divider + TextField) and BookmarkIcon
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Flexible(
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ ),
+ Flexible(
+ flex: 2,
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.border,
+ ),
+ )
+ ],
+ ),
+ const SizedBox(height: 4),
+ isLoading
+ ? const Padding(
+ padding: EdgeInsets.symmetric(vertical: 12),
+ child: CircularProgressIndicator(strokeWidth: 2),
+ )
+ : DidvanTextField(
+ disableBorders: true,
+ initialValue: tempNoteText,
+ hintText: 'برای اضافه کردن یادداشت لمس کنید.',
+ onChanged: onNoteChanged,
+ isSmall: true,
+ textInputAction: TextInputAction.done,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 8),
+ BookmarkedIcon(
+ postId: widget.item.id,
+ onBookmarkChanged: (isBookmarked) {
+ if (!isBookmarked) {
+ widget.onSwotUnbookmarked?.call(widget.item.id);
+ }
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/views/home/main/widgets/swot_item_card.dart b/lib/views/home/main/widgets/swot_item_card.dart
new file mode 100644
index 0000000..e7bd4c0
--- /dev/null
+++ b/lib/views/home/main/widgets/swot_item_card.dart
@@ -0,0 +1,157 @@
+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/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';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+
+class SwotItemCard extends StatefulWidget {
+ final SwotItem item;
+
+ const SwotItemCard({super.key, required this.item, this.onBookmarkChangedInList});
+ final void Function(int postId, bool isBookmarked)? onBookmarkChangedInList;
+
+ @override
+ State createState() => _SwotItemCardState();
+}
+
+class _SwotItemCardState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ AppInitializer.openWebLink(
+ context,
+ 'http://opportunity-threat.didvan.com/posts/${widget.item.id}/?accessToken=${RequestService.token}',
+ mode: LaunchMode.inAppWebView,
+ );
+ },
+ child: Container(
+ width: 250,
+ height: 50,
+ margin: const EdgeInsets.only(right: 0),
+ padding: const EdgeInsets.all(0),
+ decoration: BoxDecoration(
+ color: Theme.of(context).cardColor,
+ borderRadius: BorderRadius.circular(9),
+ ),
+ 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: 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: 300,
+ height: 150,
+ imageUrl: widget.item.imageUrl,
+ placeholder: (context, _) => const ShimmerPlaceholder(
+ width: 300,
+ height: 150,
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ widget.item.title,
+ style: Theme.of(context).textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ const SizedBox(height: 5),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ 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,
+ ),
+ 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,
+ )
+ ],
+ ),
+ // GestureDetector(
+ // onTap: () {}, child: Icon(DidvanIcons.bookmark_solid)
+ // // Icon(
+ // // widget.item.marked
+ // // ? DidvanIcons.bookmark_solid
+ // // : DidvanIcons.bookmark_regular,
+ // // color: widget.item.marked
+ // // ? Theme.of(context).colorScheme.secondary
+ // // : Theme.of(context).colorScheme.caption,
+ // // ),
+ // ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ child: BookmarkIcon(postId: widget.item.id),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/views/home/widgets/categories.dart b/lib/views/home/widgets/categories.dart
index 26d1474..4ecec20 100644
--- a/lib/views/home/widgets/categories.dart
+++ b/lib/views/home/widgets/categories.dart
@@ -19,6 +19,7 @@ class MainCategories extends StatelessWidget {
'$link?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView,
);
+ print("your goddamn token is :${RequestService.token}");
} else if (link.startsWith('tab-')) {
final state = context.read();
int index = int.parse(link.replaceAll('tab-', ''));
@@ -33,13 +34,14 @@ class MainCategories extends StatelessWidget {
Widget build(BuildContext context) {
final state = context.read();
return Wrap(
- alignment: WrapAlignment.center,
+ alignment: WrapAlignment.start,
children: state.menuItems
.map(
(e) => GestureDetector(
onTap: () => _onTap(e.link, context),
child: SizedBox(
- width: (MediaQuery.of(context).size.width - 40) / 3,
+ width: (MediaQuery.of(context).size.width) / 4,
+ // (MediaQuery.of(context).size.width - 40) / 3,
child: Column(
children: [
Container(
diff --git a/lib/views/home/widgets/riskcard.dart b/lib/views/home/widgets/riskcard.dart
new file mode 100644
index 0000000..28ee1db
--- /dev/null
+++ b/lib/views/home/widgets/riskcard.dart
@@ -0,0 +1,30 @@
+// import 'package:didvan/models/home_page_content/risk.dart';
+// import 'package:flutter/material.dart';
+
+// class RiskCard extends StatelessWidget {
+// final Swot risk;
+
+// const RiskCard({Key? key, required this.risk}) : super(key: key);
+
+// @override
+// Widget build(BuildContext context) {
+// return Container(
+// height: 100,
+// margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
+// padding: const EdgeInsets.all(12),
+// decoration: BoxDecoration(
+// color: Colors.green[100],
+// borderRadius: BorderRadius.circular(12),
+// border: Border.all(color: Colors.green, width: 1),
+// ),
+// child: Text(
+// risk.title,
+// style: const TextStyle(
+// fontSize: 16,
+// fontWeight: FontWeight.bold,
+// color: Colors.black87,
+// ),
+// ),
+// );
+// }
+// }
diff --git a/lib/views/podcasts/studio_details/studio_details.mobile.dart b/lib/views/podcasts/studio_details/studio_details.mobile.dart
index 20aadaf..5240a0a 100644
--- a/lib/views/podcasts/studio_details/studio_details.mobile.dart
+++ b/lib/views/podcasts/studio_details/studio_details.mobile.dart
@@ -2,6 +2,7 @@
import 'package:chewie/chewie.dart';
import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
@@ -25,25 +26,41 @@ class StudioDetails extends StatefulWidget {
}
class _StudioDetailsState extends State {
+ // ignore: unused_field
int _currentlyPlayingId = 0;
late VideoPlayerController _videoPlayerController;
- late final ChewieController _chewieController = ChewieController(
- videoPlayerController: _videoPlayerController,
- customControls: const PrimaryControls(),
- autoPlay: true,
- looping: true,
- aspectRatio: 16 / 9,
- materialProgressColors: ChewieProgressColors(
- playedColor: Theme.of(context).colorScheme.title,
- handleColor: Theme.of(context).colorScheme.title));
+ ChewieController? _chewieController;
+
@override
void initState() {
+ super.initState();
final state = context.read();
state.args = widget.pageData['args'];
Future.delayed(
Duration.zero,
- () => state.getStudioDetails(widget.pageData['id']),
+ () => state.getStudioDetails(widget.pageData['id']).then((_) {
+ if (!mounted) return;
+ _videoPlayerController = VideoPlayerController.network(
+ state.studio.link,
+ );
+ _videoPlayerController.initialize().then((_) {
+ _chewieController = ChewieController(
+ videoPlayerController: _videoPlayerController,
+ customControls: const PrimaryControls(),
+ autoPlay: true,
+ looping: true,
+ aspectRatio: 16 / 9,
+ materialProgressColors: ChewieProgressColors(
+ playedColor: Theme.of(context).colorScheme.title,
+ handleColor: Theme.of(context).colorScheme.title,
+ ),
+ );
+ setState(() {
+ _currentlyPlayingId = state.studio.id;
+ });
+ });
+ }),
);
if (widget.pageData['goToComment'] != null) {
@@ -63,7 +80,6 @@ class _StudioDetailsState extends State {
);
}
}
- super.initState();
}
@override
@@ -80,8 +96,14 @@ class _StudioDetailsState extends State {
}
},
builder: (context, state) {
- if (_currentlyPlayingId != state.studio.id) {
- _handleVideoPlayback(state);
+ if (!state.isStudioLoaded) {
+ return Center(
+ child: Image.asset(
+ Assets.loadingAnimation,
+ width: 100,
+ height: 100,
+ ),
+ );
}
return WillPopScope(
onWillPop: () async {
@@ -120,9 +142,15 @@ class _StudioDetailsState extends State {
children: [
AspectRatio(
aspectRatio: 16 / 9,
- child: Chewie(
- controller: _chewieController,
- ),
+ child: _chewieController != null
+ ? Chewie(controller: _chewieController!)
+ : Center(
+ child: Image.asset(
+ Assets.loadingAnimation,
+ width: 100,
+ height: 100,
+ ),
+ ),
),
Expanded(
child: StudioDetailsWidget(
@@ -142,18 +170,11 @@ class _StudioDetailsState extends State {
);
}
- Future _handleVideoPlayback(state) async {
- _videoPlayerController = VideoPlayerController.network(
- state.studio.link,
- )..initialize().then((value) async {});
- _currentlyPlayingId = state.studio.id;
- }
-
@override
void dispose() {
_videoPlayerController.pause();
_videoPlayerController.dispose();
- _chewieController.dispose();
+ _chewieController?.dispose();
super.dispose();
}
}
diff --git a/lib/views/podcasts/studio_details/studio_details_state.dart b/lib/views/podcasts/studio_details/studio_details_state.dart
index 63b65c6..e337f61 100644
--- a/lib/views/podcasts/studio_details/studio_details_state.dart
+++ b/lib/views/podcasts/studio_details/studio_details_state.dart
@@ -11,6 +11,7 @@ import 'package:didvan/services/network/request_helper.dart';
class StudioDetailsState extends CoreProvier {
late StudioDetailsData studio;
+ bool isStudioLoaded = false;
StudioDetailsData? nextStudio;
StudioDetailsData? prevStudio;
late int initialIndex;
@@ -66,6 +67,7 @@ class StudioDetailsState extends CoreProvier {
studio = prevStudio!;
prevStudio = null;
}
+ isStudioLoaded = true;
notifyListeners();
_handlePodcastPlayback(studio);
}
@@ -98,9 +100,11 @@ class StudioDetailsState extends CoreProvier {
}
final result = service.result;
studio = StudioDetailsData.fromJson(result['studio']);
+ isStudioLoaded = true;
if (args?.page == 0) {
initialIndex = 0;
appState = AppState.idle;
+ notifyListeners();
return;
}
if (result['nextStudio'].isNotEmpty && this.args?.page != 0) {
@@ -114,6 +118,7 @@ class StudioDetailsState extends CoreProvier {
}
alongSideState = AppState.idle;
appState = AppState.idle;
+ notifyListeners();
return;
}
if (isForward == null) {
@@ -123,10 +128,8 @@ class StudioDetailsState extends CoreProvier {
notifyListeners();
}
} catch (e) {
- // MediaService.resetAudioPlayer();
update();
appState = AppState.idle;
- // rethrow;
}
}
@@ -165,7 +168,6 @@ class StudioDetailsState extends CoreProvier {
}
} else {
MediaService.audioPlayer.pause();
- // MediaService.audioPlayer.dispose();
}
}
@@ -224,4 +226,4 @@ class StudioDetailsState extends CoreProvier {
_trackingTimer?.cancel();
super.dispose();
}
-}
+}
\ No newline at end of file
diff --git a/lib/views/widgets/audio/player_navbar.dart b/lib/views/widgets/audio/player_navbar.dart
index 22bea50..223ca5b 100644
--- a/lib/views/widgets/audio/player_navbar.dart
+++ b/lib/views/widgets/audio/player_navbar.dart
@@ -39,24 +39,26 @@ class _PlayerNavBarState extends State {
return StreamBuilder(
stream: MediaService.audioPlayer.playingStream,
builder: (context, isPlaying) => GestureDetector(
- onTap: () => (MediaService.currentPodcast == null &&
- (MediaService.audioPlayerTag ?? '')
- .split('-')[1]
- .isNotEmpty) ||
- MediaService.currentPodcast?.description == 'radar'
- ? Navigator.of(context).pushNamed(
- Routes.radarDetails,
- arguments: {
- 'onMarkChanged': (id, value) {},
- 'onCommentsChanged': (id, value) {},
- 'id': MediaService.currentPodcast?.id,
- 'args': const RadarRequestArgs(page: 0),
- 'hasUnmarkConfirmation': false,
- },
- )
- : (MediaService.audioPlayerTag ?? '').split('-')[1].isNotEmpty
- ? _showPlayerBottomSheet(context)
- : null,
+ onTap: () {
+ // (MediaService.currentPodcast == null &&
+ // (MediaService.audioPlayerTag ?? '')
+ // .split('-')[1]
+ // .isNotEmpty) ||
+ // MediaService.currentPodcast?.description == 'radar'
+ // ? Navigator.of(context).pushNamed(
+ // Routes.radarDetails,
+ // arguments: {
+ // 'onMarkChanged': (id, value) {},
+ // 'onCommentsChanged': (id, value) {},
+ // 'id': MediaService.currentPodcast?.id,
+ // 'args': const RadarRequestArgs(page: 0),
+ // 'hasUnmarkConfirmation': false,
+ // },
+ // )
+ // : (MediaService.audioPlayerTag ?? '').split('-')[1].isNotEmpty
+ // ? _showPlayerBottomSheet(context)
+ // : null;
+ },
child: Consumer(
builder: (context, state, child) => AnimatedContainer(
height: widget.inHome
diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart
index d64fdd7..ca06b25 100644
--- a/lib/views/widgets/didvan/scaffold.dart
+++ b/lib/views/widgets/didvan/scaffold.dart
@@ -107,7 +107,7 @@ class _DidvanScaffoldState extends State {
if (widget.slivers != null)
for (var i = 0; i < widget.slivers!.length; i++)
SliverPadding(
- padding: widget.padding,
+ padding: widget.padding.copyWith(bottom: 0),
sliver: widget.slivers![i],
),
if (widget.children != null && widget.showSliversFirst)
diff --git a/lib/views/widgets/didvan/text_field.dart b/lib/views/widgets/didvan/text_field.dart
index 878f865..632e7e2 100644
--- a/lib/views/widgets/didvan/text_field.dart
+++ b/lib/views/widgets/didvan/text_field.dart
@@ -20,6 +20,7 @@ class DidvanTextField extends StatefulWidget {
final bool acceptSpace;
final String? Function(String value)? validator;
final TextInputType? textInputType;
+ final TextInputAction? textInputAction; // پارامتر جدید
final bool disableBorders;
final bool isSmall;
final int? maxLine;
@@ -37,6 +38,7 @@ class DidvanTextField extends StatefulWidget {
this.initialValue,
this.validator,
this.textInputType,
+ this.textInputAction, // اضافه کردن به سازنده
this.textAlign,
this.obsecureText = false,
this.autoFocus = false,
@@ -115,6 +117,7 @@ class _DidvanTextFieldState extends State {
obscureText: _hideContent,
textAlign: widget.textAlign ?? TextAlign.start,
keyboardType: widget.textInputType,
+ textInputAction: widget.textInputAction, // پاس دادن به TextFormField
focusNode: _focusNode,
controller: _controller,
onFieldSubmitted: widget.onSubmitted,
@@ -235,7 +238,6 @@ class _DidvanTextFieldState extends State {
setState(() {
_error = null;
});
- // value = value.toEnglishDigit();
widget.onChanged?.call(value);
}
@@ -259,6 +261,7 @@ class _DidvanTextFieldState extends State {
@override
void dispose() {
_controller.dispose();
+ _focusNode.dispose();
super.dispose();
}
-}
+}
\ No newline at end of file
diff --git a/lib/views/widgets/menu_item.dart b/lib/views/widgets/menu_item.dart
index a258a17..78c0d30 100644
--- a/lib/views/widgets/menu_item.dart
+++ b/lib/views/widgets/menu_item.dart
@@ -7,16 +7,19 @@ class MenuOption extends StatelessWidget {
final String? title;
final Widget? titleWidget;
final IconData? icon;
+ final Widget? iconWidget;
final double iconSize;
final String? suffix;
final VoidCallback onTap;
final Widget? trailing;
final Color? color;
+
MenuOption({
Key? key,
required this.onTap,
this.title,
this.icon,
+ this.iconWidget,
this.suffix,
this.color,
this.trailing,
@@ -24,50 +27,59 @@ class MenuOption extends StatelessWidget {
this.iconSize = 18,
}) : super(key: key) {
if (title == null && titleWidget == null) {
- throw Exception;
+ throw Exception("MenuOption must have a title or titleWidget");
}
}
@override
Widget build(BuildContext context) {
+ final ThemeData theme = Theme.of(context);
+ final Color effectiveTitleColor = color ?? theme.colorScheme.title;
+
+ Widget? displayIcon;
+ if (iconWidget != null) {
+ displayIcon = iconWidget;
+ } else if (icon != null) {
+ displayIcon = Icon(icon, size: iconSize, color: effectiveTitleColor);
+ }
+
return GestureDetector(
onTap: onTap,
child: Container(
color: Colors.transparent,
+ padding: const EdgeInsets.symmetric(vertical: 0.0),
child: Row(
children: [
- if (icon != null)
- Icon(
- icon,
- size: iconSize,
- color: color ?? Theme.of(context).colorScheme.title,
- ),
- if (icon != null) const SizedBox(width: 4),
- if (titleWidget != null) titleWidget!,
- if (titleWidget == null)
- DidvanText(
- title!,
- color: color ?? Theme.of(context).colorScheme.title,
- ),
- const Spacer(),
+ if (displayIcon != null) displayIcon,
+ if (displayIcon != null) const SizedBox(width: 4),
+
+ if (titleWidget != null) titleWidget!
+ else DidvanText(
+ title!,
+ color: effectiveTitleColor,
+ ),
+ const Spacer(),
+
if (suffix != null)
- DidvanText(
- suffix!,
- style: Theme.of(context)
- .textTheme
- .titleSmall!
- .copyWith(fontWeight: FontWeight.w400),
- color: Theme.of(context).colorScheme.primary,
+ Padding(
+ padding: const EdgeInsets.only(left: 8.0),
+ child: DidvanText(
+ suffix!,
+ style: theme.textTheme.titleSmall!
+ .copyWith(fontWeight: FontWeight.w400),
+ color: theme.colorScheme.primary,
+ ),
),
+
trailing ??
Icon(
DidvanIcons.angle_left_regular,
size: 18,
- color: color ?? Theme.of(context).colorScheme.title,
+ color: effectiveTitleColor,
),
],
),
),
);
}
-}
+}
\ No newline at end of file
diff --git a/pubspec.lock b/pubspec.lock
index 644214b..a5052d2 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -422,6 +422,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
+ flutter_inappwebview:
+ dependency: "direct main"
+ description:
+ name: flutter_inappwebview
+ sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.5"
+ flutter_inappwebview_android:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_android
+ sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ flutter_inappwebview_internal_annotations:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_internal_annotations
+ sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ flutter_inappwebview_ios:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_ios
+ sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_macos:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_macos
+ sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_platform_interface
+ sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0+1"
+ flutter_inappwebview_web:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_web
+ sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_windows:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_windows
+ sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.0"
flutter_lints:
dependency: "direct dev"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 957d916..b7a373a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -109,6 +109,7 @@ dependencies:
image_cropper: ^9.0.0
package_info_plus: ^8.3.0
flutter_local_notifications: ^19.1.0
+ flutter_inappwebview: ^6.1.5
# fading_edge_scrollview: ^4.1.1
dev_dependencies:
flutter_test: