diff --git a/FCSC_UAE_Stats.jks b/FCSC_UAE_Stats.jks new file mode 100644 index 00000000..2519f50d Binary files /dev/null and b/FCSC_UAE_Stats.jks differ diff --git a/android/app/build.gradle b/android/app/build.gradle index ec52a38b..374c1b19 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,22 +15,30 @@ if (localPropertiesFile.exists()) { def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "1" + flutterVersionCode = "9" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0" + flutterVersionName = "1.0.8" } +def keystorePropertiesFile = rootProject.file("key.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + android { namespace = "ae.gov.fcsc.frontend" compileSdk = flutter.compileSdkVersion // ndkVersion = flutter.ndkVersion ndkVersion = "25.1.8937393" compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" } defaultConfig { @@ -44,11 +52,22 @@ android { versionName = flutterVersionName } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug + signingConfig signingConfigs.release + minifyEnabled true // Enable code shrinking for smaller APKs + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } diff --git a/android/gradle.properties b/android/gradle.properties index 303728bd..476c2b1c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -org.gradle.java.home=C:/Program Files/Eclipse Adoptium/jdk-21.0.5.11-hotspot +org.gradleorg.gradle.java.home=/usr/lib/jvm/openlogic-openjdk-17.0.13+11-linux-x64 diff --git a/assets/app_tour/down_right.png b/assets/app_tour/down_right.png new file mode 100644 index 00000000..be10a1bb Binary files /dev/null and b/assets/app_tour/down_right.png differ diff --git a/assets/app_tour/leftDown.png b/assets/app_tour/leftDown.png new file mode 100644 index 00000000..4ba2314e Binary files /dev/null and b/assets/app_tour/leftDown.png differ diff --git a/assets/app_tour/leftUp.png b/assets/app_tour/leftUp.png new file mode 100644 index 00000000..56df6adc Binary files /dev/null and b/assets/app_tour/leftUp.png differ diff --git a/assets/app_tour/right_down.png b/assets/app_tour/right_down.png new file mode 100644 index 00000000..324c148c Binary files /dev/null and b/assets/app_tour/right_down.png differ diff --git a/assets/banners/fcsc.png b/assets/banners/fcsc.png index 4de43a5c..03eff4b6 100644 Binary files a/assets/banners/fcsc.png and b/assets/banners/fcsc.png differ diff --git a/assets/icons/drawer/feedback.png b/assets/icons/drawer/feedback.png new file mode 100644 index 00000000..2d190a84 Binary files /dev/null and b/assets/icons/drawer/feedback.png differ diff --git a/assets/icons/drawer/guide.png b/assets/icons/drawer/guide.png new file mode 100644 index 00000000..aa9c8378 Binary files /dev/null and b/assets/icons/drawer/guide.png differ diff --git a/assets/icons/drawer/manageusr.png b/assets/icons/drawer/manageusr.png new file mode 100644 index 00000000..cfcba90b Binary files /dev/null and b/assets/icons/drawer/manageusr.png differ diff --git a/assets/icons/misc/save.png b/assets/icons/misc/save.png new file mode 100644 index 00000000..f9f0b947 Binary files /dev/null and b/assets/icons/misc/save.png differ diff --git a/assets/icons/misc/vector.png b/assets/icons/misc/vector.png new file mode 100644 index 00000000..d3783e75 Binary files /dev/null and b/assets/icons/misc/vector.png differ diff --git a/assets/icons/misc/visibility_off.png b/assets/icons/misc/visibility_off.png new file mode 100644 index 00000000..f01af5ba Binary files /dev/null and b/assets/icons/misc/visibility_off.png differ diff --git a/assets/icons/misc/visible_on.png b/assets/icons/misc/visible_on.png new file mode 100644 index 00000000..b25be6b4 Binary files /dev/null and b/assets/icons/misc/visible_on.png differ diff --git a/assets/icons/uae_numbers/bookmarks.png b/assets/icons/uae_numbers/bookmarks.png new file mode 100644 index 00000000..82d02a7b Binary files /dev/null and b/assets/icons/uae_numbers/bookmarks.png differ diff --git a/assets/icons/uae_numbers/share.png b/assets/icons/uae_numbers/share.png new file mode 100644 index 00000000..0014595d Binary files /dev/null and b/assets/icons/uae_numbers/share.png differ diff --git a/assets/logos/fcsc.png b/assets/logos/fcsc.png index 044f9490..dfbf3987 100644 Binary files a/assets/logos/fcsc.png and b/assets/logos/fcsc.png differ diff --git a/assets/splash_screen/logo.png b/assets/splash_screen/logo.png index 8c3218e8..03eff4b6 100644 Binary files a/assets/splash_screen/logo.png and b/assets/splash_screen/logo.png differ diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 8273d3ab..3bed1c02 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -300,6 +300,7 @@ import 'package:uae_stat/presentation/Screens/charts/chart.dart'; import 'package:uae_stat/presentation/Screens/charts/screens/chart_screen.dart'; import 'package:uae_stat/presentation/Screens/demo_home2.dart'; import 'package:uae_stat/presentation/Screens/online_offline_verification/internet_check.dart'; +import 'package:uae_stat/presentation/routes/auth_routes/pocketbase_service.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/getting_started.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; @@ -316,6 +317,7 @@ import '../presentation/Screens/demo_home.dart'; import '../presentation/Screens/profilepage.dart'; import '../presentation/routes/auth_routes/login_route.dart'; import '../presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/bookmark.dart'; import '../presentation/routes/drawer_routes/Drawer Items/edit_profile.dart'; import '../presentation/routes/drawer_routes/Drawer Items/feedback.dart'; import '../presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart'; @@ -323,17 +325,29 @@ import '../presentation/routes/drawer_routes/Drawer Items/user_guide/features.da import '../presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart'; import '../presentation/routes/drawer_routes/Drawer Items/manage_users.dart'; +final pbService = PocketBaseService(); + +Future getInitialRoute() async { + bool isLoggedIn = await pbService.loadSession(); + return isLoggedIn ? '/myhomepage' : '/login'; +} + final GoRouter router = GoRouter( routes: [ GoRoute( path: '/', //builder: (context, state) => LoginRoute(), - builder: (context, state) => MyHomePage(), + builder: (context, state) => RegisterScreen(), ), + // GoRoute( + // path: '/', + // redirect: (context, state) async { + // return await getInitialRoute(); + // }, + // ), GoRoute( path: '/internetcheck', builder: (context, state) => InternetCheck(), - ), GoRoute( path: '/login', @@ -374,6 +388,7 @@ final GoRouter router = GoRouter( '0xFFFFFFFF'; // Default white final mainTopic = state.uri.queryParameters['mainTopic'] ?? ''; final title = state.uri.queryParameters['title'] ?? ''; + final key = state.uri.queryParameters['key'] ?? ''; print('Router dataSets: $dataSets'); print('Router bgColor: $bgColor'); @@ -386,6 +401,7 @@ final GoRouter router = GoRouter( bgColor: bgColor, mainTopic: mainTopic, title: title, + keyParam: key, ); }, ), @@ -411,7 +427,9 @@ final GoRouter router = GoRouter( builder: (context, state) { final userId = state.pathParameters['userId']!; final email = state.pathParameters['email']!; - return CreateNewPw(userId: userId, email: email); + final key = state.uri.queryParameters['key'] ?? ''; + + return CreateNewPw(userId: userId, email: email, keyParam: key); }, ), GoRoute( @@ -468,6 +486,10 @@ final GoRouter router = GoRouter( path: '/editProfile', builder: (context, state) => EditProfile(), ), + GoRoute( + path: '/bookmark', + builder: (context, state) => BookMark(), + ), GoRoute( path: '/manageuser', builder: (context, state) => ManageUserRouter(), diff --git a/lib/infrastructure/services/InternetConnectivity_Service/InternetConnectivityService.dart b/lib/infrastructure/services/InternetConnectivity_Service/InternetConnectivityService.dart index 54f62dd9..5e1e930c 100644 --- a/lib/infrastructure/services/InternetConnectivity_Service/InternetConnectivityService.dart +++ b/lib/infrastructure/services/InternetConnectivity_Service/InternetConnectivityService.dart @@ -63,36 +63,36 @@ // // -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; - -final connectivityProvider = StateNotifierProvider((ref) { - return ConnectivityNotifier(); -}); - -class ConnectivityNotifier extends StateNotifier { - ConnectivityNotifier() : super(true) { - _checkInitialConnection(); - _listenToConnectivityChanges(); - } - - void _checkInitialConnection() async { - final result = await Connectivity().checkConnectivity(); - state = _hasInternet(result); - } - - void _listenToConnectivityChanges() { - // For mobile platforms, use the connectivity plugin - - // Listen for connectivity changes on web - Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { - state = _hasInternet(result); - }); - } - - - - bool _hasInternet(ConnectivityResult result) { - return result == ConnectivityResult.mobile || result == ConnectivityResult.wifi; - } -} +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:connectivity_plus/connectivity_plus.dart'; +// +// final connectivityProvider = StateNotifierProvider((ref) { +// return ConnectivityNotifier(); +// }); +// +// class ConnectivityNotifier extends StateNotifier { +// ConnectivityNotifier() : super(true) { +// _checkInitialConnection(); +// _listenToConnectivityChanges(); +// } +// +// void _checkInitialConnection() async { +// final result = await Connectivity().checkConnectivity(); +// state = _hasInternet(result); +// } +// +// void _listenToConnectivityChanges() { +// // For mobile platforms, use the connectivity plugin +// +// // Listen for connectivity changes on web +// Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { +// state = _hasInternet(result); +// }); +// } +// +// +// +// bool _hasInternet(ConnectivityResult result) { +// return result == ConnectivityResult.mobile || result == ConnectivityResult.wifi; +// } +// } diff --git a/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart b/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart index 265d41f7..cbd0bc48 100644 --- a/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart +++ b/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart @@ -8,4 +8,7 @@ abstract class DrawerAssetIconPath { static const message = '$_basePath/message.png'; static const scroll = '$_basePath/scroll.png'; static const manageuser = '$_basePath/manageuser.png'; + static const manageUser = '$_basePath/manageusr.png'; + static const guide = '$_basePath/guide.png'; + static const feedback = '$_basePath/feedback.png'; } diff --git a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart index 7b4cebd1..7912f789 100644 --- a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart +++ b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart @@ -6,4 +6,9 @@ abstract class MiscIconAssetPath { static const menu = '$_basePath/menu.png'; static const person = '$_basePath/person.png'; static const back = '$_basePath/back.png'; + static const vector = '$_basePath/vector.png'; + static const visibleOn = '$_basePath/visible_on.png'; + static const visibilityOff = '$_basePath/visibility_off.png'; + static const save = '$_basePath/save.png'; + } diff --git a/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart b/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart new file mode 100644 index 00000000..903aca60 --- /dev/null +++ b/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +abstract class UaeNumbersAssetPath { + static const _basePath = 'assets/icons/uae_numbers'; + static const bookmarksUae = '$_basePath/bookmarks.png'; + static const shareUae = '$_basePath/share.png'; +} \ No newline at end of file diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index e01858b5..8766c03b 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,24 +1,24 @@ { - "feedback_title": "نموذج الملاحظات", - "feedback_failed_msg": "لم يتم إرسال تعليقاتك. يرجى المحاولة مرة أخرى", + "feedback_title": "التعليقات", + "feedback_failed_msg": "لم يتم إرسال تعليقاتك. يرجى المحاولة مرة أخرى.", "guide_title": "نموذج الملاحظات", "register_title": "التسجيل", "register_details": "من فضلك ادخل بياناتك", "register_name": "اسم المستخدم", - "email_id": "الاسم الكامل", + "email_id": "معرف البريد الإلكتروني", "enter_name": "أدخل الاسم", - "register_email": "أدخل بريدك الإلكتروني", - "register_password": "دخل كلمة المرور", - "register_Confirm_password": "أكيد كلمة المرو", - "agree": " وافق على", - "t_and": "و ", - "conditions": " الخاصة بالمركز الاتحادي للتنافسية والإحصاء", - "terms_conditions": "الشروط والأحكام ", - "privacy_policy":" وسياسة الخصوصية", - "account_confirmation": "ل لديك حساب؟ سجل الدخول ", - "login_title": "سجيل الدخول", + "enter_your_email": "أدخل بريدك الإلكتروني", + "enter_your_password": "أدخل كلمة المرور", + "register_Confirm_password": "تأكيد كلمة المرور", + "agree": "أوافق على الشروط والأحكام وسياسة الخصوصية للمركز الإتحادي للتنافسية والإحصاء", + "t_and": "", + "conditions": "", + "terms_conditions": "الشروط والأحكام", + "privacy_policy":"سياسة الخصوصية", + "account_confirmation": "هل لديك حساب؟ سجل الدخول", + "login_title": "تسجيل الدخول", "profile_title": "نموذج الملاحظات", "my_profile": "ملفي الشخصي", @@ -30,8 +30,8 @@ "confirm": "تأكيد", "change_password": "تغيير كلمة المرور", "sure_save_page": " هل أنت متأكد أنك ترغب في حفظ هذه الصفحة؟", - "not_able_to_change": " بمجرد الحفظ، لن تتمكن من تعديل اسمك أو تاريخ ", - "save": "يحفظ", + "not_able_to_change": "بمجرد الحفظ، لن تتمكن من تعديل اسمك أو تاريخ ميلادك.", + "save": "حفظ", "name": "اسم", @@ -55,25 +55,25 @@ "password_match": "كلمات المرور غير متطابقة", "only8charac": "يجب أن تتكون كلمة المرور من 8 أحرف على الأقل", - "manage_user": "دارة المستخدمين", + "manage_user": "إدارة المستخدمين", "user_name": "اسم المستخدم", "reg_date": "تاريخ التسجيل", - "status": "تمت الموافقة", + "status": "الحالة", "denied": "مرفوض", - "pending": "يد الانتظار", + "pending": " قيد الانتظار", "approve_msg": "هل أنت متأكد أنك ترغب في تغيير الحالة إلى \"{newStatus}\"؟", "yes": "نعم", "no": "لا", "no_result_found": "لم يتم العثور على نتائج", "search": "يبحث", - "approved": "موافقة", + "approved": "تمت الموافقة", "create_new_password": "إنشاء كلمة مرور جديدة", - "different_password": "يجب أن تكون كلمة المرور الجديدة مختلفة عن كلمة المرور المستخدمة سابقًا.", + "different_password":"يجب أن تكون كلمة المرور الجديدة مختلفة عن كلمة المرور المستخدمة سابقًا.", "old_password": "كلمة المرور القديمة", "enter_new_password": "أدخل كلمة المرور الجديدة", "confirm_new_password": "تأكيد كلمة المرور الجديدة", - "password_changed_successfully": "م تغيير كلمة المرور بنجاح", + "password_changed_successfully": "تم تغيير كلمة المرور بنجاح", "enter_old_password": "أدخل كلمة المرور القديمة", "your_old_password_incorrect": "كلمة المرور القديمة الخاصة بك غير صحيحة.", "failed_to_update_password": "", @@ -82,5 +82,13 @@ "password_between_8_to_40": "يجب أن تتراوح كلمة المرور بين 8 و64 حرفًا", "new_password_required": "كلمة المرور الجديدة مطلوبة", "old_password_is_required": "كلمة المرور القديمة مطلوبة", - "confirm_password_is_required": "تأكيد كلمة المرور مطلوب" + "confirm_password_is_required": "تأكيد كلمة المرور مطلوب", + + "kpiCards": "يمكنك عرض بطاقات مؤشرات الأداء الرئيسية التفاعلية والرسوم البيانية لاستكشاف اتجاهات البيانات ومقارنة المقاييس الرئيسية بصريًا", + "home_topic": "اختر أي فئة (على سيبل المثال : الاقتصاد، الاجتماعية، البيئة) لاستكشاف مؤشرات الأداء الرئيسية التفصيلية ، التصورات، ورؤى البيانات .", + "economy": "يمكنك الوصول إلى الإحصائيات التفصيلية وصفحات التحليل لمؤشرات الأداء الرئيسية في فئة الاقتصاد لاكتشاف الاتجاهات والمقاييس الرئيسية .", + "bookMark": "يمكنك وضع إشارة مرجعية على مؤشرات الأداء الرئيسية أو مجموعات البيانات المهمة للوصول السريع والسهل في أي وقت من خلال قسم الإشارات المرجعية. ", + "toggle": "يمكنك التبديل بين اللغتين الإنجليزية والعربية باستخدام مفتاح تبديل اللغة في الزاوية اليمنى العليا للحصول على تجربة ثنائية اللغة.", + "tour_navBar": "يمكنك استخدام شريط التنقل للوصول السريع إلى الملف الشخصي ,التعليقات , دليل المستخدم , الإشعارات والميزات الأساسية الأخرى للتطبيق.", + "mainMenu": "يمكنك الوصول إلى الصفحات المخصصة والميزات الإضافية عن طريق تحديد الخيارات من القائمة الرئيسية." } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 5b58e7ce..f99002cb 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -8,17 +8,19 @@ "register_details": "Please enter your details", "register_name": "Username", "email_id": "Email ID", - "enter_name": "Enter your User Name", - "register_email": "Enter your email", - "register_password": "Enter your password", - "register_Confirm_password": "Confirm password", - "agree": "I agree to ", - "t_and": "and", - "conditions": " of Fcsc", - "terms_conditions": "terms & conditions", - "privacy_policy":"privacy policy", - "account_confirmation": "Already have an account? Login", - "login_title": "Login", + "enter_name": "Enter your User Name", + "enter_your_email": "Enter your email", + "enter_your_password": "Enter your password", + "register_Confirm_password": "Confirm password", + "agree": "I agree to the ", + "t_and": " and ", + "conditions": " of FCSC", + "terms_conditions": "Terms & Conditions", + "privacy_policy":"Privacy Policy", + "account_confirmation": "Already have an account?", + "login_title": "Login", + + "profile_title": "Edit Profile", "my_profile": "My Profile", @@ -83,5 +85,13 @@ "password_between_8_to_40": "Password must be between 8 and 64 characters", "new_password_required": "New password is required", "old_password_is_required": "Old password is required", - "confirm_password_is_required": "Confirm password is required" + "confirm_password_is_required": "Confirm password is required", + + "kpiCards": "View interactive KPI cards and graphs to explore data trends and compare key metrics visually.", + "home_topic": "Tap on any category (e.g., Economy, Social, Environment) to explore detailed KPIs, visualizations, and data insights.", + "economy": "Access detailed statistics and drill-down pages for KPIs in the Economy category to uncover key trends and metrics.", + "bookMark": "Bookmark important KPIs or datasets for quick and easy access anytime through the Bookmarks section.", + "toggle": "Switch between English and Arabic using the language toggle at the top-right corner for a bilingual experience.", + "tour_navBar": "Access dedicated pages and additional features by selecting options from the Main Menu.", + "mainMenu": "Use the navigation bar to quickly access Profile, Feedback, User Guide, Notifications, and other key app features." } diff --git a/lib/main.dart b/lib/main.dart index 44b819a5..d58dbe2d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -106,13 +106,13 @@ class MainApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final locale = ref.watch(localeProvider); - ref.listen(connectivityProvider, (previous, hasInternet) { - print('Previous Internet Status: $previous'); - print('Current Internet Status: $hasInternet'); - if (previous != null && hasInternet != previous) { - handleConnectivityChange(context, hasInternet); - } - }); + // ref.listen(connectivityProvider, (previous, hasInternet) { + // print('Previous Internet Status: $previous'); + // print('Current Internet Status: $hasInternet'); + // if (previous != null && hasInternet != previous) { + // handleConnectivityChange(context, hasInternet); + // } + // }); return MaterialApp.router( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart index 0a9a7e50..c4541382 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart @@ -1,12 +1,626 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import 'package:uae_stat/presentation/components/constant/constant.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; - -class Marriages extends StatelessWidget { +class Marriages extends ConsumerStatefulWidget { const Marriages({super.key}); + @override + ConsumerState createState() => _MarriagesState(); +} + +class _MarriagesState extends ConsumerState { + late List marriageTargets; + late List previousMarriageTargets; + late TutorialCoachMark tutorialCoachMark; + final GlobalKey chartKey = GlobalKey(); + final GlobalKey bookMarkKey = GlobalKey(); + late double cardHeight= 0; + + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _calculateMarkPosition(); + _startTutorialAfterRender(); + }); + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + context.go('/myhomepage'); + } + + void _kpiTutorialCoachMark() { + + + _initmarriageTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: marriageTargets, + onClickTarget: (target) { + if (target.identify == 'chartKey') { + // Scroll to the long widget + Scrollable.ensureVisible( + chartKey.currentContext!, + duration: Duration(milliseconds: 500), + ); + } + }, + onFinish: () { + context.push('/myhomepage'); + debugPrint('Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + void _previousKpiTutorial() { + + _initpreviousTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousMarriageTargets, + onFinish: () { + context.push('/myhomepage'); + debugPrint('Previous Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + + + void _startTutorialAfterRender() { + final chartTour = ref.watch(chartsTourProvider); + final previousChartTour = ref.watch(previousChartsTourProvider); + if (chartKey.currentContext != null && !chartTour) { + _kpiTutorialCoachMark(); + } else if(!previousChartTour) { + _previousKpiTutorial(); + } + else if (!chartTour || !previousChartTour){ + Future.delayed(Duration(milliseconds: 100), _startTutorialAfterRender); + }else{ + return; + } + + } + + void _calculateMarkPosition() { + final RenderBox cardRenderBox = bookMarkKey.currentContext!.findRenderObject() as RenderBox; + final Size cardSize = cardRenderBox.size; + cardHeight = cardSize.height; + debugPrint('height : $cardHeight'); + } + + void _initmarriageTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + marriageTargets=[ + TargetFocus( + identify: 'chartsKey', + keyTarget: chartKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + alignment: ContentAlign.top, + gap:50, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + alignment: ContentAlign.bottom, + gap: 23, + space: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: cardHeight+20), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + ]; + } + + void _initpreviousTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + previousMarriageTargets=[ + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + space: 50, + alignment: ContentAlign.bottom, + gap: 23, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: cardHeight+20), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'chartsKey', + keyTarget: chartKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + space: 0, + alignment: ContentAlign.top, + gap:50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 30, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(previousChartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + + + ]; + } @override Widget build(BuildContext context) { double myheight = MediaQuery.of(context).size.height; @@ -33,7 +647,7 @@ class Marriages extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - IconButton(onPressed: (){}, icon: Icon(Icons.bookmark_add_outlined,color: Colors.white,size: 40,)), + IconButton(key :bookMarkKey,onPressed: (){}, icon: Icon(Icons.bookmark_add_outlined,color: Colors.white,size: 40,)), IconButton(onPressed: (){}, icon: Icon(Icons.share,color: Colors.white,size: 30,)), ],), ], @@ -47,6 +661,7 @@ class Marriages extends StatelessWidget { child: Column( children: [ Row( + key:chartKey, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container(height: myheight/3.5,width: mywidth/2.5, diff --git a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart index 190d010f..c5bd1e8a 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart @@ -1,7 +1,9 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/presentation/components/constant/constant.dart'; @@ -23,28 +25,31 @@ class UaeNumbers extends StatelessWidget { } } -class uaenumberWidget extends StatefulWidget { +class uaenumberWidget extends ConsumerStatefulWidget { const uaenumberWidget({super.key}); @override - _UaenumberWidgetState createState() => _UaenumberWidgetState(); + ConsumerState createState() => _UaenumberWidgetState(); } -class _UaenumberWidgetState extends State { +class _UaenumberWidgetState extends ConsumerState { @override List homePageData = []; bool isLoading = true; + int? expandedIndex = 0; @override void initState() { - fetchData(); + super.initState(); + final locale = ref.read(localeProvider); + fetchData(locale?.languageCode ?? 'en'); } - Future fetchData() async { + Future fetchData(locale) async { // const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; const baseUrl = 'https://pb.venbait.in/api/getUAENumbersData'; try { - final response = await http.get(Uri.parse(baseUrl)); + final response = await http.get(Uri.parse(baseUrl + '?language=$locale')); if (response.statusCode == 200) { setState(() { homePageData = json.decode(response.body); @@ -63,6 +68,10 @@ class _UaenumberWidgetState extends State { } Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchData(localeCode); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; @@ -74,12 +83,13 @@ class _UaenumberWidgetState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(30.0), - border: Border.all(width: 1)), + border: Border.all(width: 1,color: Color(0xFFAA8E83)),), child: TextField( decoration: InputDecoration( hintText: "Search", - prefixIcon: Icon(Icons.search), - border: InputBorder.none, + prefixIcon: Icon(Icons.search, + color: Color(0xFFAA8E83),), + border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), ), @@ -99,7 +109,12 @@ class _UaenumberWidgetState extends State { return CustomExpandableTile( index: index, - isExpanded: isFirstTile, +isExpanded: expandedIndex == index, // Compare with expandedIndex + onTap: (index) { + setState(() { + expandedIndex = (expandedIndex == index) ? null : index; + }); + }, title: mainTopic['main_topic'], titleBackgroundColor: backgroundColor, children: _buildSubTopics(mainTopic['sub_topics'] ?? [], mywidth, @@ -280,6 +295,8 @@ class CustomExpandableTile extends StatefulWidget { final List children; final int index; final bool isExpanded; + final ValueChanged onTap; + const CustomExpandableTile({ required this.title, @@ -287,6 +304,8 @@ class CustomExpandableTile extends StatefulWidget { required this.children, required this.index, required this.isExpanded, + required this.onTap, + }); @override @@ -294,14 +313,12 @@ class CustomExpandableTile extends StatefulWidget { } class _CustomExpandableTileState extends State { - // bool isExpanded = false; - late bool isExpanded; + bool isExpanded = false; + // late bool isExpanded; @override void initState() { - super.initState(); - isExpanded = - widget.isExpanded; // Initialize isExpanded based on widget's property + super.initState(); // Initialize isExpanded based on widget's property } @override @@ -312,11 +329,8 @@ class _CustomExpandableTileState extends State { child: Column( children: [ GestureDetector( - onTap: () { - setState(() { - isExpanded = !isExpanded; - }); - }, + onTap: () => widget.onTap(widget.index), + child: Container( decoration: BoxDecoration( color: widget.titleBackgroundColor, @@ -335,7 +349,7 @@ class _CustomExpandableTileState extends State { ), ), Icon( - isExpanded + widget.isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, color: Colors.white, @@ -350,8 +364,8 @@ class _CustomExpandableTileState extends State { duration: Duration(milliseconds: 300), curve: Curves.easeInOut, width: double.infinity, - height: isExpanded ? myheight * 0.52 : 0, - child: isExpanded + height: widget.isExpanded ? myheight * 0.52 : 0, + child: widget.isExpanded ? SingleChildScrollView( child: Container( decoration: BoxDecoration( diff --git a/lib/presentation/Screens/app_tour/Target_content.dart b/lib/presentation/Screens/app_tour/Target_content.dart new file mode 100644 index 00000000..51e52b06 --- /dev/null +++ b/lib/presentation/Screens/app_tour/Target_content.dart @@ -0,0 +1,50 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:flutter/material.dart'; + +final allTargetsCompletedProvider = StateProvider((ref) => true); +final chartsTourProvider = StateProvider((ref) => true); +final previousChartsTourProvider = StateProvider((ref) => true); +final homeTourProvider = StateProvider((ref) => false); +final previousHomeTourProvider = StateProvider((ref) => true); +final scaffoldTourProvider = StateProvider((ref) => true); +final previousScaffoldTourProvider = StateProvider((ref) => true); + +TargetContent createTargetContent({ + required String text, + required ContentAlign alignment, + required double gap, + required double space , +}) { + return TargetContent( + align: alignment, + builder: (context, controller) { + return Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10, right: 10, bottom: space , top: space ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: gap,), + ], + ), + ], + ); + }, + ); +} \ No newline at end of file diff --git a/lib/presentation/Screens/auth_verification/create_new_pw.dart b/lib/presentation/Screens/auth_verification/create_new_pw.dart index 1fa3a802..83692570 100644 --- a/lib/presentation/Screens/auth_verification/create_new_pw.dart +++ b/lib/presentation/Screens/auth_verification/create_new_pw.dart @@ -13,7 +13,9 @@ import '../../../config/my_theme.dart'; class CreateNewPw extends StatefulWidget { final String userId; final String email; - const CreateNewPw({Key? key, required this.userId, required this.email}); + final String? keyParam; + + const CreateNewPw({Key? key, required this.userId, required this.email, required this.keyParam}); @override State createState() => _CreateNewPwState(); @@ -205,7 +207,38 @@ class _CreateNewPwState extends State { padding: const EdgeInsets.all(24.0), child: Column( children: [ + // SizedBox(height: screenHeight / 7), + Align( + alignment: Alignment.topRight, + child: GestureDetector( + onTap: () { + + if (widget.keyParam == 'editProfile') { + context.go('/editProfile'); + }else + { + context.go('/profile/${widget.userId}'); + } + + }, + child: Container( + margin: EdgeInsets.only(top: 16,left: 16,bottom: 16,right: 1), // Add margin for positioning + width: 30, // Circle diameter + height: 30, + decoration: BoxDecoration( + color: Colors.grey[300], // Circle color + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + size: 20, // Icon size + color: Colors.white, // Icon color + ), + ), + ), + ), SizedBox(height: screenHeight / 6), + Text( // "Create New Password", AppLocalizations.of(context)!.create_new_password, @@ -327,8 +360,8 @@ class _CreateNewPwState extends State { SizedBox(height: screenHeight / 5), Center( child: Container( - height: screenHeight / 8, - width: screenWidth / 2, + height: screenHeight / 12, + width: screenWidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/splash_screen/logo.png"), diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index b6ef1088..cd1babd9 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -1,10 +1,13 @@ +import 'package:external_repos/external_repos.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:pocketbase/pocketbase.dart'; +import 'package:uae_stat/config/my_theme.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; import 'package:uae_stat/presentation/Screens/profilepage.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -31,6 +34,7 @@ class _RegisterScreenState extends State { String? _password; bool registrationSuccess = false; bool registrationFailed = false; + bool isRegistering = false; dynamic userID; final pb = PocketBase('https://pb.venbait.in'); @@ -144,6 +148,12 @@ class _RegisterScreenState extends State { final regex = RegExp( r'^[A-Za-z0-9~!@#$%^&*()_+=[\]{}|;:,.<>?/-àèìòùáéíóúÀÈÌÒÙÁÉÍÓÚ]*$'); + // Define regular expressions for password complexity requirements + final hasUppercase = RegExp(r'[A-Z]'); + final hasLowercase = RegExp(r'[a-z]'); + final hasDigit = RegExp(r'\d'); + final hasSpecialCharacter = RegExp(r'[~!@#$%^&*()_+=[\]{}|;:,.<>?/-]'); + if (value == null || value.isEmpty) { return 'Required'; } else if (value.length < 8) { @@ -151,7 +161,7 @@ class _RegisterScreenState extends State { } // Check the length constraint - if (value.length < 8 || value.length > 40) { + if (value.length < 8 || value.length > 64) { return 'Password must be between 8 and 64 characters'; } @@ -160,6 +170,27 @@ class _RegisterScreenState extends State { return 'Password contains invalid characters'; } + // Track missing constraints + List missingConstraints = []; + + if (!hasUppercase.hasMatch(value)) { + missingConstraints.add('uppercase letter'); + } + if (!hasLowercase.hasMatch(value)) { + missingConstraints.add('lowercase letter'); + } + if (!hasDigit.hasMatch(value)) { + missingConstraints.add('numeric digit'); + } + if (!hasSpecialCharacter.hasMatch(value)) { + missingConstraints.add('special character'); + } + + // If there are missing constraints, return a consolidated message + if (missingConstraints.isNotEmpty) { + return 'At least one ${missingConstraints.join(', ')}'; + } + _password = value; // Store the password for confirm password validation return null; } @@ -185,12 +216,15 @@ class _RegisterScreenState extends State { Future _registerUser() async { if (_formKey.currentState?.validate() ?? false) { if (isChecked) { + setState(() { + isRegistering = true; // Disable the button + }); try { final adminAuth = await pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - print('adminToken- ${adminToken}'); + print('adminTokenREgistration- ${adminToken}'); // Create user in PocketBase final response = await pb.collection('users').create(body: { 'uname': _usernameController.text, @@ -211,6 +245,7 @@ class _RegisterScreenState extends State { userID = response.id; registrationSuccess = true; registrationFailed = false; // Show success message on success + isRegistering = false; }); // Navigate to ProfileScreen after successful registration @@ -236,7 +271,13 @@ class _RegisterScreenState extends State { 'Email ID already exists. Please use a different email.'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: () { + Navigator.pop(context); + setState(() { + isRegistering = + false; // Re-enable the button after error + }); + }, child: Text('OK'), ), ], @@ -249,6 +290,9 @@ class _RegisterScreenState extends State { registrationFailed = true; registrationSuccess = false; }); + setState(() { + isRegistering = false; // Re-enable the button after error + }); } } } else { @@ -318,6 +362,7 @@ class _RegisterScreenState extends State { }, ), ], + backgroundColor: Colors.white, ), backgroundColor: Colors.white, body: registrationSuccess @@ -330,7 +375,9 @@ class _RegisterScreenState extends State { buildIconContainer(Icons.report, Color(0xFF7DAFBC)), SizedBox(height: 20), Text( - "Your registration is pending for verification.", + context.translate( + 'Your registration is pending for verification.', + 'تسجيلك في انتظار التحقق.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 16, @@ -339,7 +386,9 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), Text( - "Kindly verify your mail to proceed further.", + context.translate( + 'Kindly verify your mail to proceed further.', + 'يرجى التحقق من البريد الخاص بك للمضي قدما.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -350,14 +399,14 @@ class _RegisterScreenState extends State { ElevatedButton( onPressed: () => {context.go('/')}, child: Text( - 'Go to Login', + context.translate('Go to Login', 'اذهب إلى تسجيل الدخول'), ), ), SizedBox(height: screenheight / 5), Center( child: Container( - height: screenheight / 8, - width: screenwidth / 2, + height: screenheight / 16, + width: screenwidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( @@ -379,7 +428,8 @@ class _RegisterScreenState extends State { buildIconContainer(Icons.report, Colors.red), SizedBox(height: 20), Text( - "Sorry ${_usernameController.text}!", + context.translate('Sorry ${_usernameController.text}!', + 'آسف ${_usernameController.text}!'), textAlign: TextAlign.center, style: TextStyle( fontSize: 16, @@ -388,7 +438,9 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), Text( - "Your registration process failed. For further assistance, please contact support.", + context.translate( + 'Your registration process failed. For further assistance, please contact support.', + 'فشلت عملية التسجيل الخاصة بك. لمزيد من المساعدة، يرجى الاتصال بالدعم.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -409,14 +461,14 @@ class _RegisterScreenState extends State { }) }, child: Text( - 'Retry', + context.translate('Retry', 'أعد المحاولة'), ), ), SizedBox(height: screenheight / 5), Center( child: Container( - height: screenheight / 8, - width: screenwidth / 2, + height: screenheight / 16, + width: screenwidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( @@ -428,313 +480,566 @@ class _RegisterScreenState extends State { ], ), ) - : Padding( - padding: const EdgeInsets.all(20.0), - child: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(height: screenheight / 15), - Text( - // 'Register', - AppLocalizations.of(context)!.register_title, - style: TextStyle( - fontSize: 32, fontWeight: FontWeight.w400), - ), - SizedBox(height: 10), - Text(AppLocalizations.of(context)!.register_details), + : Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: screenheight / 60), + Text( + // 'Register', + AppLocalizations.of(context)!.register_title, + style: TextStyle( + fontSize: 32, fontWeight: FontWeight.w400), + ), + SizedBox(height: 10), + Text(AppLocalizations.of(context)!.register_details), - SizedBox(height: 20), - // Display this if registration is pending approval - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - // hintText: _showHints[0] ? 'Username' : null, - hintText: _showHints[0] - ? AppLocalizations.of(context)!.register_name - : null, - prefixIcon: Icon( - Icons.person, - color: Colors.blue, - ), - border: OutlineInputBorder(), - counterText: '', + SizedBox(height: 20), + // Display this if registration is pending approval + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: TextFormField( + controller: _usernameController, + // focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Username' : null, + hintText: _showHints[0] + ? AppLocalizations.of(context)!.register_name + : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), ), - validator: _validateUsername, - maxLength: - 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 15), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - // hintText: 'Enter your email', - hintText: _showHints[1] - ? AppLocalizations.of(context)!.register_email - : null, - // _showHints[1] ? 'Enter your email' : null, - prefixIcon: Icon( - Icons.email, - color: Colors.blue, + prefixIcon: Padding( + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), + child: Image.asset( + MiscIconAssetPath.person, + fit: BoxFit.fitHeight, + height: 25, + width: 25, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, ), - border: OutlineInputBorder(), - counterText: '', ), - validator: _validateEmail, - maxLength: 320, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - inputFormatters: [ - LengthLimitingTextInputFormatter( - 320), // Limit to 320 characters - ], - ), - SizedBox(height: 15), - TextFormField( - controller: _passwordController, - focusNode: _focusNodes[2], - obscureText: _obscurePassword, - decoration: InputDecoration( - hintText: _showHints[2] - ? AppLocalizations.of(context)! - .register_password - : null, - // _showHints[2] ? 'Enter your password' : null, - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, - ), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility - : Icons.visibility_off, - color: Colors.blue, - ), - onPressed: () { - setState(() { - _obscurePassword = !_obscurePassword; - }); - }, - ), - border: OutlineInputBorder(), - counterText: '', + + // prefixIcon: Icon( + // Icons.person, + // color: Color(0xFF90B0D5), + // + // ), + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border ), - validator: _validatePassword, - maxLength: 40, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 15), - TextFormField( - controller: _confirmpasswordController, - focusNode: _focusNodes[3], - obscureText: _obscureConfirmPassword, - decoration: InputDecoration( - hintText: _showHints[3] - ? AppLocalizations.of(context)! - .register_Confirm_password - : null, - // _showHints[3] ? 'Confirm password' : null, - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, - ), - suffixIcon: IconButton( - icon: Icon( - _obscureConfirmPassword - ? Icons.visibility - : Icons.visibility_off, - color: Colors.blue, - ), - onPressed: () { - setState(() { - _obscureConfirmPassword = - !_obscureConfirmPassword; - }); - }, - ), - border: OutlineInputBorder(), - counterText: '', + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border ), - validator: _validateConfirmPassword, - maxLength: 40, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - inputFormatters: [ - LengthLimitingTextInputFormatter( - 64), // Limit to 40 characters - ], + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color + ), + counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), - SizedBox(height: 10), - Row( - children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: showError ? Colors.red : Colors.grey, - width: 1.5, - ), + validator: _validateUsername, + maxLength: + 40, // Set the maximum length to 20 characters + maxLengthEnforcement: MaxLengthEnforcement.enforced, + ), + ), + SizedBox(height: 15), + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: TextFormField( + controller: _emailController, + // focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: 'Enter your email', + hintText: _showHints[1] + ? AppLocalizations.of(context)! + .enter_your_email + : null, + // _showHints[1] ? 'Enter your email' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), + prefixIcon: Padding( + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, ), - Expanded( - child: Text.rich( - TextSpan( - text: AppLocalizations.of(context)!.agree, - // text: 'I agree to ', - children: [ - TextSpan( - text: AppLocalizations.of(context)! - .terms_conditions, - // text: 'Terms & Conditions', - style: TextStyle(color: Colors.blue), - ), - TextSpan( - text: AppLocalizations.of(context)! - .t_and), - TextSpan( - text: AppLocalizations.of(context)! - .privacy_policy, - style: TextStyle(color: Colors.blue), - ), - TextSpan( - text: AppLocalizations.of(context)! - .conditions), - ], - ), - ), + child: Image.asset( + MiscIconAssetPath.vector, + fit: BoxFit.fitHeight, + height: 20, + width: 25, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade100, ), - ], + ), + + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, + width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color + ), + counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), - if (showError) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions.', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, + validator: _validateEmail, + maxLength: 320, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + inputFormatters: [ + LengthLimitingTextInputFormatter( + 320), // Limit to 320 characters + ], + ), + ), + SizedBox(height: 15), + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: TextFormField( + controller: _passwordController, + // focusNode: _focusNodes[2], + obscureText: _obscurePassword, + decoration: InputDecoration( + hintText: _showHints[2] + ? AppLocalizations.of(context)! + .enter_your_password + : null, + // _showHints[2] ? 'Enter your password' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), + prefixIcon: Padding( + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), + child: Image.asset( + MiscIconAssetPath.lock, + fit: BoxFit.fitHeight, + height: 25, + width: 25, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, + ), + ), + + // prefixIcon: Icon( + // Icons.lock, + // color: Color(0xFF90B0D5), + // ), + suffixIcon: IconButton( + // icon: Icon( + // _obscurePassword + // ? Icons.visibility_off + // : Icons.visibility, + // color: Color(0xFF9EA2A9), + // ), + + icon: Image.asset( + _obscurePassword + ? MiscIconAssetPath.visibilityOff + : MiscIconAssetPath.visibleOn, + + color: Color( + 0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + width: 24, + height: 24, + ), + + onPressed: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); + }, + ), + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, + width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color + ), + + counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), + ), + validator: _validatePassword, + maxLength: 40, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + ), + ), + SizedBox(height: 15), + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: TextFormField( + controller: _confirmpasswordController, + // focusNode: _focusNodes[3], + obscureText: _obscureConfirmPassword, + decoration: InputDecoration( + hintText: _showHints[3] + ? AppLocalizations.of(context)! + .register_Confirm_password + : null, + // _showHints[3] ? 'Confirm password' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), + prefixIcon: Padding( + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), + child: Image.asset( + MiscIconAssetPath.lock, + fit: BoxFit.fitHeight, + height: 25, + width: 25, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, + ), + ), + + suffixIcon: IconButton( + icon: Image.asset( + _obscureConfirmPassword + ? MiscIconAssetPath.visibilityOff + : MiscIconAssetPath.visibleOn, + + color: Color( + 0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + width: 24, + height: 24, + ), + onPressed: () { + setState(() { + _obscureConfirmPassword = + !_obscureConfirmPassword; + }); + }, + ), + // border: OutlineInputBorder( + // borderSide: BorderSide(color: Colors.blue, width: 2), // Default border color + // ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, + width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color + ), + counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), + ), + validator: _validateConfirmPassword, + maxLength: 40, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + inputFormatters: [ + LengthLimitingTextInputFormatter( + 64), // Limit to 40 characters + ], + ), + ), + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.only( + top: 1.0, bottom: 10, left: 25, right: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: showError + ? Color(0xFFD83731) + : Color(0xFF7DAFBC), + width: 1, + ), + ), + Expanded( + child: Text.rich( + TextSpan( + text: AppLocalizations.of(context)!.agree, + // text: 'I agree to ', + children: [ + TextSpan( + text: AppLocalizations.of(context)! + .terms_conditions, + // text: 'Terms & Conditions', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Color(0xFF985400), + // Makes the text bold + decoration: + TextDecoration.underline, + decorationColor: Color(0xFF985400), + decorationThickness: 1), ), - ), + TextSpan( + text: AppLocalizations.of(context)! + .t_and), + TextSpan( + text: AppLocalizations.of(context)! + .privacy_policy, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + fontFamily: 'Roboto', + color: Color(0xFF985400), + decoration: + TextDecoration.underline, + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), + ), + TextSpan( + text: AppLocalizations.of(context)! + .conditions), + ], ), + ), + ), + ], + ), + ), + if (showError) + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 30.0), + child: Text( + 'Required', + style: TextStyle( + color: Color(0xFFb22222), + fontSize: 12, + ), + ), + ), + ], + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: SizedBox( + width: screenwidth / 1, + child: ElevatedButton( + onPressed: isRegistering ? null : _registerUser, + style: ElevatedButton.styleFrom( + backgroundColor: Color( + 0xFFA7887A), // Brownish color for Register + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + // "Register", + AppLocalizations.of(context)! + .register_title, + style: TextStyle( + fontSize: 16, color: Colors.white), + ), + SizedBox(width: 8), + const Icon( + Icons.chevron_right_outlined, + color: Colors + .white, // Set your desired color here + ) ], ), - SizedBox(height: 20), - SizedBox( - width: screenwidth / 1.3, - child: ElevatedButton( - onPressed: _registerUser, - style: ElevatedButton.styleFrom( - backgroundColor: Color( - 0xFFA7887A), // Brownish color for Register - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - // "Register", - AppLocalizations.of(context)! - .register_title, - style: TextStyle( - fontSize: 16, color: Colors.white), - ), - SizedBox(width: 8), - Icon(Icons.arrow_forward, - color: Colors.white), - ], - ), - ), ), - SizedBox(height: 10), - Text( - AppLocalizations.of(context)!.account_confirmation, - style: TextStyle( - fontSize: 16, color: Colors.grey[600]), - ), - SizedBox(height: 10), - SizedBox( - width: screenwidth / 1.3, - child: ElevatedButton( - onPressed: () { - context.go('/'); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color( - 0xFF82AFCB), // Blueish color for Login - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.login_title, - style: TextStyle( - fontSize: 16, color: Colors.white), - ), - SizedBox(width: 8), - Icon(Icons.arrow_forward, - color: Colors.white), - ], - ), - - // child: GestureDetector( - // onTap: () { - // // Navigate to LoginRoute - // context.go('/'); - // }, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Text( - // "Login", - // style: TextStyle( - // fontSize: 16, - // color: Colors.white, - // ), - // ), - // SizedBox(width: 8), - // Icon(Icons.arrow_forward, - // color: Colors.white), - // ], - // ), - // ), - ), - ), - SizedBox( - height: 10, - ), - Center( - child: Container( - height: screenheight / 8, - width: screenwidth / 2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - "assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) - ], + ), ), - ), + SizedBox(height: 15), + Text( + AppLocalizations.of(context)!.account_confirmation, + style: TextStyle( + fontSize: 14, color: Colors.grey[600]), + ), + SizedBox(height: 15), + Padding( + padding: const EdgeInsets.only( + top: 5.0, bottom: 10, left: 30, right: 30), + child: SizedBox( + width: screenwidth / 1, + child: ElevatedButton( + onPressed: () { + context.go('/'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color( + 0xFF82AFCB), // Blueish color for Login + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.login_title, + style: TextStyle( + fontSize: 16, color: Colors.white), + ), + SizedBox(width: 8), + const Icon( + Icons.chevron_right_outlined, + color: Colors + .white, // Set your desired color here + ) + ], + ), + + // child: GestureDetector( + // onTap: () { + // // Navigate to LoginRoute + // context.go('/'); + // }, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Text( + // "Login", + // style: TextStyle( + // fontSize: 16, + // color: Colors.white, + // ), + // ), + // SizedBox(width: 8), + // Icon(Icons.arrow_forward, + // color: Colors.white), + // ], + // ), + // ), + ), + ), + ), + SizedBox( + height: 10, + ), + Center( + child: Container( + height: screenheight / 16, + width: screenwidth / 3, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )), + SizedBox( + height: 20, + ), + ], ), ), + ), ); } } diff --git a/lib/presentation/Screens/charts/chart.dart b/lib/presentation/Screens/charts/chart.dart index 5c35d379..a18a0194 100644 --- a/lib/presentation/Screens/charts/chart.dart +++ b/lib/presentation/Screens/charts/chart.dart @@ -1035,49 +1035,47 @@ // // [ // { -// chart_heading :"Guest Nights by Region" -// chart_type :"fl_stacked_bar" -// dataset :"hotels" -// group_by :"GUEST_REGION" +// chart_heading :"Population Growth Over Time" +// chart_type :"line_trend" +// dataset :"population" +// group_by :"GENDER" // is_chart :"true" -// kpi :"hotel_occupancy_rate_chart_1" -// main_id :"economy" +// kpi :"population" +// main_id :"social" // response:[ // { // "ObsKey": { // "FREQ": "A", -// "GUEST_REGION": "AF", -// "H_INDICATOR": "GUN", -// "H_TYPE": "_Z", -// "MEASURE": "H", +// "GENDER": "M", +// "MEASURE": "POP", +// "POP_IND": "_Z", // "REF_AREA": "AE", // "SOURCE_DETAIL": "FCSC", -// "TIME_PERIOD": "2016", -// "UNIT_MEASURE": "NUMBER" +// "TIME_PERIOD": "1970", +// "UNIT_MEASURE": "PS" // }, // "ObsValue": { -// "Value": "2659446" +// "Value": "149195" +// } // } -// }, // { // "ObsKey": { // "FREQ": "A", -// "GUEST_REGION": "OC", -// "H_INDICATOR": "GUN", -// "H_TYPE": "_Z", -// "MEASURE": "H", +// "GENDER": "F", +// "MEASURE": "POP", +// "POP_IND": "_Z", // "REF_AREA": "AE", // "SOURCE_DETAIL": "FCSC", -// "TIME_PERIOD": "2018", -// "UNIT_MEASURE": "NUMBER" +// "TIME_PERIOD": "1995", +// "UNIT_MEASURE": "PS" // }, // "ObsValue": { -// "Value": "1127282" +// "Value": "850654" // } // } // // ] -// sub_id :"tourism" -// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_GUEST_REGION,4.3.0/...A..GUN.OTH+OC+AF+EC+AM+AC+ASC+GCC+UAE.?startPeriod=2016&dimensionAtObservation=AllDimensions" +// sub_id :"population" +// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_POP,2.7.0/....A..?startPeriod=1970&dimensionAtObservation=AllDimensions" // } // ] diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 3682dcb4..d6edd328 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -1,15 +1,25 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart'; import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart'; import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; import '../filters/search_filter_helper.dart'; -class ChartScreen1 extends StatefulWidget { +class ChartScreen1 extends ConsumerStatefulWidget { final String dataSets; final String bgColor; final String mainTopic; final String title; + final String? keyParam; // Nullable String const ChartScreen1({ Key? key, @@ -17,18 +27,20 @@ class ChartScreen1 extends StatefulWidget { required String this.bgColor, required this.mainTopic, required this.title, + required this.keyParam, }); @override - _ChartScreen1State createState() => _ChartScreen1State(); + ConsumerState createState() => _ChartScreen1State(); } -class _ChartScreen1State extends State { +class _ChartScreen1State extends ConsumerState { List> selectedFiltersStorage = []; final GlobalKey _scaffoldKey = GlobalKey(); final ApiService apiService = ApiService(); bool isLoading = true; List isChartData = []; List nonChartData = []; + List filterDataSet = []; List filterData = []; List chartsData = []; List tabFilteredChartData = []; @@ -43,6 +55,12 @@ class _ChartScreen1State extends State { int _activeTabIndex = 0; dynamic _tabsData = []; + late List marriageTargets; + late List previousMarriageTargets; + late TutorialCoachMark tutorialCoachMark; + final GlobalKey chartKey = GlobalKey(); + final GlobalKey bookMarkKey = GlobalKey(); + final GlobalKey cardKey= GlobalKey(); @override void initState() { @@ -50,12 +68,722 @@ class _ChartScreen1State extends State { final String bgColor = widget.bgColor; print(' bgColor $bgColor'); // fetchChartData(widget.dataSets); - fetchChartData(widget.dataSets).then((_) { + final locale = ref.read(localeProvider); + fetchChartData(widget.dataSets, locale?.languageCode ?? 'en').then((_) { if (_tabsData.isNotEmpty) { // Call onTabSelected for the first tab onTabSelected(_tabsData[0]['id']); } }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _startTutorialAfterRender(); + }); + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + context.go('/myhomepage'); + } + + void _kpiTutorialCoachMark() { + debugPrint('Not Initialized and Started'); + // _calculateMarkPosition(); + _initmarriageTargets(); + debugPrint('Initialized and Started'); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + + pulseEnable: false, + hideSkip: true, + targets: marriageTargets, + onFinish: () { + context.go('/myhomepage'); + debugPrint('Marriage Tutorial Finished'); + }, + + )..show(context: context); + } + + + void _startTutorialAfterRender() { + final chartTour = ref.watch(chartsTourProvider); + final previousChartTour = ref.watch(previousChartsTourProvider); + debugPrint('render problem'); + if (cardKey.currentContext != null && !chartTour) { + _kpiTutorialCoachMark(); + } else if(cardKey.currentContext != null && !previousChartTour) { + _previousKpiTutorial(); + } + else if (!chartTour || !previousChartTour){ + Future.delayed(Duration(milliseconds: 100), _startTutorialAfterRender); + }else{ + return; + } + + } + + void _previousKpiTutorial() { + _initpreviousTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousMarriageTargets, + onFinish: () { + context.go('/myhomepage'); + debugPrint('Previous Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + void _initmarriageTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + marriageTargets=[ + TargetFocus( + identify: 'cardKey', + keyTarget: cardKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 0, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + alignment: ContentAlign.top, + gap:55, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 55, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: EdgeInsets.only(right:10), + child:Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ) , + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + // TargetFocus( + // identify: 'chartKey', + // keyTarget: chartKey, + // shape: ShapeLightFocus.RRect, + // paddingFocus: 0, + // contents: [ + // createTargetContent( + // text: AppLocalizations.of(context)!.kpiCards, + // alignment: ContentAlign.top, + // gap:0, + // space: 0, + // ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: double.infinity, + // height: MediaQuery.of(context).size.height * + // 0.30, // Set an appropriate height for the Stack + // child: Stack( + // children: [ + // Positioned( + // bottom: 25, + // left: 16, + // right: 16, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ElevatedButton( + // onPressed: () => handleSkip(), + // style: ElevatedButton.styleFrom( + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12), + // ), + // side: const BorderSide(color: Colors.white), + // backgroundColor: Colors.transparent, + // elevation: 0, + // ), + // child: const Text( + // 'Skip', + // style: TextStyle( + // color: Colors.white, + // fontSize: 14, + // ), + // ), + // ), + // Column( + // children: [ + // Text( + // '5/7', + // style: const TextStyle( + // color: Colors.white, + // fontSize: 14, + // ), + // ), + // const SizedBox(height: 3), + // Row( + // children: [ + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all( + // color: Colors.white, width: 2.0), + // ), + // child: IconButton( + // icon: const Icon(Icons.arrow_back, + // color: Colors.white), + // onPressed: () { + // scrollToTargetThenShowTutorial(bookMarkKey); + // tutorialCoachMark.next(); + // }, + // ), + // ), + // const SizedBox(width: 10), + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: Color(0xFF7DAFBC), + // border: Border.all( + // color: Color(0xFF7DAFBC), width: 1.5), + // ), + // child: IconButton( + // icon: const Icon(Icons.arrow_forward, + // color: Colors.white), + // onPressed: () { + // tutorialCoachMark.next(); + // }, + // ), + // ), + // ], + // ), + // ], + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 12, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + alignment: ContentAlign.bottom, + gap: 53, + space: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 55, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding(padding: EdgeInsets.only(right: 10), + child: Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top:0), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + + ]; + } + + void _initpreviousTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + previousMarriageTargets=[ + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 12, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + space: 50, + alignment: ContentAlign.bottom, + gap: 23, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: 10), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'cardKey', + keyTarget: cardKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 0, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + space: 0, + alignment: ContentAlign.top, + gap:50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.30, + child: Stack( + children: [ + Positioned( + bottom: 55, + left: 16, + right: 16, + child: Column( + children: [ + Align(alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ),), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(previousChartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + + + ]; } void _scrollToIndex(int index) { @@ -74,6 +802,7 @@ class _ChartScreen1State extends State { }); _scrollToIndex(_activeTabIndex); onTabSelected(_tabsData[_activeTabIndex]['id']!); // Pass the tab's id + print("TABFiltered Data: $filterData"); } } @@ -87,9 +816,39 @@ class _ChartScreen1State extends State { } } + // void processChartData(chartsData) { + // // Group data by 'kpi' + // Map>> groupedData = {}; + // for (var chart in chartsData) { + // String kpi = chart['kpi'] ?? ''; + // if (!groupedData.containsKey(kpi)) { + // groupedData[kpi] = []; + // } + // groupedData[kpi]!.add(chart); + // } + // + // // Format 'kpi' values for _tabs + // List> _tabs = groupedData.keys.map((kpi) { + // String name = kpi + // .split('_') // Split by underscore + // .map( + // (word) => word[0].toUpperCase() + word.substring(1)) // Capitalize + // .join(' '); // Join words with space + // + // return {'id': kpi, 'name': name}; + // }).toList(); + // + // print('Grouped Data: $groupedData'); + // print('Tabs: $_tabs'); + // setState(() { + // _tabsData = _tabs; + // }); + // } + void processChartData(chartsData) { // Group data by 'kpi' Map>> groupedData = {}; + for (var chart in chartsData) { String kpi = chart['kpi'] ?? ''; if (!groupedData.containsKey(kpi)) { @@ -98,30 +857,52 @@ class _ChartScreen1State extends State { groupedData[kpi]!.add(chart); } - // Format 'kpi' values for _tabs - List> _tabs = groupedData.keys.map((kpi) { - String name = kpi + // Format 'kpi' values for _tabs with tab_heading + List> _tabs = groupedData.entries.map((entry) { + String kpi = entry.key; + + // Extract tab_heading from the first chart in the grouped list + String tabHeading = entry.value.isNotEmpty + ? entry.value.first['tab_heading'] ?? 'Unknown' + : 'Unknown'; + + String formattedKpi = kpi .split('_') // Split by underscore - .map( - (word) => word[0].toUpperCase() + word.substring(1)) // Capitalize + .map((word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1) + : '') // Capitalize .join(' '); // Join words with space - return {'id': kpi, 'name': name}; + return {'id': kpi, 'name': tabHeading}; }).toList(); print('Grouped Data: $groupedData'); print('Tabs: $_tabs'); + setState(() { _tabsData = _tabs; }); } - Future fetchChartData(String dataSets) async { - var data = await apiService.fetchChartData(dataSets); + Future fetchChartData(String dataSets, locale) async { + var data = await apiService.fetchChartData(dataSets, locale); + + if (data.containsKey('filterData')) { + var filterData = data['filterData']; + filterDataSet = filterData.entries + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList(); + print("filterDataf1:- $filterData"); + print("filterDataf11:- $filterDataSet"); + } else { + filterDataSet = data['filterData'] ?? []; + print('filterData not found!'); + } + setState(() { isChartData = data['isChartData'] ?? []; nonChartData = data['nonChartData'] ?? []; - filterData = data['filterData'] ?? []; + // filterData = data['filterData'] ?? []; originalChartsData = List.from(isChartData); // Store original data originalCardData = List.from(nonChartData); @@ -133,17 +914,19 @@ class _ChartScreen1State extends State { print('chartsData :- $chartsData'); print('cardData :- $cardData'); - print('filterData :- $filterData'); + // print('filterData :- $filterData'); isLoading = false; }); - print(chartsData); - print(cardData); + print('chartsData -$chartsData'); + print('cardData -$cardData'); } void applyFilters(BuildContext context, List filters, List data, List dataCard, List selectedFilters) { - print("Selected Filters before applying: $selectedFilters"); - // Check if all filter_data is empty + print("applyFilters called"); + print("Selected ApplyFilters: $selectedFilters"); + print("Selected data: $data"); + if (selectedFilters.every((filter) => filter['filter_data'].isEmpty)) { setState(() { chartsData = @@ -157,8 +940,9 @@ class _ChartScreen1State extends State { // Loop through each chart data in the `data` list List filteredData = []; + Set addedChartIds = {}; // Track unique chart identifiers for (var chart in data) { - // Extract response data for filtering + final groupBy = chart['group_by']; List response = chart['response'] ?? []; // Filter the response based on selected filters @@ -171,20 +955,31 @@ class _ChartScreen1State extends State { final filterValues = filter['filter_data']; // Ensure the filter_key exists in the `ObsKey` and check if it matches any of the `filter_data` - if (obsKey.containsKey(filterKey)) { + if (groupBy == filterKey && obsKey.containsKey(filterKey)) { final obsKeyValue = obsKey[filterKey]?.toString(); return filterValues.isEmpty || filterValues.contains(obsKeyValue); } - return false; + + if (filterKey == 'TIME_PERIOD' && obsKey.containsKey('TIME_PERIOD')) { + final timePeriodValue = obsKey['TIME_PERIOD']?.toString(); + return filterValues.isEmpty || + filterValues.contains(timePeriodValue); + } + + return true; }); }).toList(); // If any data matches the filter, add the whole chart data object - if (chartFilteredData.isNotEmpty) { + // Add filtered chart only once + if (chartFilteredData.isNotEmpty && + !addedChartIds.contains(chart['chart_heading'])) { filteredData.add({ - ...chart, // Include all other properties of the chart object - 'response': chartFilteredData, // Only include filtered response data + ...chart, + 'response': chartFilteredData, }); + addedChartIds + .add(chart['chart_heading']); // Track by a unique identifier } } @@ -224,7 +1019,7 @@ class _ChartScreen1State extends State { // Update the chartsData with the filtered data setState(() { chartsData = filteredData; - cardData = filteredCardData; + cardData = filteredCardData; // Adjust this part as needed }); print("Filtered Data: $filteredData"); @@ -271,7 +1066,10 @@ class _ChartScreen1State extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Filters', + context.translate( + 'Filters', + 'المرشحات', + ), style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -289,9 +1087,15 @@ class _ChartScreen1State extends State { child: ListView.builder( itemCount: filters.length, itemBuilder: (context, index) { + filters.sort((a, b) => a["filter_text_and_order"]["order"] + .compareTo(b["filter_text_and_order"]["order"])); + final filter = filters[index]; final filterKey = filter["filter_key"]; final filterData = filter["filter_data"]; + final filter_text_and_order = + filter["filter_text_and_order"]; + final fieldOrder = filter_text_and_order['order']; return StatefulBuilder( builder: (context, setState) { @@ -306,7 +1110,9 @@ class _ChartScreen1State extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - capitalizeAndSplit(filterKey), + context.translate(filter_text_and_order['en'], + filter_text_and_order['ar']), + // capitalizeAndSplit(filterKey), style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -319,8 +1125,13 @@ class _ChartScreen1State extends State { builder: (BuildContext context) { return StatefulBuilder( builder: (context, dialogSetState) { + final locale = + ref.watch(localeProvider); + print('localelocale $locale'); return AlertDialog( - title: Text('Select $filterKey'), + title: Text( + // "${locale == 'ar' ? 'يختار ' : 'Select '}" + "${context.translate(filter_text_and_order['en'], filter_text_and_order['ar'])}"), content: SingleChildScrollView( child: ListBody( children: filterData @@ -357,7 +1168,12 @@ class _ChartScreen1State extends State { setState(() {}); Navigator.pop(context); }, - child: Text('OK'), + child: Text( + context.translate( + 'OK', + 'نعم', + ), + ), ), ], ); @@ -380,7 +1196,9 @@ class _ChartScreen1State extends State { children: selectedFilter["filter_data"].isEmpty ? [ - Text('Select $filterKey', + Text( + // "${locale == 'ar' ? 'يختار ' : 'Select '}" + "${context.translate(filter_text_and_order['en'], filter_text_and_order['ar'])}", style: TextStyle( color: Colors.grey)) ] @@ -409,47 +1227,140 @@ class _ChartScreen1State extends State { ), ), // Button Section + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ElevatedButton( + // onPressed: () { + // // Clear all selected filters + // selectedFilters.forEach((filter) { + // filter["filter_data"].clear(); + // }); + // setState(() { + // chartsData = List.from(originalTabChartsData); + // cardData = List.from(originalTabCardData); + // }); + // // Reset the selectedFiltersStorage to empty when clearing + // selectedFiltersStorage.clear(); + // Navigator.pop(context); + // }, + // child: Text(context.translate( + // 'Clear', + // 'واضح', + // )), + // style: ElevatedButton.styleFrom( + // backgroundColor: Colors.grey, + // ), + // ), + // ElevatedButton( + // onPressed: () { + // print( + // "Selected Filters before applying: $selectedFilters"); + // setState(() { + // chartsData = originalTabChartsData; + // cardData = originalTabCardData; + // }); + // + // applyFilters(context, filters, chartsData, cardData, + // selectedFilters); + // // Save the selected filters to storage after applying + // selectedFiltersStorage = List.from(selectedFilters); + // }, + // child: Text(context.translate( + // 'Apply Filter', + // 'تطبيق الفلتر', + // )), + // style: ElevatedButton.styleFrom( + // backgroundColor: Colors.blue, + // ), + // ), + // ], + // ), + // ), Padding( padding: const EdgeInsets.all(16.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ElevatedButton( - onPressed: () { - // Clear all selected filters - selectedFilters.forEach((filter) { - filter["filter_data"].clear(); - }); - setState(() { - chartsData = List.from(originalTabChartsData); - cardData = List.from(originalTabCardData); - }); - // Reset the selectedFiltersStorage to empty when clearing - selectedFiltersStorage.clear(); - Navigator.pop(context); - }, - child: Text('Clear'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey, + Expanded( + child: ElevatedButton( + onPressed: () { + selectedFilters.forEach((filter) { + filter["filter_data"].clear(); + }); + setState(() { + chartsData = List.from(originalTabChartsData); + cardData = List.from(originalTabCardData); + }); + selectedFiltersStorage.clear(); + Navigator.pop(context); + }, + + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0.0, vertical: 14.2), // Added padding + child: Text( + context.translate('Clear', 'واضح'), + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + overflow: + TextOverflow.ellipsis, // Prevents wrapping + maxLines: 1, // Ensures single line + ), + ), ), ), - ElevatedButton( - onPressed: () { - print( - "Selected Filters before applying: $selectedFilters"); - setState(() { - chartsData = originalTabChartsData; - cardData = originalTabCardData; - }); + SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + print( + "Selected Filters before applying: $selectedFilters"); + setState(() { + chartsData = originalTabChartsData; + cardData = originalTabCardData; + }); - applyFilters(context, filters, chartsData, cardData, - selectedFilters); - // Save the selected filters to storage after applying - selectedFiltersStorage = List.from(selectedFilters); - }, - child: Text('Apply Filter'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, + applyFilters(context, filters, chartsData, cardData, + selectedFilters); + selectedFiltersStorage = List.from(selectedFilters); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0.0, vertical: 16.0), // Added padding + child: FittedBox( + fit: BoxFit + .scaleDown, // Ensures text resizes if necessary + child: Text( + context.translate( + 'Apply Filter', 'تطبيق الفلتر'), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + overflow: + TextOverflow.ellipsis, // Prevents wrapping + maxLines: 1, // Ensures single line + ), + ), + ), ), ), ], @@ -466,6 +1377,28 @@ class _ChartScreen1State extends State { void onTabSelected(String tabId) { print('Selected Tab: $tabId'); selectedFiltersStorage.clear(); + // Filter the filterDataSet + filterData = filterDataSet + .where((item) => item['key'] == tabId) + .map((item) => item['value']) + .where((item) => item != null && item is Iterable) + .expand((item) => item) + .toList(); + + for (var item in filterData) { + if (item['filter_key'] == 'TIME_PERIOD') { + // Convert the values to integers, sort them, and convert back to strings + List timePeriodData = item['filter_data'] + .map((e) => int.parse(e.toString())) // Convert to int + .toList(); + timePeriodData.sort((a, b) => a.compareTo(b)); // Sort numerically + + // Optionally, convert sorted integers back to strings if necessary + item['filter_data'] = timePeriodData.map((e) => e.toString()).toList(); + } + } + + print("TABFiltered Data: $filterData"); setState(() { chartsData = originalChartsData; cardData = originalCardData; @@ -507,22 +1440,47 @@ class _ChartScreen1State extends State { // For example, you can update the chart data or display the results } - double calculateAspectRatio(int itemCount) { - // Modify the logic based on your layout requirements - if (itemCount <= 2) { - return 190.5 / 180; - } else if (itemCount == 3) { - return 180.0 / 300; - } else if (itemCount == 4) { - return 190.5 / 180; + // double calculateAspectRatio(int itemCount) { + // // Modify the logic based on your layout requirements + // if (itemCount <= 2) { + // return 190.5 / 180; + // } else if (itemCount == 3) { + // return 180.0 / 250; + // } else if (itemCount == 4) { + // return 190.5 / 180; + // } else { + // return 180.0 / 180; // Default for more items + // // return 190.5 / 180; + // } + // } + + double calculateAspectRatio(int crossAxisCount, List cardData) { + // Determine a default aspect ratio based on the most common chart type in the list + if (cardData.any((item) => + item['chart_type'] == 'total' || item['chart_type'] == 'average')) { + return crossAxisCount == 2 ? 1.0 : 0.7; // Larger Content + // return crossAxisCount == 2 ? 1.5 : 0.9; } else { - return 180.0 / 260; // Default for more items - // return 190.5 / 180; + return crossAxisCount == 2 ? 1.5 : 0.9; + // Shorter Content } } @override Widget build(BuildContext context) { + final locale = ref.watch(localeProvider); + final localeNotifier = ref.read(localeProvider.notifier); + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + // fetchChartData(widget.dataSets, localeCode); + + fetchChartData(widget.dataSets, localeCode).then((_) { + if (_tabsData.isNotEmpty) { + // Call onTabSelected for the first tab + onTabSelected(_tabsData[0]['id']); + } + }); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; final color = @@ -533,14 +1491,38 @@ class _ChartScreen1State extends State { appBar: AppBar( backgroundColor: color, elevation: 0, + actions: [ + // IconButton( + // onPressed: () {}, + // icon: const Icon(Icons.toggle_off_outlined), + // ), + MyToggle( + // key: toggleKey, + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ), + ], leading: IconButton( icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () { - context.go('/uaenumbers'); + if (widget.keyParam == 'home') { + context.go('/myhomepage'); + } else { + context.go('/uaenumbers'); + } }, ), title: Text( - 'UAE Numbers', + context.translate( + 'UAE Numbers', + 'أرقام الإمارات', + ), style: TextStyle(color: Colors.white), ), ), @@ -607,32 +1589,47 @@ class _ChartScreen1State extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ Row( + key: bookMarkKey, children: [ Text( - 'Bookmark', + context.translate( + 'Bookmark', + 'إشارة مرجعية', + ), style: const TextStyle( fontSize: 16, color: Colors.white, ), ), SizedBox(width: 5), - Icon(Icons.bookmark_add_outlined, - color: Colors.white, size: 18), + Image.asset( + UaeNumbersAssetPath.bookmarksUae, + color: Colors.white, + width: 24, + height: 24, + ), ], ), SizedBox(width: 10), Row( children: [ Text( - 'Share', + context.translate( + 'Share', + 'يشارك', + ), style: const TextStyle( fontSize: 16, color: Colors.white, ), ), SizedBox(width: 5), - Icon(Icons.share_sharp, - color: Colors.white, size: 18), + Image.asset( + UaeNumbersAssetPath.shareUae, + color: Colors.white, + width: 24, + height: 24, + ), ], ), SizedBox( @@ -653,33 +1650,37 @@ class _ChartScreen1State extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ // Left arrow button - _buildArrowButton( - onPressed: _activeTabIndex > 0 ? _scrollLeft : null, - icon: Icons.arrow_back_ios_new, - ), + if (_tabsData.length > 1) + _buildArrowButton( + onPressed: + _activeTabIndex > 0 ? _scrollLeft : null, + icon: Icons.arrow_back_ios_new, + ), // Tabs with horizontal scroll - Expanded( - child: SingleChildScrollView( - controller: _scrollController, - scrollDirection: Axis.horizontal, - child: Row( - children: - List.generate(_tabsData.length, (index) { - return _buildTab( - _tabsData[index], - isActive: index == _activeTabIndex, - ); - }), + if (_tabsData.length > 1) + Expanded( + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + child: Row( + children: + List.generate(_tabsData.length, (index) { + return _buildTab( + _tabsData[index], + isActive: index == _activeTabIndex, + ); + }), + ), ), ), - ), // Right arrow button - _buildArrowButton( - onPressed: _activeTabIndex < _tabsData.length - 1 - ? _scrollRight - : null, - icon: Icons.arrow_forward_ios, - ), + if (_tabsData.length > 1) + _buildArrowButton( + onPressed: _activeTabIndex < _tabsData.length - 1 + ? _scrollRight + : null, + icon: Icons.arrow_forward_ios, + ), ], ), // Expanded( @@ -694,30 +1695,38 @@ class _ChartScreen1State extends State { Padding( padding: const EdgeInsets.all(3.34), child: GridView.builder( + key:cardKey, itemCount: cardData.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: cardData.length == 2 - ? 2 - : cardData.length == 3 - ? 3 - : cardData.length == 4 - ? 2 - : 3, // Default to 3 if more than 4 items // 2 cards per row - crossAxisSpacing: 2, - mainAxisSpacing: 5, - childAspectRatio: - calculateAspectRatio(cardData.length)), + crossAxisCount: cardData.length == 2 + ? 2 + : cardData.length == 3 + ? 3 + : cardData.length == 4 + ? 2 + : 3, // Default to 3 if more than 4 items // 2 cards per row + crossAxisSpacing: 2, + mainAxisSpacing: 5, + childAspectRatio: + calculateAspectRatio(cardData.length, cardData), + ), itemBuilder: (context, index) { final item = cardData[index]; print('item item item $item'); final chart_type = item['chart_type']; final chart_heading = item['chart_heading']; + final card_logo = item['card_logo']; final data = apiService.processNonChartData(item); print('processNonChartData'); + double cardHeight = (chart_type == 'totals' || + chart_type == 'averages') + ? 180.0 + : 130.0; + if (chart_type == 'total') { return Card( margin: const EdgeInsets.all(10), @@ -725,14 +1734,261 @@ class _ChartScreen1State extends State { borderRadius: BorderRadius.circular(12), ), elevation: 4, - child: Padding( + child: Container( + height: cardHeight, padding: const EdgeInsets.all(10.0), child: Column( + mainAxisSize: MainAxisSize + .min, // Adjust card height based on content + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // const Icon(Icons.public, + // color: Color(0xFF90B0D5), size: 30), + Image.network( + card_logo ?? '', + width: 30, + height: 30, + errorBuilder: + (context, error, stackTrace) { + return Icon(Icons.public, + color: Color(0xFF90B0D5), + size: 30); // Fallback icon + }, + ), + const SizedBox(height: 3), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + child: SizedBox( + // height: 70.0, + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + ), + ), + ), + const SizedBox(height: 1), + Text( + '(${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + const SizedBox(height: 1), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['lastYearValue']), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), + ), + ), + SizedBox( + height: 2, // Height of the divider + child: Divider( + color: Colors.grey, + thickness: + 1, // Divider line thickness + ), + ), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['secondLastYearValue']), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFFD83731), + ), + ), + ), + ), + Text( + '(${data['secondLastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 9, + fontWeight: FontWeight.w500, + color: Colors.grey), + ), + ], + ), + ), + ); + } else if (chart_type == 'average') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Container( + height: cardHeight, + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.network( + card_logo ?? '', + width: 30, + height: 30, + errorBuilder: + (context, error, stackTrace) { + return Icon(Icons.public, + color: Color(0xFF90B0D5), + size: 30); // Fallback icon + }, + ), + const SizedBox(height: 5), + Flexible( + fit: FlexFit.loose, + // child: FittedBox( + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + // ), + ), + const SizedBox(height: 1), + Text( + '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + const SizedBox(height: 1), + Flexible( + fit: FlexFit.loose, + child: SizedBox( + height: 50.0, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + '${data['roundedAverage'] ?? 'NA'}', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } else if (chart_type == 'totals') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Container( + height: cardHeight, + // height: maxHeight, + // padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 10), + child: Column( + // mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.public, color: Color(0xFF90B0D5), size: 30), const SizedBox(height: 3), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + ), + ), + Text( + '(${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 11, color: Colors.grey), + ), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['lastYearValue']), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), + ), + ), + ], + ), + ), + ); + } else if (chart_type == 'averages') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Container( + // height: maxHeight, + // height: 200, + height: cardHeight, + padding: const EdgeInsets.all(5.0), + // padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + child: Column( + // mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.public, + color: Color(0xFF90B0D5), size: 30), + const SizedBox(height: 5), Text( '${chart_heading ?? 'NA'}', textAlign: TextAlign.center, @@ -747,76 +2003,23 @@ class _ChartScreen1State extends State { style: const TextStyle( fontSize: 11, color: Colors.grey), ), - Text( - apiService.formatAmount( - data['lastYearValue']), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), - ), - ), - SizedBox( - height: 2, // Height of the divider - child: Divider( - color: Colors.grey, - thickness: - 1, // Divider line thickness - ), - ), - Text( - apiService.formatAmount( - data['secondLastYearValue']), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFFD83731), - ), - ), - Text( - '(${data['secondLastYear'] ?? 'NA'})', - style: const TextStyle( - fontSize: 11, color: Colors.grey), - ), - ], - ), - ), - ); - } else if (chart_type == 'average') { - return Card( - margin: const EdgeInsets.all(10), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - elevation: 4, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.analytics, - color: Color(0xFF90B0D5), size: 30), const SizedBox(height: 5), - Text( - '${chart_heading ?? 'NA'}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Colors.black87, - ), - ), - Text( - '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', - style: const TextStyle( - fontSize: 11, color: Colors.grey), - ), - Text( - '${data['roundedAverage'] ?? 'NA'}', - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), ), ), ], diff --git a/lib/presentation/Screens/charts/services/api_service.dart b/lib/presentation/Screens/charts/services/api_service.dart index 31494a16..a99ecb50 100644 --- a/lib/presentation/Screens/charts/services/api_service.dart +++ b/lib/presentation/Screens/charts/services/api_service.dart @@ -13,13 +13,13 @@ class ChartData { class ApiService { static const String baseUrl = 'https://pb.venbait.in/api/getDataSet'; - Future> fetchChartData(String dataSets) async { + Future> fetchChartData(String dataSets, locale) async { List isChartData = []; List nonChartData = []; List originalChartsData = []; List originalCardData = []; - final url = Uri.parse('$baseUrl?dataset=$dataSets'); + final url = Uri.parse('$baseUrl?dataset=$dataSets&language=$locale'); try { final response = await http.get(url); @@ -43,7 +43,8 @@ class ApiService { return { 'isChartData': isChartData, 'nonChartData': nonChartData, - 'filterData': jsonData['filter_data'] + 'filterData': jsonData['new_filter_data'] + // 'filterData': jsonData['filter_data'] // 'originalChartsData': originalChartsData, // 'originalCardData': originalCardData, }; diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 8061a39a..59321013 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -72,27 +72,34 @@ class ChartWidget extends StatelessWidget { }).toList(); } - List generateIndicators(dynamic chartData, groupByValue) { + List generateIndicators(dynamic chartData, String groupByValue) { return chartData['response'].asMap().entries.map((entry) { int index = entry.key; var data = entry.value; Color color = Colors.primaries[index % Colors.primaries.length]; String title = data['ObsKey'][groupByValue] ?? ''; + String shortTitle = + title.length > 10 ? '${title.substring(0, 10)}…' : title; + return Row( mainAxisSize: MainAxisSize.min, children: [ Container( - width: 16, - height: 16, + width: 10, + height: 10, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), ), SizedBox(width: 8), - Text( - title, - style: TextStyle(fontSize: 14), + Tooltip( + message: title, // Full text on hover + child: Text( + shortTitle, + style: TextStyle(fontSize: 12), + overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow + ), ), ], ); @@ -108,7 +115,7 @@ class ChartWidget extends StatelessWidget { }).toSet(); } - Widget buildChart(dynamic chartData) { + Widget buildChart(dynamic chartData, BuildContext context) { print('chartDataccccccc $chartData'); // Extract group_by dynamically from the chartData if (chartData == null || chartData['response'] == null) { @@ -294,12 +301,12 @@ class ChartWidget extends StatelessWidget { aspectRatio: 1.5, child: BarChart( BarChartData( - alignment: BarChartAlignment.start, + alignment: BarChartAlignment.spaceEvenly, barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.8), fitInsideHorizontally: true, - fitInsideVertically: false, + fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(8), tooltipMargin: 16, getTooltipItem: @@ -324,6 +331,17 @@ class ChartWidget extends StatelessWidget { double value = toY - fromY; String groupName = groupNames[i]; + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } // Get the color for the current group Color groupColor = _getColorForGroup( i); // Replace with your color logic @@ -337,8 +355,7 @@ class ChartWidget extends StatelessWidget { fontSize: 14), // Circle color ), TextSpan( - text: - '$groupName - ${value.toStringAsFixed(0)}\n', + text: '$groupName - $formattedValue\n', style: TextStyle(color: Colors.white, fontSize: 12), ), @@ -379,7 +396,7 @@ class ChartWidget extends StatelessWidget { ), gridData: FlGridData(show: false), borderData: FlBorderData(show: false), - barGroups: _generateBarGroups(chartData, groupByKey), + barGroups: _generateBarGroups(context, chartData, groupByKey), ), ), ), @@ -432,51 +449,1322 @@ class ChartWidget extends StatelessWidget { ) ], ); - case 'fl_multi_bar': - return BarChart( - BarChartData( - alignment: BarChartAlignment.spaceAround, - maxY: 100, // Maximum value for the bar - barGroups: _buildBarGroups(), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - return Text( - '${value.toInt()}%', - style: TextStyle(fontSize: 12), - ); - }, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - return Text( - "Bar ${value.toInt()}", - style: TextStyle(fontSize: 12), - ); - }, - ), - ), - topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - rightTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), + case 'line_trend': + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], ), - borderData: FlBorderData( - show: false, + ); + } + + // Find the maximum year and calculate the range for the last 5 years + int maxYear = years.reduce((a, b) => a > b ? a : b); + int minYear = maxYear - 5; + + // Filter chart data to only include entries within the last 5 years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return year >= minYear && year <= maxYear; + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = + lineBarsData(filteredData, groupByValues, groupByKey); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, ), - barTouchData: BarTouchData(enabled: true), - gridData: FlGridData(show: false), + textAlign: TextAlign.center, ), - ); + SizedBox(height: 10), + // Chart + Expanded( + child: LineChart(LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ))), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 12, + runSpacing: 8, + children: groupByValues.map((group) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + color: groupColorMap[group], + ), + SizedBox(width: 6), + + Text( + group, + style: TextStyle(fontSize: 14), + ), + ], + ); + }).toList(), + ), + ), + ]); + case 'line_trend_population': + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], + ), + ); + } + + if (years.isNotEmpty) { + // Find the latest available year + int latestYear = years.reduce((a, b) => a > b ? a : b); + selectedYears.add(latestYear); // Include the latest year in selection + } + +// Filter chart data to include only the selected years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return selectedYears.contains(year); + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = + lineBarsData(filteredData, groupByValues, groupByKey); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: LineChart(LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ))), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 12, + runSpacing: 8, + children: groupByValues.map((group) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + color: groupColorMap[group], + ), + SizedBox(width: 6), + Text( + group, + style: TextStyle(fontSize: 14), + ), + ], + ); + }).toList(), + ), + ), + ]); + case 'line_trend_2': + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], + ), + ); + } + + if (years.isNotEmpty) { + // Find the latest available year + int latestYear = years.reduce((a, b) => a > b ? a : b); + selectedYears.add(latestYear); // Include the latest year in selection + } + +// Filter chart data to include only the selected years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return selectedYears.contains(year); + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = lineBarsData2(filteredData); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, // Enable horizontal scrolling + padding: const EdgeInsets.only(right: 40), + child: SizedBox( + width: (uniqueXValues.length * 80) + + 80, // Adjust width dynamically + child: LineChart( + LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ), + ), + ), + ), + ), + ]); + case 'bar_chart': + // Extract groupBy values and their corresponding y-axis values + List xAxisData = []; + List yAxisData = []; + + for (var entry in chartData['response']) { + var xValue = entry['ObsKey'][groupByKey]; + var yValue = entry['ObsValue']['Value']; + if (xValue != null && yValue != null) { + xAxisData.add(xValue.toString()); + yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); + } + } + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + : 10, + barTouchData: BarTouchData(enabled: true), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 + : 1), + getTitlesWidget: (value, meta) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + String title = xAxisData[value.toInt()]; + String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: 60, // Limit width to force wrapping + child: Transform.rotate( + angle: -0.5, + child:Tooltip( + message: title, + child: Text( + displayTitle, + softWrap: true, + overflow: TextOverflow.ellipsis, + )),) + )); + } + return Container(); + }, + reservedSize: 40, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide right titles + ), + ), + gridData: FlGridData(show: false), + borderData: FlBorderData(show: false), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: Colors.blueAccent, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + ), + ), + ), + )) + ]); + case 'bar_chart_horizontal': + // Extract groupBy values and their corresponding y-axis values + List xAxisData = []; + List yAxisData = []; + + for (var entry in chartData['response']) { + var xValue = entry['ObsKey'][groupByKey]; + var yValue = entry['ObsValue']['Value']; + if (xValue != null && yValue != null) { + xAxisData.add(xValue.toString()); + yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); + } + } + int rotationTurns = 1; + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + : 10, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData(enabled: true), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 + : 1), + getTitlesWidget: (value, meta) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Transform.rotate( + angle: -45 * + (3.1415927 / 180), // Rotating by -45 degrees + alignment: Alignment.center, + child: Text( + xAxisData[value.toInt()], + style: TextStyle(fontSize: 12), + ), + ), + ); + } + return Container(); + }, + reservedSize: 60, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide right titles + ), + ), + gridData: FlGridData(show: false), + borderData: FlBorderData(show: false), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: Colors.blueAccent, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + ), + ), + ), + )) + ]); + case 'fl_multi_bar': + double _calculateChartWidth(dynamic chartData) { + int totalBars = chartData['response']?.length ?? 0; + const double barWidth = 30; // Width for each bar, including spacing + return totalBars * barWidth; // Calculate total chart width + } + // Group crops by CROP_TYPE + Map> groupedCrops = {}; + int touchedGroupIndex = -1; + // Iterate over the chartData to group crops by CROP_TYPE + for (var item in chartData['response']) { + String crop, cropType; + if (chartData['dataset'] == 'health_services') { + crop = item['ObsKey']['SECTOR']; + cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'general_education') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'higher_education') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'labour_force') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; + } else { + crop = item['ObsKey']['CROP']; + cropType = item['ObsKey'][groupByKey]; + } + + if (groupedCrops.containsKey(cropType)) { + groupedCrops[cropType]!.add(crop); + } else { + groupedCrops[cropType] = [crop]; + } + } + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, // Enable horizontal scrolling + child: SizedBox( + width: _calculateChartWidth( + chartData), // Dynamically calculate the chart width + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: + _calculateMaxY(chartData), // Dynamically calculate max Y + barGroups: _buildHorizontalRotateBarGroups( + chartData, groupByValues), // Build bar groups + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = + groupByValues.elementAt(value.toInt()); + + String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: 60, // Limit width to force wrapping + child: Transform.rotate( + angle: -0.5, + child:Tooltip( + message: title, + child: Text( + displayTitle, + softWrap: true, + overflow: TextOverflow.ellipsis, + )),) + )); + // return Transform.rotate( + // angle: + // -0.5, // Rotation in radians (~ -30 degrees) + // child: Text( + // title, + // style: const TextStyle(fontSize: 12), + // ), + // ); + } + return const SizedBox.shrink(); + }, + ), + ), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipHorizontalAlignment: FLHorizontalAlignment.center, + tooltipRoundedRadius: 8, + fitInsideHorizontally: + true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0 || touchedGroupIndex == -1) { + return null; // Don't show the tooltip if the value is 0 or there's no touch + } + if (groupIndex == touchedGroupIndex) { + // Get the group label dynamically + String groupLabel = + groupByValues.elementAt(groupIndex); + + // Fetch the crop for the current group from groupedCrops + String cropType = groupByValues.elementAt(groupIndex); + print('GrpcropType: $cropType'); + // String crop = groupedCrops[cropType]![rodIndex]; + + // Safely fetch crop with null check + List? crops = groupedCrops[cropType]; + String crop; + if (crops != null && rodIndex < crops.length) { + crop = crops[rodIndex]; + } else { + print( + 'Crop is null or index out of bounds for cropType: $cropType and rodIndex: $rodIndex'); + crop = cropType; // Fallback to cropType + } + + // print('groupedCrops1: $groupedCrops - $rodIndex'); + // print( + // 'Groupcrop: $crop'); + double value = rod.toY; + + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } + // print('Cropvaluevalue: $groupLabel\n$crop $value $formattedValue'); + + return BarTooltipItem( + '$groupLabel\n$crop', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + // text: ' Value: ${rod.toY}', + text: ' Value: $formattedValue', + style: const TextStyle( + color: Colors.yellow, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + return null; + }, + ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + touchedGroupIndex = -1; // Reset if no interaction + // }); + } + }, + ), + gridData: FlGridData(show: false), + ), + ), + ), + )) + ]); + + case 'horizontal_rotate': + // Group crops by CROP_TYPE + Map> groupedCrops = {}; + + // Iterate over the chartData to group crops by CROP_TYPE + for (var item in chartData['response']) { + String crop, cropType; + if (chartData['dataset'] == 'natural_reserves') { + crop = item['ObsKey']['NR_TYPE']; + cropType = item['ObsKey'][groupByKey]; + } else { + crop = item['ObsKey']['CROP']; + cropType = item['ObsKey'][groupByKey]; + } + + if (groupedCrops.containsKey(cropType)) { + groupedCrops[cropType]!.add(crop); + } else { + groupedCrops[cropType] = [crop]; + } + } + int touchedGroupIndex = -1; + + int rotationTurns = 1; + print('Grouped Crops: $groupedCrops'); + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + maxY: 400000, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipHorizontalAlignment: FLHorizontalAlignment.center, + tooltipRoundedRadius: 8, + fitInsideHorizontally: true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0 || touchedGroupIndex == -1) { + return null; // Don't show the tooltip if the value is 0 or there's no touch + } + + if (groupIndex == touchedGroupIndex) { + // print('Group Index: $groupIndex, Group : $group'); + + // Get the group label dynamically + String groupLabel = groupByValues.elementAt(groupIndex); + + // Fetch the crop for the current group from groupedCrops + String cropType = groupByValues.elementAt(groupIndex); + String crop = groupedCrops[cropType]![rodIndex]; + double value = rod.toY; + + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } + + return BarTooltipItem( + '$groupLabel\n$crop', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + text: ' Value: $formattedValue', + style: const TextStyle( + color: Colors.yellow, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + return null; + }, + ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + touchedGroupIndex = -1; // Reset if no interaction + // }); + } + }, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 20, + interval: 100000, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: const TextStyle(fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 80, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = groupByValues.elementAt(value.toInt()); + return Transform.rotate( + angle: -1.58, // Rotation in radians (~ -30 degrees) + child: Center( + child: SizedBox( + width: 80, + child: Text( + title, + style: const TextStyle(fontSize: 12), + softWrap: true, + maxLines: 2, + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.white), + ), + ), + gridData: FlGridData( + show: false, + drawVerticalLine: true, + verticalInterval: 1, + horizontalInterval: 100000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + ), + barGroups: + _buildHorizontalRotateBarGroups(chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, + ), + ), + ) + // Expanded( + // child: Stack( + // children:[ + // BarChart( + // BarChartData( + // maxY: 400000, + // rotationQuarterTurns: rotationTurns, + // barTouchData: BarTouchData( + // touchTooltipData: BarTouchTooltipData( + // tooltipHorizontalAlignment: FLHorizontalAlignment.center, + // // Only show tooltip when touched + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0 || touchedGroupIndex == -1) { + // return null; // Don't show the tooltip if the value is 0 or there's no touch + // } + // + // if (groupIndex == touchedGroupIndex) { + // // print('Group Index: $groupIndex, Group : $group'); + // + // // Get the group label dynamically + // String groupLabel = groupByValues.elementAt(groupIndex); + // + // // Fetch the crop for the current group from groupedCrops + // String cropType = groupByValues.elementAt(groupIndex); + // String crop = groupedCrops[cropType]![rodIndex]; + // double value = rod.toY; + // + // String formattedValue; + // if (value >= 1000000) { + // formattedValue = + // (value / 1000000).toStringAsFixed(1) + 'M'; + // } else if (value >= 1000) { + // formattedValue = + // (value / 1000).toStringAsFixed(1) + 'K'; + // } else { + // formattedValue = value + // .toStringAsFixed(0); // for values smaller than 1000 + // } + // + // return BarTooltipItem( + // '$groupLabel\n$crop', + // const TextStyle( + // color: Colors.white, + // fontWeight: FontWeight.bold, + // ), + // children: [ + // TextSpan( + // text: ' Value: $formattedValue', + // style: const TextStyle( + // color: Colors.yellow, + // fontWeight: FontWeight.w500, + // ), + // ), + // ], + // ); + // } + // return null; + // }, + // ), + // touchCallback: (event, response) { + // if (event.isInterestedForInteractions && + // response != null && + // response.spot != null) { + // // setState(() { + // touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // // }); + // } else { + // // setState(() { + // touchedGroupIndex = -1; // Reset if no interaction + // // }); + // } + // }, + // ), + // titlesData: FlTitlesData( + // leftTitles: AxisTitles( + // sideTitles: SideTitles( + // showTitles: false, + // reservedSize: 20, + // interval: 100000, + // getTitlesWidget: (value, meta) { + // return Text( + // value.toInt().toString(), + // style: const TextStyle(fontSize: 12), + // ); + // }, + // ), + // ), + // bottomTitles: AxisTitles( + // sideTitles: SideTitles( + // showTitles: true, + // reservedSize: 80, // Added space for rotated titles + // getTitlesWidget: (value, meta) { + // if (value < groupByValues.length) { + // String title = groupByValues.elementAt(value.toInt()); + // return Transform.rotate( + // angle: -1.58, // Rotation in radians (~ -30 degrees) + // child: Center( + // child: SizedBox( + // width: 80, + // child: Text( + // title, + // style: const TextStyle(fontSize: 12), + // softWrap: true, + // maxLines: 2, + // ), + // ), + // ), + // ); + // } + // return const SizedBox.shrink(); + // }, + // ), + // ), + // rightTitles: + // AxisTitles(sideTitles: SideTitles(showTitles: false)), + // topTitles: + // AxisTitles(sideTitles: SideTitles(showTitles: false)), + // ), + // borderData: FlBorderData( + // show: true, + // border: const Border( + // // left: BorderSide(color: Colors.grey), + // bottom: BorderSide(color: Colors.white), + // ), + // ), + // gridData: FlGridData( + // show: false, + // drawVerticalLine: true, + // verticalInterval: 1, + // horizontalInterval: 100000, + // getDrawingHorizontalLine: (value) { + // return FlLine( + // color: Colors.grey.withOpacity(0.5), + // strokeWidth: 1, + // ); + // }, + // getDrawingVerticalLine: (value) { + // return FlLine( + // color: Colors.grey.withOpacity(0.5), + // strokeWidth: 1, + // ); + // }, + // ), + // barGroups: + // _buildHorizontalRotateBarGroups(chartData, groupByValues), + // alignment: BarChartAlignment.spaceAround, + // ), + // + // ), + // // Overlay Texts + // ..._buildHorizontalRotateBarGroups(chartData, groupByValues) + // .asMap() + // .entries + // .expand((entry) { + // int groupIndex = entry.key; + // BarChartGroupData groupData = entry.value; + // return groupData.barRods.asMap().entries.map((rodEntry) { + // int rodIndex = rodEntry.key; + // BarChartRodData rodData = rodEntry.value; + // + // // Position calculation + // double barHeight = rodData.toY / 400000 * MediaQuery.of(context).size.height; + // + // return Positioned( + // left: (groupIndex * 80).toDouble() + 20, // Adjust based on spacing + // bottom: barHeight / 2, // Centered inside the bar + // child: Transform.rotate( + // angle: -0.1, + // child: Text( + // // '${rodData.toY.toInt()}', + // "123", + // style: TextStyle( + // color: Colors.black, + // fontWeight: FontWeight.bold, + // fontSize: 12, + // ), + // ), + // ), + // ); + // }).toList(); + // }).toList(), + // ])) + ]); + default: return Center(child: Text('Unknown chart type')); } } +// Start Of line trend chart + FlTitlesData titlesData2(Set xValues) { + return FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) { + // Format values as millions (M) + // String formattedValue = (value / 1000000).toStringAsFixed(1) + 'M'; + return Text( + value.toStringAsFixed(1), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, // Ensure each year is shown only once + getTitlesWidget: (value, meta) { + if (xValues.contains(value)) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } + }, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide right titles + ), + ); + } + + List lineBarsData2(List filteredData) { + List lineBars = []; + + // Create a map to store population values by year and gender + Map> yearGenderMap = {}; + + for (var entry in filteredData) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'].toString()) ?? 0; + String gender = entry['ObsKey']['GENDER']; + double value = + double.tryParse(entry['ObsValue']['Value'].toString()) ?? 0.0; + + if (!yearGenderMap.containsKey(year)) { + yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; + } + + if (gender == 'Male') { + yearGenderMap[year]!['M'] = value; + } else if (gender == 'Female') { + yearGenderMap[year]!['F'] = value; + } + } + + // Calculate the ratio (M/F) * 100 for each year + List spots = []; + yearGenderMap.forEach((year, genderMap) { + if (genderMap['M'] != 0.0 && genderMap['F'] != 0.0) { + double ratio = (genderMap['M']! / genderMap['F']!) * 100; + spots.add(FlSpot(year.toDouble(), ratio)); + } + }); + + // Add a line for the ratio data + lineBars.add( + LineChartBarData( + spots: spots, + isCurved: true, + color: Colors.blue, // Set color for the ratio line + barWidth: 3, + isStrokeCapRound: true, + belowBarData: BarAreaData(show: true), + ), + ); + + return lineBars; + } + + List lineBarsData(List filteredData, + Set groupByValues, String groupByKey) { + List lineBars = []; + print('filteredData $filteredData'); + print('filteredData222 $groupByValues'); + final List uniqueColors = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + + // Create a map to assign colors to each group in order + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + } + + print('Group-Color Map: $groupColorMap'); + // Iterate through each group and generate line data + for (String group in groupByValues) { + List spots = filteredData + .where((entry) => entry['ObsKey'][groupByKey] == group) + .map((entry) { + double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); + double yValue = double.parse(entry['ObsValue']['Value']); + return FlSpot(xValue, yValue); + }).toList(); + + // Add a line for this group + lineBars.add( + LineChartBarData( + spots: spots, + isCurved: true, + color: groupColorMap[group], + barWidth: 3, + isStrokeCapRound: true, + belowBarData: BarAreaData(show: false), + ), + ); + } + + return lineBars; + } + +// Helper functions for chart styles + LineTouchData lineTouchData1() { + return LineTouchData( + touchTooltipData: LineTouchTooltipData( + // tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background + tooltipRoundedRadius: 8, + fitInsideHorizontally: true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, // Adds margin to prevent clipping + ), + handleBuiltInTouches: true, + ); + } + + FlGridData gridData() { + return FlGridData( + show: false, + drawVerticalLine: true, + getDrawingHorizontalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + getDrawingVerticalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + ); + } + + FlTitlesData titlesData1(Set xValues) { + return FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) { + // Format values as millions (M) + String formattedValue; + + if (value % 10 == 0) { +// Check if the value is in the millions or thousands range + if (value >= 1000000) { + formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; + } else if (value >= 1000) { + formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; + } else { + formattedValue = value.toStringAsFixed( + 0); // No decimals for values less than 1000 + } + + // Skip rendering if the formatted value is the same as the last one + if (_lastFormattedValue == formattedValue) { + return const SizedBox.shrink(); // Empty widget for duplicates + } + // Update the last formatted value for the next comparison + _lastFormattedValue = formattedValue; + + print('formattedValueLine - $formattedValue'); + return Text(formattedValue, style: TextStyle(fontSize: 10)); + } + return const SizedBox.shrink(); + + // return Text( + // formattedValue, + // style: TextStyle(color: Colors.black, fontSize: 12), + // ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, // Ensure each year is shown only once + getTitlesWidget: (value, meta) { + if (xValues.contains(value)) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } + }, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide right titles + ), + ); + } + + FlBorderData borderData() { + return FlBorderData( + show: true, + border: Border( + bottom: BorderSide(color: Colors.black, width: 1), // Show bottom border + left: BorderSide(color: Colors.black, width: 1), // Show left border + top: BorderSide.none, // Hide top border + right: BorderSide.none, // Hide right border + ), + ); + } + + // End Of line trend chart + // Define the list of unique colors final List uniqueColors = [ Color(0xFF648CBA), @@ -493,7 +1781,7 @@ class ChartWidget extends StatelessWidget { } List _generateBarGroups( - dynamic chartData, String groupByKey) { + BuildContext context, dynamic chartData, String groupByKey) { Map> groupedData = {}; // Group data by `group_by` and `TIME_PERIOD`, accumulating the values for each group and year @@ -521,6 +1809,8 @@ class ChartWidget extends StatelessWidget { List timePeriods = groupedData.values.first.keys.toList(); print('timePeriods $timePeriods'); + // double barWidth = calculateBarWidth(context, timePeriods.length); + // For each time period, generate a BarChartGroupData for (int i = 0; i < timePeriods.length; i++) { // Intermediate variable to track the current toY value as we stack bars @@ -556,6 +1846,7 @@ class ChartWidget extends StatelessWidget { toY: currentToY, // Use the accumulated `currentToY` value rodStackItems: rodStackItems, // Add the stacked items width: 20, + // width: barWidth, borderRadius: BorderRadius.zero, color: Colors .transparent, // This will act as a container for stacked items @@ -584,70 +1875,186 @@ class ChartWidget extends StatelessWidget { } // Function to dynamically set left axis titles (values) + String? _lastFormattedValue; Widget _generateLeftTitles(double value, TitleMeta meta) { + String formattedValue; + if (value % 1000 == 0) { - return Text(value.toStringAsFixed(0), style: TextStyle(fontSize: 10)); +// Check if the value is in the millions or thousands range + if (value >= 1000000) { + formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; + } else if (value >= 1000) { + formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; + } else { + formattedValue = + value.toStringAsFixed(0); // No decimals for values less than 1000 + } + + // Skip rendering if the formatted value is the same as the last one + if (_lastFormattedValue == formattedValue) { + return const SizedBox.shrink(); // Empty widget for duplicates + } + // Update the last formatted value for the next comparison + _lastFormattedValue = formattedValue; + + print('formattedValue - $formattedValue'); + return Text(formattedValue, style: TextStyle(fontSize: 10)); } - return Container(); + return const SizedBox.shrink(); + // return Container(); } - /// Build the data for the bar groups - List _buildBarGroups() { - return [ - BarChartGroupData( - x: 1, // Identifier for the group - barRods: [ - BarChartRodData( - toY: 40, // Value for the first part of the stack - color: Colors.blue, - width: 10, - ), - BarChartRodData( - toY: 60, // Value for the second part of the stack - color: Colors.green, - width: 10, - ), - ], - ), - BarChartGroupData( - x: 2, - barRods: [ - BarChartRodData( - toY: 30, - color: Colors.orange, - width: 10, - ), - BarChartRodData( - toY: 70, - color: Colors.red, - width: 10, - ), - ], - ), - BarChartGroupData( - x: 3, - barRods: [ - BarChartRodData( - toY: 50, - color: Colors.purple, - width: 10, - ), - BarChartRodData( - toY: 50, - color: Colors.cyan, - width: 10, - ), - ], - ), - ]; + /// Start horizontal rotate and fl_multi_bar bar + // List _buildBarGroups(dynamic chartData) { + // String groupByKey = chartData['group_by']; + // if (groupByKey == null || chartData['response'] == null) { + // return []; + // } + // + // // Extract unique group_by values + // Set groupByValues = extractGroupByValues(chartData, groupByKey); + // + // // Create a map to hold grouped data + // Map>> groupedData = {}; + // for (var entry in chartData['response']) { + // String groupValue = entry['ObsKey'][groupByKey] ?? ''; + // if (groupValue.isNotEmpty) { + // groupedData.putIfAbsent(groupValue, () => []).add(entry); + // } + // } + // + // // Convert grouped data into BarChartGroupData + // List barGroups = []; + // int groupIndex = 0; + // + // groupedData.forEach((key, values) { + // int colorIndex = 0; // Track the color for each bar within the group + // + // List rods = values.map((entry) { + // double yValue = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; + // + // // Cycle through colors for each bar + // final barColor = uniqueColors[colorIndex % uniqueColors.length]; + // colorIndex++; + // + // return BarChartRodData( + // fromY: 0, + // toY: yValue, + // color: barColor, + // width: 20, // Adjust bar width for better visibility + // borderRadius: BorderRadius.circular(4), + // ); + // }).toList(); + // + // barGroups.add(BarChartGroupData( + // x: groupIndex, + // barRods: rods, + // showingTooltipIndicators: [0], + // )); + // groupIndex++; + // }); + // + // return barGroups; + // } + + double _calculateMaxY(dynamic chartData) { + // Dynamically calculate the maximum Y value + List response = chartData['response'] ?? []; + double maxY = 0; + + for (var entry in response) { + double value = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; + if (value > maxY) { + maxY = value; + } + } + + return maxY * 1.1; // Add 10% buffer for better visualization } + List _buildHorizontalRotateBarGroups( + dynamic chartData, Set groupByValues) { + List responseData = chartData['response']; + List barGroups = []; + int colorIndex = 0; + // Iterate through groupByValues and populate BarChartGroupData + for (int i = 0; i < groupByValues.length; i++) { + String groupValue = groupByValues.elementAt(i); + + // Filter data for the current group (grouping by CROP_TYPE) + List groupData = responseData.where((entry) { + return entry['ObsKey'][chartData['group_by']] == groupValue; + }).toList(); + + // Create BarChartRodData for each bar in the group + List barRods = groupData.map((data) { + // Ensure to retrieve and parse 'ObsValue' value (which should be a double) + double value = + double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; + + final barColor = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + + return BarChartRodData( + toY: value, // Use the parsed value + color: barColor, // Dynamic color + width: 20, + // backDrawRodData: BackgroundBarChartRodData( + // show: true, + // toY: 400000, + // color: Colors.grey.shade300, + // ), + ); + }).toList(); + + // Add BarChartGroupData for the group + barGroups.add( + BarChartGroupData( + x: i, + barRods: barRods, + showingTooltipIndicators: + List.generate(barRods.length, (index) => index), + ), + ); + } + + return barGroups; + } + + /// End horizontal rotate and fl_multi_bar bar + @override Widget build(BuildContext context) { - return buildChart(chartData); + return buildChart(chartData, context); } } +double calculateBarWidth(BuildContext context, int totalBars) { + double width = MediaQuery.of(context).size.width; // Chart width + double padding = 16.0; // Space between bars + double availableWidth = (width - 100); + if (totalBars <= 0) { + throw ArgumentError("TotalBars must be greater than 0"); + } + + double barWidth = (availableWidth - (padding * (totalBars - 1))) / totalBars; + + print('barWidth - $barWidth'); + + // Ensure bar width does not exceed availableWidth + if (barWidth > availableWidth) { + barWidth = availableWidth; + } + + // Optional: Add a minimum width constraint if needed + double minWidth = 10.0; // Example minimum width + if (barWidth < minWidth) { + barWidth = minWidth; + } + + return barWidth; +} + class ChartData { final String? x; final double y; diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index e77c3187..f9667d3b 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -10,6 +10,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; import '../components/my_bottom_nav_bar.dart'; @@ -74,6 +75,8 @@ class _ProfileScreenState extends State { // Regular expression to validate Full Name (no special characters) final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); late Future userDetails; + bool _isHoveringDate = false; + bool _isHoveringDropdown = false; @override void initState() { @@ -334,418 +337,450 @@ class _ProfileScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: Icon(Icons.arrow_back_ios_new, color: Colors.black), - onPressed: () { - context.go('/'); - }, - ), - title: Text( - 'My Profile', - style: TextStyle(color: Colors.orange), - ), - actions: [ - TextButton( + return PopScope( + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + context.go('/'); + }, + child: Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: Colors.black), onPressed: () { context.go('/'); }, - child: Text( - 'Logout', - style: TextStyle(color: Colors.orange), + ), + title: Text( + 'My Profile', + style: TextStyle(color: Color(0xFF985400)), + ), + actions: [ + TextButton( + onPressed: () { + context.go('/'); + }, + child: Text( + 'Logout', + style: TextStyle(color: Colors.orange), + ), ), - ), - IconButton( - icon: Icon(Icons.logout, color: Colors.black), - onPressed: () {}, - ), - ], - ), - body: SingleChildScrollView( - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - children: [ - // CircleAvatar( - // radius: 50, - // backgroundImage: _profileImage != null - // ? FileImage(_profileImage!) - // : AssetImage("assets/edit_profile/profile.png") - // as ImageProvider, - // child: Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, // Call `_pickImage` on tap - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ), + IconButton( + icon: Icon(Icons.logout, color: Colors.black), + onPressed: () {}, + ), + ], + ), + body: SingleChildScrollView( + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.only( + top: 20.0, bottom: 20.0, left: 25, right: 25), + child: Column( + children: [ + // CircleAvatar( + // radius: 50, + // backgroundImage: _profileImage != null + // ? FileImage(_profileImage!) + // : AssetImage("assets/edit_profile/profile.png") + // as ImageProvider, + // child: Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, // Call `_pickImage` on tap + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), - Stack( - alignment: Alignment.center, - children: [ - // CircleAvatar with the profile image - CircleAvatar( - radius: 50, - backgroundImage: _profileImage != null - ? FileImage(_profileImage!) - : AssetImage("assets/edit_profile/profile.png") - as ImageProvider, - child: Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, + Stack( + alignment: Alignment.center, + children: [ + // CircleAvatar with the profile image + CircleAvatar( + radius: 50, + backgroundImage: _profileImage != null + ? FileImage(_profileImage!) + : AssetImage("assets/edit_profile/profile.png") + as ImageProvider, + child: Align( + alignment: Alignment.bottomRight, + child: GestureDetector( + onTap: _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), ), ), ), ), - ), - // Conditional loader that shows when _isLoading is true - if (_isLoading) - Positioned( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: - AlwaysStoppedAnimation(Colors.grey), + // Conditional loader that shows when _isLoading is true + if (_isLoading) + Positioned( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation(Colors.grey), + ), ), - ), - ], - ), + ], + ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "User Name", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - hintText: _showHints[0] ? 'Enter the User Name' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Email ID", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - hintText: - _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Full Name*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - enabled: true, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - - //RegExp(r"^[a-zA-Z\s]+$"); - final nameRegex = RegExp( - r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); - - if (!nameRegex.hasMatch(value)) { - return 'Invalid Characters'; - } - return null; - }, - controller: _fullNameController, - focusNode: _focusNodes[2], - decoration: InputDecoration( - hintText: _showHints[2] ? 'Enter the name' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - counterText: '', - ), - maxLength: 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Date of Birth*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _dateController, - focusNode: _focusNodes[3], - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - hintText: _showHints[3] ? 'DD/MM/YYYY' : null, - //_showHints[3] ? 'Select your Date of Birth' : null, - hintStyle: TextStyle(color: Colors.grey), - suffixIcon: const Icon(Icons.arrow_drop_down_sharp), - ), - readOnly: true, - onTap: _pickDate, - validator: _validateDob, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Country/Region*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - DropdownButtonFormField( - value: _selectedCountry, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - labelText: 'Select', - ), - items: _countries - .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) - .toList(), - onChanged: (String? newValue) { - setState(() { - _selectedCountry = newValue; - }); - }, - validator: _validateDropdown, - ), - SizedBox(height: 20), - Row( - children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: showError ? Colors.red : Colors.grey, - width: 1.5, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 10, - ), - Text.rich( - TextSpan( - text: 'I agree to the ', - style: TextStyle(color: Colors.black), - children: [ - TextSpan( - text: 'Terms & Conditions', - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // Add action for Terms & Conditions tap - }, - ), - TextSpan( - text: ' and ', - style: TextStyle(color: Colors.black), - ), - TextSpan( - text: 'Privacy Policy', - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // Add action for Privacy Policy tap - }, - ), - TextSpan( - text: ' of FCSC.', - style: TextStyle(color: Colors.black), - ), - ], - ), - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.visible, - softWrap: true, - ), - ], - ), - ), - ], - ), - if (showError) + SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, + Text( + "User Name", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _usernameController, + focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Enter the User Name' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Email ID", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _emailController, + focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: + // _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Full Name*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + enabled: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + + //RegExp(r"^[a-zA-Z\s]+$"); + final nameRegex = RegExp( + r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); + + if (!nameRegex.hasMatch(value)) { + return 'Invalid Characters'; + } + return null; + }, + controller: _fullNameController, + focusNode: _focusNodes[2], + decoration: InputDecoration( + hintText: _showHints[2] ? 'Enter the name' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + counterText: '', + ), + maxLength: 40, // Set the maximum length to 20 characters + maxLengthEnforcement: MaxLengthEnforcement.enforced, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Date of Birth*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDate = true), + onExit: (_) => setState(() => _isHoveringDate = false), + child: TextFormField( + controller: _dateController, + focusNode: _focusNodes[3], + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: _showHints[3] ? 'DD/MM/YYYY' : null, + //_showHints[3] ? 'Select your Date of Birth' : null, + hintStyle: TextStyle(color: Colors.grey), + + suffixIcon: Container( + width: 45, + padding: EdgeInsets.only(right: 1), + alignment: + Alignment.center, // Center the icon vertically + child: Icon( + Icons.keyboard_arrow_down_sharp, + color: _isHoveringDate ? Colors.black : Colors.grey, ), ), ), + readOnly: true, + onTap: _pickDate, + validator: _validateDob, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDropdown = true), + onExit: (_) => setState(() => _isHoveringDropdown = false), + child: DropdownButtonFormField( + value: _selectedCountry, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + labelText: 'Select', + ), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: + _isHoveringDropdown ? Colors.black : Colors.grey), + items: _countries + .map((item) => DropdownMenuItem( + value: item, + child: Text(item), + )) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCountry = newValue; + }); + }, + validator: _validateDropdown, + ), + ), + SizedBox(height: 20), + Row( + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: + showError ? Color(0xFFb22222) : Color(0xFF92722A), + width: 1, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + Text.rich( + TextSpan( + text: 'I agree to the ', + style: TextStyle(color: Colors.black), + children: [ + TextSpan( + text: 'Terms & Conditions', + style: TextStyle( + color: Color(0xFF648CBA), + decoration: TextDecoration.underline, + fontWeight: FontWeight + .bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // Add action for Terms & Conditions tap + }, + ), + TextSpan( + text: ' and ', + style: TextStyle(color: Colors.black), + ), + TextSpan( + text: 'Privacy Policy', + style: TextStyle( + color: Color(0xFF648CBA), + decoration: TextDecoration.underline, + fontWeight: FontWeight + .bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // Add action for Privacy Policy tap + }, + ), + TextSpan( + text: ' of FCSC.', + style: TextStyle(color: Colors.black), + ), + ], + ), + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.visible, + softWrap: true, + ), + ], + ), + ), ], ), - SizedBox(height: 20), - Center( - child: GestureDetector( - onTap: () { - final userId = widget.userId; - final email = _emailController.text; + if (showError) + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text( + 'Please agree to terms and conditions', + style: TextStyle( + color: Color(0xFFb22222), + fontSize: 12, + ), + ), + ), + ], + ), + SizedBox(height: 20), + Center( + child: GestureDetector( + onTap: () { + final userId = widget.userId; + final email = _emailController.text; - context.go('/createNewPw/$userId/$email'); + context.go('/createNewPw/$userId/$email'); + }, + child: Text( + 'Change Password', + style: TextStyle( + color: Color(0xFF648CBA), + fontSize: 14, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + // decoration: TextDecoration.underline), + ), + ), + ), + ), + SizedBox(height: 20), + // ElevatedButton.icon( + ElevatedButton( + onPressed: () { + if ((_formKey.currentState?.validate() ?? false) && + (isChecked)) { + _formKey.currentState?.save(); + showConfirmationDialog(context); + } else { + setState(() { + showError = + !isChecked; // Show error if the checkbox is not checked + }); + } }, - child: Text( - "Change Password", - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), ), - ), - ), - SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () { - if ((_formKey.currentState?.validate() ?? false) && - (isChecked)) { - _formKey.currentState?.save(); - showConfirmationDialog(context); - } else { - setState(() { - showError = - !isChecked; // Show error if the checkbox is not checked - }); - } - // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { - // _formKey.currentState?.save(); - // showConfirmationDialog(context); - // // if (isChecked) { - // // _formKey.currentState?.save(); - // // // _confirmSaveProfile(); - // // showConfirmationDialog(context); - // // } - // else { - // setState(() { - // showError = !isChecked; // Show error if the checkbox is not checked - // }); - - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)), - // ); - //} - //} - }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - 'Save', - style: TextStyle(color: Colors.white), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, // Center the content + children: [ + Text( + 'Save', + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), // Add space between text and icon + Image.asset( + MiscIconAssetPath.save, + color: Colors.white, + width: 20, + height: 20, + ), + ], ), - ), - ), - ], + ) + ], + ), ), ), ), ), - //bottomNavigationBar: MyBottomNavBar(), ); } } diff --git a/lib/presentation/components/themed_text_field.dart b/lib/presentation/components/themed_text_field.dart index 27a526ec..82b58ce6 100644 --- a/lib/presentation/components/themed_text_field.dart +++ b/lib/presentation/components/themed_text_field.dart @@ -25,13 +25,13 @@ class ThemedFormField extends HookWidget { final isObscured = useState(true); final boxDecoration = BoxDecoration( borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - offset: const Offset(0, 1), - blurRadius: 2, - color: Colors.black.withOpacity(0.1), - ), - ], + // boxShadow: [ + // BoxShadow( + // offset: const Offset(0, 1), + // blurRadius: 2, + // color: Colors.black.withOpacity(0.1), + // ), + // ], ); final obscureBtn = IconButton( onPressed: () => isObscured.value = !isObscured.value, diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 29b2a978..e4f1ee20 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -1,5 +1,6 @@ import 'package:external_repos/external_repos.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -16,8 +17,9 @@ import 'package:uae_stat/presentation/components/lang_toggle.dart'; import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart'; import 'package:uae_stat/presentation/components/space.dart'; import 'package:uae_stat/presentation/components/themed_text_field.dart'; +import 'package:uae_stat/presentation/routes/auth_routes/pocketbase_service.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; - +import 'package:http/http.dart' as http; import '../../Screens/auth_verification/registration.dart'; import 'package:pocketbase/pocketbase.dart'; @@ -26,7 +28,10 @@ import '../../components/my_toggle.dart'; class LoginRoute extends HookConsumerWidget { final pb = PocketBase('https://pb.venbait.in'); + final _pbService = PocketBaseService(); // final _pb = PocketBase('http://127.0.0.1:8090'); + // final bool isLoginScreen; // Pass `true` if this is the login screen + // LoginRoute({Key? key, required this.isLoginScreen}) : super(key: key); LoginRoute({super.key}); dynamic userData; String? role; @@ -93,52 +98,91 @@ class LoginRoute extends HookConsumerWidget { final forgotPwFormKey = GlobalKey(); final email = await showDialog( context: context, - builder: (dialogCtx) => AlertDialog( - title: Text( - context.translate( - 'Reset Password', - 'إعادة تعيين كلمة المرور', + builder: (dialogCtx) => SizedBox( + // width: 850, // Increase dialog width + child: AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), // Rounded corners ), + elevation: 10, + shadowColor: Colors.black12, + title: Text( + context.translate( + 'Reset Password', + 'إعادة تعيين كلمة المرور', + ), + style: TextStyle( + fontSize: 20, + color: Color(0xFF898C81), + fontWeight: FontWeight.w600), + ), + content: Form( + key: forgotPwFormKey, + child: TextFormField( + validator: FieldValidator.email(), + controller: emailCtl, + decoration: InputDecoration( + labelText: context.translate( + 'Email address', + 'عنوان البريد الإلكتروني', + ), + labelStyle: TextStyle( + fontSize: 16, + color: Color(0xFF898C81), // For label text color + // fontWeight: FontWeight.w600, + ), + ), + ), + ), + actions: [ + TextButton( + onPressed: Navigator.of(dialogCtx, rootNavigator: true).pop, + style: TextButton.styleFrom( + // backgroundColor: Colors.white, // Background color + foregroundColor: Color(0xFF92722A), // Font (text) color + side: BorderSide( + color: Color(0xFF92722A)), // Border outline color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8), // Optional: Rounded corners + ), + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 12), // Optional: Padding + ), + child: Text( + context.translate( + 'Return', + 'يعود', + ), + ), + ), + ElevatedButton( + onPressed: () { + final isValid = forgotPwFormKey.currentState!.validate(); + if (!isValid) return; + Navigator.of(dialogCtx, rootNavigator: true).pop( + emailCtl.text, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 15, vertical: 15), // Optional: Adjusts size + shape: RoundedRectangleBorder( + // Optional: Adds rounded corners + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + context.translate( + 'Send Reset Email', + 'إرسال إعادة تعيين البريد الإلكتروني', + ), + ), + ), + ], ), - content: Form( - key: forgotPwFormKey, - child: TextFormField( - validator: FieldValidator.email(), - controller: emailCtl, - decoration: InputDecoration( - labelText: context.translate( - 'Email address', - 'عنوان البريد الإلكتروني', - ), - ), - ), - ), - actions: [ - TextButton( - onPressed: Navigator.of(dialogCtx, rootNavigator: true).pop, - child: Text( - context.translate( - 'Return', - 'يعود', - ), - ), - ), - ElevatedButton( - onPressed: () { - final isValid = forgotPwFormKey.currentState!.validate(); - if (!isValid) return; - Navigator.of(dialogCtx, rootNavigator: true).pop( - emailCtl.text, - ); - }, - child: Text( - context.translate( - 'Send Reset Email', - 'إرسال إعادة تعيين البريد الإلكتروني', - ), - ), - ), - ], ), ); if (email == null) return; @@ -174,7 +218,7 @@ class LoginRoute extends HookConsumerWidget { ), fontWeight: FontWeight.bold, fontSize: 14, - color: MyTheme.topicColor(IndicatorTopic.economy).shade600, + color: Color(0xFF985400), ), ), ); @@ -253,6 +297,22 @@ class LoginRoute extends HookConsumerWidget { if (!context.mounted || session == null) return; final userId = session.id; if (userId.isNotEmpty) { + final url = + Uri.parse("https://pb.venbait.in/api/login_success?id=$userId"); + + try { + final response = await http.get(url); + + if (response.statusCode == 200) { + print("Login success API call successful: ${response.body}"); + } else { + print( + "Failed to call login success API. Status code: ${response.statusCode}"); + } + } catch (e) { + print("Error calling login success API: $e"); + } + await saveUserId(userId); } try { @@ -340,7 +400,10 @@ class LoginRoute extends HookConsumerWidget { ), ), 6.horizontalSpace, - const Icon(Icons.chevron_right_outlined), + const Icon( + Icons.chevron_right_outlined, + color: Colors.white, // Set your desired color here + ) ], ), ), @@ -354,14 +417,21 @@ class LoginRoute extends HookConsumerWidget { 'Email', 'بريد إلكتروني', ), - validator: FieldValidator.email(), + validator: (value) { + if (value == null || value.isEmpty) { + return context.translate('Required', 'مطلوب'); + } + return FieldValidator.email()(value); + }, imgPath: MiscIconAssetPath.person, controller: emailCtl, ), 15.verticalSpace, ThemedFormField( validator: (text) { - if (text!.length < 8) { + if (text == null || text.isEmpty) { + return context.translate('Required', 'مطلوب'); + } else if (text.length < 8) { return 'The password must be at least 8 characters'; } return FieldValidator.password(minLength: 8)(text); @@ -375,9 +445,12 @@ class LoginRoute extends HookConsumerWidget { isObscurable: true, ), // 6.verticalSpace, - Align( - alignment: AlignmentDirectional.topEnd, - child: forgotPwBtn, + Padding( + padding: EdgeInsets.zero, + child: Align( + alignment: AlignmentDirectional.topEnd, + child: forgotPwBtn, + ), ), 10.verticalSpace, loginBtn, @@ -403,7 +476,7 @@ class LoginRoute extends HookConsumerWidget { 10.verticalSpace, Text( context.translate( - 'Please login to access UAE’s key official statistics', + 'Please login to access \n UAE’s key official statistics', 'يرجى تسجيل الدخول للوصول إلى الإحصاءات الرسمية الرئيسية لدولة الإمارات العربية المتحدة', ), textAlign: TextAlign.center, @@ -412,9 +485,9 @@ class LoginRoute extends HookConsumerWidget { 'Roboto', 'NotoKufi', ), - fontSize: 18, + fontSize: context.translate(18.0, 14.0), color: const Color(0xff898C81), - fontWeight: FontWeight.w700, + fontWeight: FontWeight.w600, ), ), ], @@ -429,7 +502,7 @@ class LoginRoute extends HookConsumerWidget { 'Roboto', 'NotoKufi', ), - fontSize: 18, + fontSize: 14, fontWeight: FontWeight.bold, ), children: [ @@ -451,63 +524,71 @@ class LoginRoute extends HookConsumerWidget { 'سجل الان', ), style: TextStyle( - color: MyTheme.topicColor(IndicatorTopic.economy).shade600, + color: Color(0xFF985400), ), ), ], ), ), ); - final continueAsGuestBtn = SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => - context.go('/myhomepage'), - //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), - style: ButtonStyle( - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - padding: const WidgetStatePropertyAll( - EdgeInsets.symmetric(vertical: 10.5), - ), - textStyle: WidgetStatePropertyAll( - TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - ), - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - backgroundColor: WidgetStatePropertyAll( - MyTheme.topicColor(IndicatorTopic.environment), - ), - foregroundColor: const WidgetStatePropertyAll( - Colors.white, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - context.translate( - 'Continue as Guest', - 'استمر كضيف', - ), - ), - 6.horizontalSpace, - const Icon(Icons.chevron_right_outlined), - ], - ), - ), - ); + // final continueAsGuestBtn = SizedBox( + // width: double.infinity, + // child: ElevatedButton( + // onPressed: () async { + // final prefs = await SharedPreferences.getInstance(); + // prefs.clear(); + // final userId = 'guest'; + // if (userId.isNotEmpty) { + // await saveUserId(userId); + // } + // context.go('/myhomepage'); + // //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + // }, + // //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + // style: ButtonStyle( + // shape: WidgetStatePropertyAll( + // RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // ), + // padding: const WidgetStatePropertyAll( + // EdgeInsets.symmetric(vertical: 10.5), + // ), + // textStyle: WidgetStatePropertyAll( + // TextStyle( + // fontFamily: context.translate( + // 'Roboto', + // 'NotoKufi', + // ), + // fontSize: 16, + // fontWeight: FontWeight.w600, + // ), + // ), + // backgroundColor: WidgetStatePropertyAll( + // MyTheme.topicColor(IndicatorTopic.environment), + // ), + // foregroundColor: const WidgetStatePropertyAll( + // Colors.white, + // ), + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // context.translate( + // 'Continue as Guest', + // 'استمر كضيف', + // ), + // ), + // 6.horizontalSpace, + // const Icon(Icons.chevron_right_outlined), + // ], + // ), + // ), + // ); final fcscBanner = Image.asset( BannerAssetPath.fcsc, - height: 56, + height: 40, ); final screenWidth = MediaQuery.of(context).size.width; final listViewHorizontalPadding = @@ -520,31 +601,64 @@ class LoginRoute extends HookConsumerWidget { 36.verticalSpace, Align( alignment: AlignmentDirectional.topEnd, - child: MyToggle(isOn: locale?.languageCode == 'en', + child: MyToggle( + isOn: locale?.languageCode == 'en', knobTextWhenOn: 'ع', knobTextWhenOff: 'EN', pathColorWhenOn: Colors.grey.shade300, pathColorWhenOff: Colors.grey.shade300, - onTap: (){ + onTap: () { ref.read(localeProvider.notifier).toggleLocale(); - },), + }, + ), ), 16.verticalSpace, helloAndPleaseLoginTexts, 42.verticalSpace, form, - 20.verticalSpace, + 15.verticalSpace, dontHaveAnAccountRegisterBtn, - 36.verticalSpace, - continueAsGuestBtn, - 72.verticalSpace, + 20.verticalSpace, + // continueAsGuestBtn, + 95.verticalSpace, fcscBanner, ], ); - final bgScaffold = Scaffold( - backgroundColor: Colors.white, - body: SafeArea(child: scaffoldBody), + // final bgScaffold = Scaffold( + // backgroundColor: Colors.white, + // body: SafeArea(child: scaffoldBody), + // ); + // return bgScaffold; + return PopScope( + canPop: false, // Allow back navigation only if not login screen + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + _showExitConfirmation(context); // Show exit confirmation dialog + }, + child: Scaffold( + backgroundColor: Colors.white, + body: SafeArea(child: scaffoldBody), + ), + ); + } + + void _showExitConfirmation(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("Exit App"), + content: Text("Are you sure you want to exit?"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), // Close dialog + child: Text("Cancel"), + ), + TextButton( + onPressed: () => SystemNavigator.pop(), // Exit the app + child: Text("Exit"), + ), + ], + ), ); - return bgScaffold; } } diff --git a/lib/presentation/routes/auth_routes/pocketbase_service.dart b/lib/presentation/routes/auth_routes/pocketbase_service.dart new file mode 100644 index 00000000..8e5cc2c8 --- /dev/null +++ b/lib/presentation/routes/auth_routes/pocketbase_service.dart @@ -0,0 +1,66 @@ +import 'package:pocketbase/pocketbase.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class PocketBaseService { + final PocketBase pb = PocketBase('https://pb.venbait.in'); + + // Save session to local storage + Future saveSession() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('pb_auth', pb.authStore.token); + await prefs.setString('pb_user', pb.authStore.model.toJson()); + await prefs.setInt('pb_auth_time', DateTime.now().millisecondsSinceEpoch); + } + + // Load session from local storage + Future loadSession() async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('pb_auth'); + final userJson = prefs.getString('pb_user'); + + if (token != null && userJson != null) { + pb.authStore + .save(token, RecordModel.fromJson(userJson as Map)); + + if (!pb.authStore.isValid || isSessionExpired(prefs)) { + await clearSession(); + return false; + } + return true; + } + return false; + } + + // Check if the session is expired (4 days) + bool isSessionExpired(SharedPreferences prefs) { + final savedTime = prefs.getInt('pb_auth_time') ?? 0; + final currentTime = DateTime.now().millisecondsSinceEpoch; + const sessionDuration = Duration(days: 4); + return (currentTime - savedTime) > sessionDuration.inMilliseconds; + } + + // User login + Future loginUser(String email, String password) async { + try { + await pb.collection('users').authWithPassword(email, password); + await saveSession(); + return true; + } catch (e) { + return false; + } + } + + // User logout + Future logoutUser() async { + pb.authStore.clear(); + await clearSession(); + } + + // Clear session from local storage + Future clearSession() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('pb_auth'); + await prefs.remove('pb_user'); + await prefs.remove('pb_auth_time'); + } +} diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index 260f7692..e4d22b45 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -163,43 +163,26 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import '../../drawer_routes/custom_drawer_routes.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:http/http.dart' as http; -class MyHomePage extends StatefulWidget { +class MyHomePage extends ConsumerStatefulWidget { const MyHomePage({super.key}); @override - State createState() => _MyHomePageState(); + ConsumerState createState() => _MyHomePageState(); } -void handleInfoCardClick(BuildContext context, String data, Color color) { - // Handle navigation and pass dynamic data - print(data); - final dataSets = data; - if (dataSets != null) { - // Perform navigation - context.go('/chartScreen/$dataSets'); - } else { - // Show error if dataSet is null - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('No dataset available')), - ); - } - // final dataSets = data; - // print(dataSets); - // if (dataSets == 'hotels') { - // context.go('/chartScreen/$dataSets'); - // } else if (dataSets == 'divorces') { - // context.go('/chartScreen/$dataSets'); - // } else if (dataSets == 'marriages') { - // context.go('/Chart/$dataSets'); - // } -} - -class _MyHomePageState extends State { +class _MyHomePageState extends ConsumerState { @override Widget build(BuildContext context) { double myheight = MediaQuery.of(context).size.height; @@ -215,28 +198,660 @@ class _MyHomePageState extends State { } } -class EconomyStatsWidget extends StatefulWidget { +class EconomyStatsWidget extends ConsumerStatefulWidget { const EconomyStatsWidget({super.key}); @override - EconomyStatsState createState() => EconomyStatsState(); + ConsumerState createState() => EconomyStatsState(); } -class EconomyStatsState extends State { +class EconomyStatsState extends ConsumerState { List data = []; - + final _pb = PocketBase('https://pb.venbait.in'); bool isLoading = true; + final GlobalKey cardTopicKey = GlobalKey(); + final GlobalKey cardsKey = GlobalKey(); + late TutorialCoachMark tutorialCoachMark; + late List homeTargets; + late List previousHomeTargets; + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read(previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + } + + //Method waits app tour to render Targets + void _starTourRender() { + if (cardTopicKey.currentContext != null) { + print('render finished'); + _showHomeTour(); + } else { + Future.delayed(Duration(milliseconds: 100), _starTourRender); + } + } + + //Method to Start App Tour + void _showHomeTour() { + final homeTour = ref.watch(homeTourProvider); + final previousHomeTour = ref.watch(previousHomeTourProvider); + +// Check and show tutorials + if (!homeTour) { + // Show Home Tour + _initTarget(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: homeTargets, + onFinish: () { + ref.read(homeTourProvider.notifier).state = true; + ref.read(chartsTourProvider.notifier).state = false; + debugPrint('Home Tutorial Finished'); + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + }, + )..show(context: context); + } else if (!previousHomeTour) { + _initPreviousTarget(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousHomeTargets, + onFinish: () { + ref.read(previousHomeTourProvider.notifier).state = true; + debugPrint('Previous Home Tutorial Finished'); + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + }, + )..show(context: context); + } + } + + void _initTarget() { + final double screenWidth = MediaQuery.of(context).size.width; + homeTargets = [ + TargetFocus( + identify: 'cardTopicKey', + keyTarget: cardTopicKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.home_topic, + alignment: ContentAlign.bottom, + gap: 0, + space: 43, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * 0.70, + child: Stack( + children: [ + Positioned( + bottom: 20, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only( + right: 10), // Adjust the value as needed + child: Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 20, + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + )), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), + align: ContentAlign.bottom, + child: Container( + width: 200, + height: 77, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'cardsKey', + keyTarget: cardsKey, + shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, + // targetPosition:TargetPosition(), + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.economy, + alignment: ContentAlign.bottom, + gap: 0, + space: 43, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + )), + const SizedBox(height: 3), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, + color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), + align: ContentAlign.bottom, + child: Container( + width: 200, + height: 77, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], + ), + ), + ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: MediaQuery.of(context).size.width , + // height:MediaQuery.of(context).size.height*0.69 , + // child: Stack( + // children: [ + // Positioned( + // left: MediaQuery.of(context).size.width*0.40, + // top: _imageTopicPosition.dy-29, + // child: Image.asset( + // 'assets/app_tour/right_down.png', + // width: 40, + // height: 90, + // ), + // ), + // ] + // ), + // + // ), + // ), + ], + ), + ]; + } + + void _initPreviousTarget() { + final double screenWidth = MediaQuery.of(context).size.width; + previousHomeTargets = [ + TargetFocus( + identify: 'cardsKey', + keyTarget: cardsKey, + shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.economy, + alignment: ContentAlign.bottom, + gap: 0, + space: 43, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + )), + SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, + color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), + width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + ref + .read(previousHomeTourProvider + .notifier) + .state = true; + ref + .read(chartsTourProvider.notifier) + .state = false; + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), + align: ContentAlign.bottom, + child: Container( + width: 200, + height: 77, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'cardTopicKey', + keyTarget: cardTopicKey, + shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.home_topic, + alignment: ContentAlign.bottom, + gap: 0, + space: 43, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * 0.69, + child: Stack( + children: [ + Positioned( + bottom: 20, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), + align: ContentAlign.bottom, + child: Container( + width: 200, + height: 77, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], + ), + ), + ), + ], + ), + ]; + } + @override void initState() { super.initState(); - fetchData(); + final locale = ref.read(localeProvider); + fetchData(locale?.languageCode ?? 'en'); + _fetchUserData(); } - Future fetchData() async { +// @override +// void didChangeDependencies() { +// super.didChangeDependencies(); +// // Access the provider here +// final locale = ref.watch(localeProvider); +// fetchData(locale); // Pass the locale to the fetchData method +// } + + Future getUserId() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('userId'); // Retrieve the userId + } + + Future _fetchUserData() async { + try { + final userId = await getUserId(); + print('EDIT PROFILE isPageLoad'); + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + print('adminToken- $adminToken'); + final userDetailsResponse = await _pb.collection('users').getOne( + userId!, + headers: { + 'Authorization': 'Bearer $adminToken', + }, + ); + final loginCount = userDetailsResponse.data['login_count']; + print(loginCount); + if (loginCount == 1) { + setState(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + _starTourRender(); + }); + }); + } + } catch (e) { + print('Error fetching user details: $e'); + } + } + + Future fetchData(locale) async { const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; try { - final response = await http.get(Uri.parse(baseUrl)); + final response = await http.get(Uri.parse(baseUrl + '?language=$locale')); if (response.statusCode == 200) { setState(() { data = json.decode(response.body); @@ -283,6 +898,10 @@ class EconomyStatsState extends State { } Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchData(localeCode); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; @@ -336,6 +955,9 @@ class EconomyStatsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ RoundedCornerContainer( + key: mainTopic['main_topic'] == 'ECONOMY' + ? cardTopicKey + : null, text: mainTopic['main_topic'], backgroundColor: backgroundColor, textStyle: const TextStyle( @@ -347,6 +969,9 @@ class EconomyStatsState extends State { SizedBox( height: myheight / 4.8, // Set dynamic height child: Container( + key: mainTopic['main_topic'] == 'ECONOMY' + ? cardsKey + : null, // color: Colors.grey[200], // Set a background color for the scrollable container child: ConstrainedBox( constraints: BoxConstraints( @@ -386,7 +1011,8 @@ class EconomyStatsState extends State { children: _buildRows( [tileData], borderColor, - mainTopic['color_pattern']), + mainTopic['color_pattern'], + mainTopic['main_topic']), ), ), ], @@ -452,8 +1078,8 @@ class EconomyStatsState extends State { // return rows; // } -List _buildRows( - List> tileData, Color borderColor, color_pattern) { +List _buildRows(List> tileData, Color borderColor, + String colorPattern, String mainTopic) { // tileData.sort((a, b) => // (a['data_set_list_order'] ?? 0).compareTo(b['data_set_list_order'] ?? 0),); @@ -479,8 +1105,10 @@ List _buildRows( dataset: tile['data_set']!, bordercolor: borderColor, textcolor: borderColor, + colorPattern: colorPattern, backgroundColor: Colors.white, // Set background color for InfoCard + mainTopic: mainTopic, onTap: () {}, ), ); @@ -529,10 +1157,12 @@ class RoundedCornerContainer extends StatelessWidget { class InfoCard extends StatelessWidget { final String title; + final String mainTopic; final String subtitle; final String value; final String dataset; final Color bordercolor; + final String colorPattern; final Color? textcolor; final VoidCallback onTap; final Color backgroundColor; @@ -544,9 +1174,11 @@ class InfoCard extends StatelessWidget { required this.value, required this.dataset, required this.bordercolor, + required this.colorPattern, this.textcolor, required this.onTap, required this.backgroundColor, + required this.mainTopic, }) : super(key: key); @override @@ -554,11 +1186,15 @@ class InfoCard extends StatelessWidget { double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; + print("colorPatternInfo -$colorPattern $mainTopic"); + final encodedMainTopic = Uri.encodeComponent(mainTopic); + final encodedTitle = Uri.encodeComponent(title); + final encodedKey = Uri.encodeQueryComponent('home'); + return GestureDetector( onTap: () { - // Call handleInfoCardClick and pass the title and color - handleInfoCardClick( - context, dataset, bordercolor); // Pass 'title' to the function + context.go( + '/chartScreen/$dataset?bgColor=$colorPattern&mainTopic=$encodedMainTopic&title=$encodedTitle&key=$encodedKey'); }, child: Container( height: myheight / 12, @@ -589,6 +1225,7 @@ class InfoCard extends StatelessWidget { fontSize: 11, fontWeight: FontWeight.w400, // color: Colors.black, + fontFamily: 'Roboto', color: Color(0xFF000000), ), ), @@ -608,6 +1245,7 @@ class InfoCard extends StatelessWidget { textAlign: TextAlign.center, style: const TextStyle( fontSize: 11, + fontFamily: 'Roboto', fontWeight: FontWeight.w400, color: Color(0xFF8E8E8E), ), @@ -626,8 +1264,9 @@ class InfoCard extends StatelessWidget { Text( value, style: TextStyle( - fontSize: 26, + fontSize: 18, fontWeight: FontWeight.w900, + fontFamily: 'Roboto', color: textcolor ?? Colors.black, ), ), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart new file mode 100644 index 00000000..75e2ccbd --- /dev/null +++ b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; + + +class BookMark extends ConsumerStatefulWidget { + const BookMark({super.key}); + + @override + ConsumerState createState() => _BookMarkState(); +} + +class _BookMarkState extends ConsumerState { + // Example dynamic data + final List> dataList = [ + { + 'title': 'GDP(Constant)', + 'subtitle': '(2022)(AED)', + 'value': '1.62T', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Trade Value', + 'subtitle': '(Jan-Jan 2024)-AED', + 'value': '215.4B', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Electricity Production', + 'subtitle': '(2022) (GWh)', + 'value': '155,438', + 'valueColor': Color(0xFFAA8E83), + 'isBookmark': true + }, + { + 'title': 'Crude Oil Production', + 'subtitle': '(2022) (1000 b/d)', + 'value': '3,064', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Quantitiy of Export Oil', + 'subtitle': '(2022) (1000 b/d)', + 'value': '2717', + 'valueColor': Color(0xFF7DAFBC), + 'isBookmark': true + }, + { + 'title': 'Desalinated Water Production', + 'subtitle': '(2022) (MCM)', + 'value': '1823.8', + 'valueColor': Color(0xFF7DAFBC), + 'isBookmark': true + }, + ]; + + @override + Widget build(BuildContext context) { + final filteredList = dataList.where((item) => item['isBookmark'] == true) + .toList(); + return BaseScaffold( + title: Text('Bookmark'), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Two columns + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + mainAxisExtent: 100, + ), + itemCount: filteredList.length, + itemBuilder: (context, index) { + return _buildBox(filteredList[index], context); + }, + ), + ), + ); + } + + // Widget for creating a box + Widget _buildBox(Map data, BuildContext context) { + return GestureDetector( + onTap: () { + debugPrint("Box clicked: ${data['title']}"); + }, + child: Container( + width: MediaQuery + .of(context) + .size + .width * 0.4, + height: 100, + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: data['valueColor']), + ), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Align( + alignment: Alignment.center, + child: Text( + data['title'] ?? '', + style: TextStyle( + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + // Truncate long text + maxLines: 1, // Limit to 1 line + ), + ), + ), + GestureDetector( + onTap: () { + debugPrint("Icon clicked in: ${data['title']}"); + setState(() { + // Change isOpen to false when icon is clicked + int index = dataList.indexWhere( + (item) => item['title'] == data['title'],); + if (index != -1) { + dataList[index]['isBookmark'] = false; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Removed From Bookmark', + style: TextStyle(color: Color(0xFF2F692C), + fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Failed to Remove', + style: TextStyle(color: Color(0xFFEB5F24), + fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFF544C4C), + duration: Duration(seconds: 2), + ), + ); + } + }); + }, + child: Icon( + Icons.bookmarks_rounded, + color: Colors.black, + size: 20, + ), + ), + ], + ), + Text( + data['subtitle'] ?? '', + style: TextStyle( + fontWeight: FontWeight.w500, color: Colors.grey), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + Text( + data['value'] ?? '', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: data['valueColor'], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index 3828a89e..55d92b96 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -11,10 +11,12 @@ import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../../../Screens/auth_verification/changepassword.dart'; -import '../custom_drawer_routes.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; class EditProfile extends StatefulWidget { + const EditProfile({super.key}); + @override State createState() => _EditProfileState(); } @@ -31,7 +33,7 @@ class _EditProfileState extends State { true, true, true, - true + true, ]; // One for each TextField String _getControllerText(int index) { @@ -57,7 +59,7 @@ class _EditProfileState extends State { 'United Arab Emirates', 'United States', 'India', - 'Canada' + 'Canada', ]; String? _selectedCountry; bool isChecked = false; @@ -65,11 +67,17 @@ class _EditProfileState extends State { final _picker = ImagePicker(); File? _profileImage; String _avatarUrl = ''; + bool isPageLoad = false; // Regular expression to validate Full Name (no special characters) - final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); + final RegExp _nameRegExp = RegExp(r'^[a-zA-Z\s]+$'); late Future userDetails; dynamic userId; + dynamic recordId; + dynamic collectionId; + dynamic role; + + bool _isHoveringDropdown = false; @override void initState() { @@ -147,16 +155,22 @@ class _EditProfileState extends State { Future _fetchUserData() async { try { + setState(() { + isPageLoad = true; // Show loader + print('Im isPageLoad'); + }); + print('EDIT PROFILE isPageLoad'); final adminAuth = await _pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - print('adminToken- ${adminToken}'); + print('adminToken- $adminToken'); final userDetailsResponse = await _pb.collection('users').getOne( userId, headers: { 'Authorization': 'Bearer $adminToken', }, ); + print('userDetails: $userDetailsResponse'); setState(() { _usernameController.text = userDetailsResponse.data['uname'] ?? ''; @@ -179,9 +193,10 @@ class _EditProfileState extends State { // Set the avatar URL String avatarFilename = userDetailsResponse.data['avatar'] ?? ''; - String recordId = userId; - String collectionId = + recordId = userId; + collectionId = userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; + role = userDetailsResponse.data['role']; if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = @@ -189,6 +204,7 @@ class _EditProfileState extends State { } else { _avatarUrl = ''; // Reset to default or empty } + isPageLoad = false; }); } catch (e) { print('Error fetching user details: $e'); @@ -229,7 +245,7 @@ class _EditProfileState extends State { if (fileExtension == 'jpg' || fileExtension == 'jpeg' || fileExtension == 'png' || - fileExtension == "heic") { + fileExtension == 'heic') { setState(() { _isLoading = false; _profileImage = File(pickedFile.path); @@ -250,13 +266,17 @@ class _EditProfileState extends State { final DateTime today = DateTime.now(); final DateTime initialDate = _selectedDate ?? today.subtract( - const Duration(days: 365 * 18)); // Default to 18 years ago - final DateTime firstDate = today.subtract(const Duration( - days: 365 * 100)); // Allow picking dates back to 100 years ago + const Duration(days: 365 * 18), + ); // Default to 18 years ago + final DateTime firstDate = today.subtract( + const Duration( + days: 365 * 100, + ), + ); // Allow picking dates back to 100 years ago final DateTime lastDate = today; // Allow picking dates up to today // Updated Date format to DD/MM/YYYY - final DateFormat _dateFormat = DateFormat('dd/MM/yyyy'); + final DateFormat dateFormat = DateFormat('dd/MM/yyyy'); final DateTime? pickedDate = await showDatePicker( context: context, @@ -268,7 +288,7 @@ class _EditProfileState extends State { if (pickedDate != null && pickedDate != _selectedDate) { setState(() { _selectedDate = pickedDate; - _dateController.text = _dateFormat.format(pickedDate); + _dateController.text = dateFormat.format(pickedDate); }); } } @@ -314,7 +334,7 @@ class _EditProfileState extends State { try { String userID = userId; - print("ShowConfirmationuserID - $userID "); + print('ShowConfirmationuserID - $userID '); // Retrieve data from the country/region field String countryRegion = _selectedCountry ?? ''; // Ensure the country is selected @@ -330,10 +350,12 @@ class _EditProfileState extends State { // If profile image exists, add it if (_profileImage != null) { - request.files.add(await http.MultipartFile.fromPath( - 'avatar', - _profileImage!.path, - )); + request.files.add( + await http.MultipartFile.fromPath( + 'avatar', + _profileImage!.path, + ), + ); } // Add headers (e.g., authorization) @@ -341,25 +363,26 @@ class _EditProfileState extends State { // Send the request final response = await request.send(); - print(response); - + // print(response); + print('ShowConfirmationuser response - $response '); // Handle response if (response.statusCode == 200) { _resetFormFields(); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Profile updated successfully!")), + SnackBar(content: Text('Profile updated successfully!')), ); + // print('ShowConfirmation userData - $userData '); context.go('/myhomepage'); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: - Text("Failed to update profile: ${response.statusCode}")), + content: Text('Failed to update profile: ${response.statusCode}'), + ), ); } } catch (error) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Failed to update profile: $error")), + SnackBar(content: Text('Failed to update profile: $error')), ); } } else { @@ -392,9 +415,9 @@ class _EditProfileState extends State { try { // Simulate a network delay of 2 seconds (this mimics loading an image from the internet) // await Future.delayed(Duration(seconds: 3)); - print("Image loaded from URL: $url"); + print('Image loaded from URL: $url'); } catch (e) { - print("Error loading image: $e"); + print('Error loading image: $e'); throw Exception('Failed to load image'); } } @@ -402,7 +425,8 @@ class _EditProfileState extends State { @override Widget build(BuildContext context) { return BaseScaffold( - title: Text(AppLocalizations.of(context)!.my_profile), + title: Text(AppLocalizations.of(context)!.my_profile, + style: TextStyle(color: Color(0xFF985400)),), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0), @@ -412,69 +436,143 @@ class _EditProfileState extends State { child: Form( key: _formKey, child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - children: [ - // CircleAvatar( - // radius: 50, - // backgroundImage: _profileImage != null - // ? FileImage( - // _profileImage!) // If a local file is selected - // : _avatarUrl.isNotEmpty - // ? NetworkImage(_avatarUrl) // Load from URL - // : AssetImage( - // "assets/edit_profile/profile.png") - // as ImageProvider, - // - // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, - // - // child: - // Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, // Call `_pickImage` on tap - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ), + // padding: const EdgeInsets.all(20.0), +padding: const EdgeInsets.only( + top: 20.0, bottom: 20.0, left: 25, right: 25), + child: isPageLoad + ? Center( + child: CircularProgressIndicator(), + ) + : Column( + children: [ + // CircleAvatar( + // radius: 50, + // backgroundImage: _profileImage != null + // ? FileImage( + // _profileImage!) // If a local file is selected + // : _avatarUrl.isNotEmpty + // ? NetworkImage(_avatarUrl) // Load from URL + // : AssetImage( + // "assets/edit_profile/profile.png") + // as ImageProvider, + // + // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, + // + // child: + // Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, // Call `_pickImage` on tap + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), - Stack( - alignment: Alignment.center, - children: [ - CircleAvatar( - radius: 50, - backgroundImage: _profileImage != null - ? FileImage( - _profileImage!) // If a local file is selected - : _avatarUrl.isNotEmpty - ? NetworkImage( - _avatarUrl) // Load from URL - : AssetImage( - "assets/edit_profile/profile.png") - as ImageProvider, - child: _avatarUrl.isNotEmpty - ? FutureBuilder( - future: _loadImage(_avatarUrl), - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - return Center( - child: Icon(Icons.error), - ); - } else { - return Align( + // Stack( + // alignment: Alignment.center, + // children: [ + // // FutureBuilder to load the image + // FutureBuilder( + // future: _loadProfileImage(), + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.waiting) { + // // While the image is loading, show a progress indicator + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else if (snapshot.hasError || snapshot.data == null) { + // // If there's an error or no image, show an error icon + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else { + // // Display the loaded image + // return CircleAvatar( + // radius: 50, + // backgroundImage: snapshot.data, + // child: Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ); + // } + // }, + // ), + // ], + // ), + + Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: 50, + backgroundImage: _profileImage != null + ? FileImage( + _profileImage!) // If a local file is selected + : _avatarUrl.isNotEmpty + ? NetworkImage( + _avatarUrl) // Load from URL + : AssetImage( + 'assets/edit_profile/profile.png') + as ImageProvider, + child: _avatarUrl.isNotEmpty + ? FutureBuilder( + future: _loadImage(_avatarUrl), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Center( + child: + CircularProgressIndicator(), + ); + } else if (snapshot.hasError) { + return Center( + child: Icon(Icons.error), + ); + } else { + return Align( + alignment: + Alignment.bottomRight, + child: GestureDetector( + onTap: + _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: + Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), + ), + ), + ); + } + }, + ) + : Align( alignment: Alignment.bottomRight, child: GestureDetector( onTap: @@ -489,345 +587,413 @@ class _EditProfileState extends State { ), ), ), - ); - } - }, - ) - : Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: - _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, ), + ), + if (_isLoading) + Positioned( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation( + Colors.grey, ), ), ), - ), - if (_isLoading) - Positioned( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.grey), - ), + ], ), - ], - ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.register_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - // hintText: _showHints[0] ? 'Mohammad Hassan' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.email_id, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.full_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - enabled: false, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - - //RegExp(r"^[a-zA-Z\s]+$"); - final nameRegex = RegExp( - r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); - if (!nameRegex.hasMatch(value)) { - return 'Invalid Characters'; - } - return null; - }, - controller: _fullNameController, - focusNode: _focusNodes[2], - decoration: InputDecoration( - // hintText: _showHints[2] ? 'Mohammad' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // counterText: '', - enabled: !_isProfileCompleted, - ), - maxLength: - 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Date of Birth*", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _dateController, - focusNode: _focusNodes[3], - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // hintText: _showHints[3] ? 'Select your Date of Birth' : null, - hintStyle: TextStyle(color: Colors.grey), - suffixIcon: const Icon(Icons.arrow_drop_down_sharp), - enabled: !_isProfileCompleted, - ), - readOnly: true, - onTap: _pickDate, - validator: _validateDob, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.region, - style: TextStyle( - fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - DropdownButtonFormField( - value: _selectedCountry, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - labelText: 'Select', - ), - items: _countries - .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) - .toList(), - onChanged: (String? newValue) { - setState(() { - _selectedCountry = newValue; - }); - }, - validator: _validateDropdown, - ), - SizedBox(height: 20), - if (!_isProfileCompleted) // Conditional rendering - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + SizedBox(height: 20), Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: - showError ? Colors.red : Colors.grey, - width: 1.5, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox(height: 10), - Text.rich( - TextSpan( - text: AppLocalizations.of(context)! - .agree, - style: - TextStyle(color: Colors.black), - children: [ - TextSpan( - text: AppLocalizations.of( - context)! - .terms_conditions, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Terms & Conditions tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context)! - .t_and, - style: TextStyle( - color: Colors.black), - ), - TextSpan( - text: AppLocalizations.of( - context)! - .privacy_policy, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Privacy Policy tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context)! - .conditions, - style: TextStyle( - color: Colors.black), - ), - ], - ), - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.visible, - softWrap: true, - ), - ], + Text( + AppLocalizations.of(context)!.register_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, ), ), ], ), - if (showError) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: - const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, - ), + SizedBox(height: 10), + TextFormField( + controller: _usernameController, + focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Mohammad Hassan' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.email_id, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _emailController, + focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.full_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + enabled: false, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + + //RegExp(r"^[a-zA-Z\s]+$"); + final nameRegex = RegExp( + r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$", + ); + if (!nameRegex.hasMatch(value)) { + return 'Invalid Characters'; + } + return null; + }, + controller: _fullNameController, + focusNode: _focusNodes[2], + decoration: InputDecoration( + // hintText: _showHints[2] ? 'Mohammad' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // counterText: '', + enabled: !_isProfileCompleted, + ), + maxLength: + 40, // Set the maximum length to 20 characters + maxLengthEnforcement: + MaxLengthEnforcement.enforced, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.dob, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _dateController, + focusNode: _focusNodes[3], + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // hintText: _showHints[3] ? 'Select your Date of Birth' : null, + hintStyle: TextStyle(color: Colors.grey), + suffixIcon: Container( + width: 45, + padding: EdgeInsets.only(right: 1), + alignment: Alignment + .center, // Center the icon vertically + child: Icon( + Icons.keyboard_arrow_down_sharp, + color: Colors.grey, + ), + ), + enabled: !_isProfileCompleted, + ), + readOnly: true, + onTap: _pickDate, + validator: _validateDob, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.region, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => + setState(() => _isHoveringDropdown = true), + onExit: (_) => + setState(() => _isHoveringDropdown = false), + child: DropdownButtonFormField( + value: _selectedCountry, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + labelText: 'Select', + ), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: _isHoveringDropdown + ? Colors.black + : Colors.grey), + items: _countries + .map( + (item) => DropdownMenuItem( + value: item, + child: Text(item), ), + ) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCountry = newValue; + }); + }, + validator: _validateDropdown, + ), + ), + SizedBox(height: 20), + if (!_isProfileCompleted) // Conditional rendering + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: showError + ? Colors.red + : Colors.grey, + width: 1.5, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Text.rich( + TextSpan( + text: AppLocalizations.of( + context)! + .agree, + style: TextStyle( + color: Colors.black), + children: [ + TextSpan( + text: AppLocalizations.of( + context, + )! + .terms_conditions, + style: TextStyle( + // color: Colors.blue, + color: + Color(0xFF648CBA), + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Terms & Conditions tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .t_and, + style: TextStyle( + color: Colors.black, + ), + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .privacy_policy, + style: TextStyle( + // color: Colors.blue, + color: Color(0xFF648CBA), + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Privacy Policy tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .conditions, + style: TextStyle( + color: Colors.black, + ), + ), + ], + ), + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.visible, + softWrap: true, + ), + ], + ), + ), + ], + ), + if (showError) + Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 10.0), + child: Text( + 'Please agree to terms and conditions', + style: TextStyle( + color: Colors.red[700], + fontSize: 12, + ), + ), + ), + ], + ), + ], + ), + SizedBox(height: 20), + Center( + child: GestureDetector( + onTap: () { + final email = _emailController.text; + context.go( + '/createNewPw/$userId/$email?key=editProfile'); + }, + child: Text( + AppLocalizations.of(context)! + .change_password, + style: TextStyle( + color: Color(0xFF648CBA), + fontSize: 14, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + // decoration: TextDecoration.underline + ), + ), + ), + ), + SizedBox(height: 20), + // ElevatedButton.icon( + // onPressed: () { + // showConfirmationDialog(context); + // }, + // icon: Icon( + // Icons.save, + // color: Colors.white, + // ), + // label: Text( + // AppLocalizations.of(context)!.save, + // style: TextStyle(color: Colors.white), + // ), + // style: ElevatedButton.styleFrom( + // backgroundColor: Color(0xFF92722A), + // minimumSize: Size(double.infinity, 50), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // ), + ElevatedButton( + onPressed: () { + // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { + // _formKey.currentState?.save(); + showConfirmationDialog(context); + // } else { + // setState(() { + // showError = !isChecked; // Show error if the checkbox is not checked + // }); + // } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, // Center the content + children: [ + Text( + AppLocalizations.of(context)!.save, + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), // Add space between text and icon + Image.asset( + MiscIconAssetPath.save, + color: Colors.white, + width: 20, + height: 20, ), ], ), + ) + ], ), - SizedBox(height: 20), - Center( - child: GestureDetector( - onTap: () { - final email = _emailController.text; - context.go('/createNewPw/$userId/$email'); - }, - child: Text( - AppLocalizations.of(context)!.change_password, - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline), - ), - ), - ), - SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () { - showConfirmationDialog(context); - }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - AppLocalizations.of(context)!.save, - style: TextStyle(color: Colors.white), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ], - ), ), ), ), @@ -841,7 +1007,7 @@ class _EditProfileState extends State { } class ConfirmationDialog extends StatelessWidget { - const ConfirmationDialog({Key? key}) : super(key: key); + const ConfirmationDialog({super.key}); @override Widget build(BuildContext context) { return AlertDialog( @@ -849,7 +1015,7 @@ class ConfirmationDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - "Are you sure you want to save this page?", + 'Are you sure you want to save this page?', style: const TextStyle( fontSize: 14, color: Color(0xFF898C81), @@ -861,7 +1027,7 @@ class ConfirmationDialog extends StatelessWidget { SizedBox(height: 8), Text.rich( TextSpan( - text: "Once saved, you will not be able to change your ", + text: 'Once saved, you will not be able to change your ', style: TextStyle( fontSize: 14, color: Color(0xFF898C81), @@ -870,25 +1036,26 @@ class ConfirmationDialog extends StatelessWidget { ), children: [ TextSpan( - text: "Country", + text: 'Country', style: TextStyle(fontWeight: FontWeight.w700), // Bold for "name" ), TextSpan( - text: " or ", + text: ' or ', ), TextSpan( - text: "Profile Image", + text: 'Profile Image', style: TextStyle( - fontWeight: FontWeight.w700), // Bold for "date of birth" + fontWeight: FontWeight.w700, + ), // Bold for "date of birth" ), TextSpan( - text: ".", + text: '.', ), ], ), textAlign: TextAlign.center, - ) + ), ], ), shape: RoundedRectangleBorder( @@ -913,7 +1080,7 @@ class ConfirmationDialog extends StatelessWidget { ), // Set the border color here ), child: Text( - "Cancel", + 'Cancel', style: TextStyle(color: Color(0xFF92722A)), ), ), @@ -938,7 +1105,7 @@ class ConfirmationDialog extends StatelessWidget { ), SizedBox( height: 5, - ) + ), ], ); } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart index 363a66d4..2c701b8a 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart @@ -28,7 +28,7 @@ class FeedbackForm extends StatefulWidget { class _FeedbackFormState extends State with WidgetsBindingObserver { final _pb = - PocketBase('https://pb.venbait.in'); // Initialize PocketBase client + PocketBase('https://pb.venbait.in'); // Initialize PocketBase client // final _pb = // PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client @@ -46,32 +46,32 @@ class _FeedbackFormState extends State dynamic userId; List> get _emojiOptions => [ - { - "icon": Icons.sentiment_very_dissatisfied, - "label": context.translate("Terrible", "رهيب"), // Translate here - "value": 1, - }, - { - "icon": Icons.sentiment_dissatisfied, - "label": context.translate("Bad", "سيء"), // Translate here - "value": 2, - }, - { - "icon": Icons.sentiment_neutral, - "label": context.translate("Okay", "تمام"), // Translate here - "value": 3, - }, - { - "icon": Icons.sentiment_satisfied, - "label": context.translate("Good", "جيد"), // Translate here - "value": 4, - }, - { - "icon": Icons.sentiment_very_satisfied, - "label": context.translate("Amazing", "مدهش"), // Translate here - "value": 5, - }, - ]; + { + "icon": Icons.sentiment_very_dissatisfied, + "label": context.translate("Terrible", "سئ جدا"), // Translate here + "value": 1, + }, + { + "icon": Icons.sentiment_dissatisfied, + "label": context.translate("Bad", "سيء"), // Translate here + "value": 2, + }, + { + "icon": Icons.sentiment_neutral, + "label": context.translate("Okay", "مقبول"), // Translate here + "value": 3, + }, + { + "icon": Icons.sentiment_satisfied, + "label": context.translate("Good", "جيد"), // Translate here + "value": 4, + }, + { + "icon": Icons.sentiment_very_satisfied, + "label": context.translate("Amazing", "مذهل"), // Translate here + "value": 5, + }, + ]; static const int _characterLimit = 1200; final RegExp _allowedCharacters = RegExp(r'^[a-zA-Z0-9 .,!?-]*$'); @@ -213,7 +213,7 @@ class _FeedbackFormState extends State title: Text(AppLocalizations.of(context)!.feedback_title), // appBar: AppBar( // backgroundColor: const Color(0xFFf8f9ff), - // title: Text(context.translate('Feedback Form', 'نموذج الملاحظات')), + // title: Text(AppLocalizations.of(context)!.feedback_title), // actions: [ // Align( // alignment: AlignmentDirectional.topEnd, @@ -235,8 +235,8 @@ class _FeedbackFormState extends State child: _isFeedbackSubmitted ? _buildThankYouMessage() : _isFeedbackFailed - ? _buildFailureMessage() - : _buildFeedbackForm(), + ? _buildFailureMessage() + : _buildFeedbackForm(), ), ), ); @@ -249,7 +249,7 @@ class _FeedbackFormState extends State Text( context.translate( 'Do you have a suggestion or had any problem? Let us know.', - 'هل لديك اقتراح أو واجهت أي مشكلة؟ اسمحوا لنا أن نعرف.', + 'هل لديك اقتراح أو واجهت أي مشكلة؟ أخبرنا', ), style: const TextStyle( fontSize: 18, @@ -309,7 +309,7 @@ class _FeedbackFormState extends State const SizedBox(height: 30), Text( context.translate('How good did we do in these aspects?', - 'ما مدى جودة ما قمنا به في هذه الجوانب؟'), + 'ما مدى جودة أدائنا في هذه الجوانب؟'), style: const TextStyle( fontSize: 18, fontFamily: 'Roboto', @@ -321,34 +321,34 @@ class _FeedbackFormState extends State _buildRatingRow( context.translate('Ease of use', 'سهولة الاستخدام'), _easeOfUseRating, - (rating) { + (rating) { setState(() { _easeOfUseRating = rating; }); }, ), _buildRatingRow( - context.translate('Quality', 'جودة'), + context.translate('Quality', 'الجودة'), _qualityRating, - (rating) { + (rating) { setState(() { _qualityRating = rating; }); }, ), _buildRatingRow( - context.translate('Design', 'تصميم'), + context.translate('Design', 'التصميم'), _designRating, - (rating) { + (rating) { setState(() { _designRating = rating; }); }, ), _buildRatingRow( - context.translate('Redundant', 'متكرر'), + context.translate('Redundant', 'مكرر'), _redundancyRating, - (rating) { + (rating) { setState(() { _redundancyRating = rating; }); @@ -362,14 +362,14 @@ class _FeedbackFormState extends State maxLength: _characterLimit, decoration: InputDecoration( hintText: context.translate( - 'Tell us how we can improve', 'أخبرنا كيف يمكننا تحسين'), + 'Tell us how we can improve', 'أخبرنا كيف يمكننا التحسين'), border: OutlineInputBorder( borderSide: BorderSide( color: _hasError ? Colors.red : Color(0xFF7296BE), ), ), errorText: - _hasError ? _errorMessage : null, // Show error below field + _hasError ? _errorMessage : null, // Show error below field ), onChanged: (text) { // Trigger re-validation and character limit on each change @@ -412,7 +412,7 @@ class _FeedbackFormState extends State Text( context.translate( 'Submit', - 'يُقدِّم', + 'إرسال', ), ), 6.horizontalSpace, @@ -514,10 +514,10 @@ class _FeedbackFormState extends State } Widget _buildRatingRow( - String label, - double rating, - Function(double) onRatingUpdate, - ) { + String label, + double rating, + Function(double) onRatingUpdate, + ) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -573,7 +573,7 @@ class _FeedbackFormState extends State "design": _designRating, "redundancy": _redundancyRating, "emoji_rating": _emojiOptions[_selectedEmojiIndex!] - ["label"], // Emoji rating + ["label"], // Emoji rating "feedback": _feedbackController.text, }; @@ -783,7 +783,7 @@ class LangToggle extends ConsumerWidget { ref.read(preferencesUseCaseProvider.notifier).updatePreferences( (prefs) => prefs.copyWith(language: languageAfter), - ); + ); await onLoadFeedback(); // Reload feedback text after toggling language }, diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart index 880584e7..f9aceff7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart @@ -1,7 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; + class FAQPage extends StatefulWidget { const FAQPage({super.key}); @@ -12,63 +17,90 @@ class FAQPage extends StatefulWidget { class _FAQPageState extends State { @override Widget build(BuildContext context) { - return BaseScaffold( - title: Text('FAQs', - style: TextStyle( - color:Color(0xFF7296BE), - fontWeight: FontWeight.w500, - ),), - showBackButton: true, - body: SafeArea(child:Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text.rich( - TextSpan( - text: 'Here are some common questions about the ', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, + return Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('Key Features',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + }, + ), + ], + + ), + body: SafeArea( + child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child:SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container + ( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'Here are some common questions about the ', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + children: [ + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + TextSpan( + text: 'mobile application:', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + ), + ], + ), + ), ), - children: [ - TextSpan( - text:' Federal Competitiveness and Statistics Centre (FCSC),', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - TextSpan( - text: 'mobile application:', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - ), - ], - ), + Container( + padding: const EdgeInsets.all(0.0), + color: Colors.grey[50], + child: QuestionAnswerScrollView(), + ), + ], ), ), - Expanded(child: Scrollbar( - thumbVisibility: true, - thickness: 8, - radius: Radius.circular(10), - interactive: true, - child:SingleChildScrollView( - child:Container( - padding: const EdgeInsets.all(0.0), - color: Colors.grey[50], - child: QuestionAnswerScrollView(), - ), - ), - ),), - ], - ), + ), + ), ); } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart index fa8bb66a..0f54ed28 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart @@ -1,8 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/custom_text.dart'; +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; + class UsingFeatures extends StatelessWidget { @@ -517,44 +522,74 @@ class UsingFeatures extends StatelessWidget { ]; return - BaseScaffold( title: Text('Key Features', - style: TextStyle( - color: Color(0xFFAA8E83), - ),), - showBackButton: true, - body:SafeArea(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text( - 'FEATURES', - style: TextStyle( - color: Colors.black,// Text color - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), + Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('Key Features',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + }, ), - Expanded(child: Scrollbar( + ], + + ), + body:SafeArea( + + child: Scrollbar( thumbVisibility: true, thickness: 6, radius: Radius.circular(10), interactive: true, - child: - Container( - color: Colors.grey[50], // Set the background color here - // child: SingleChildScrollView( - child: CustomTextRich( - textSpans: textSpans, - padding: const EdgeInsets.all(16.0), - textAlign: TextAlign.start, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text( + 'FEATURES', + style: TextStyle( + color: Colors.black,// Text color + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + Container( + color: Colors.grey[50], // Set the background color here + // child: SingleChildScrollView( + child: CustomTextRich( + textSpans: textSpans, + padding: const EdgeInsets.all(16.0), + textAlign: TextAlign.start, + ), + ), + ], ), - ), - ),), - ], - ), ), + ) + ), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart index c4dafe91..a0e575a9 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart @@ -1,5 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/custom_text.dart'; @@ -7,6 +9,9 @@ import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes. import 'package:uae_stat/infrastructure/services/img_asset_paths/banner_asset_path.dart'; import 'package:url_launcher/url_launcher.dart'; + +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; class GettingStarted extends StatelessWidget { GettingStarted({super.key}); @@ -66,55 +71,90 @@ class GettingStarted extends StatelessWidget { ]; - return BaseScaffold( title: Text('About the App', - style: TextStyle( - color:Color(0xFF265E84), - fontWeight: FontWeight.w500, + return Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('About the App',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + // IconButton( + // icon: Icon(locale?.languageCode == 'en' + // ? Icons.toggle_off_outlined + // : Icons.toggle_on_outlined,), + // onPressed: () { + // ref.read(localeProvider.notifier).toggleLocale(); + // }, + // ); + }, + ), + ], ), - ), - showBackButton: true, - body:SafeArea(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text.rich( - TextSpan( - text: 'The', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - children: [ - TextSpan( - text:' Federal Competitiveness and Statistics Centre (FCSC),', + // title: Text('About the App',), + // showBackButton: true, + body:SafeArea( + child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child:SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'The', style: TextStyle( fontSize: 19, fontWeight: FontWeight.w600, - color: Colors.black87, + color: Colors.grey, ), - ), - TextSpan( - text:' in your App is designed to provide registered and approved users with access to accurate and comprehensive statistics about the UAE. The app serves as a centralized platform for exploring key datasets, trends, and insights across various sectors.', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, + children: [ + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + TextSpan( + text:' in your App is designed to provide registered and approved users with access to accurate and comprehensive statistics about the UAE. The app serves as a centralized platform for exploring key datasets, trends, and insights across various sectors.', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + ), + ], ), ), - ], - ), - ), - ), - Expanded(child: Scrollbar( - thumbVisibility: true, - thickness: 6, - radius: Radius.circular(10), - interactive: true, - child: SingleChildScrollView( - child: Column( + ), + + Column( children: [ Container( color: Colors.grey[50], // Set the background color here @@ -169,11 +209,11 @@ class GettingStarted extends StatelessWidget { ), ], ), - ), - - ),), - ], - ), ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart index c4086555..2455932a 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart @@ -15,15 +15,16 @@ class _UserGuideState extends State { List createRows(List>> items) { return items.map((pair) { return Padding( - padding: const EdgeInsets.only(bottom: 20), // Add space between rows + padding: const EdgeInsets.only(bottom: 20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: pair.length == 1 + ? MainAxisAlignment.center + : MainAxisAlignment.spaceEvenly, children: pair.map((item) { return userGuides( color: item['color'] as Color, text: item['text'] as String, dynamicIcon: item['icon'], - no: item['index'] as String, routePath:item['routePath']as String, ); }).toList(), @@ -36,17 +37,16 @@ class _UserGuideState extends State { title: Text('User Guide'), body: SingleChildScrollView( child: Container( - color: Colors.grey[300], // Grey background for content padding: const EdgeInsets.all(16.0), // Padding around content child: Column( children: createRows([ // Each pair defines a row of (Color, Text) pairs [ - {'routePath':'gettingStarted','color': Color(0xFF90B0D5), 'text': 'Getting Started', 'icon': 'START', 'index': '1'}, - {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined, 'index': '2'}, + {'routePath':'gettingStarted','color': Color(0xFF90B0D5), 'text': 'Getting Started', 'icon': 'START',}, + {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined,}, ], [ - {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp, 'index': '3'}, + {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp,}, // {'routePath':'features','color': Color(0xFF7DAFBC), 'text': 'Advanced Settings', 'icon': Icons.settings_outlined, 'index': '6'}, ], ]), @@ -58,21 +58,20 @@ class _UserGuideState extends State { class userGuides extends StatelessWidget { - const userGuides({super.key, required this.routePath ,required this.color, required this.text,required this.dynamicIcon,required this.no}); + const userGuides({super.key, required this.routePath ,required this.color, required this.text,required this.dynamicIcon}); final Color color; final String text; final dynamic dynamicIcon; - final String no; final String routePath; - Widget buildDynamicWidget(dynamic icons) { + Widget buildDynamicWidget(dynamic icons, Color iconColor) { if (dynamicIcon is IconData) { return Icon( icons, size: 38, - color: Colors.white, + color: iconColor, ); } else if (dynamicIcon is String) { // Create a Text widget if variable is a String @@ -81,7 +80,7 @@ class userGuides extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 20, - color: Colors.white, + color: iconColor, ), ); } else { @@ -96,67 +95,52 @@ class userGuides extends StatelessWidget { @override Widget build(BuildContext context) { + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; return InkWell( onTap: (){ context.push('/user-guide/$routePath'); }, child: Container( - padding: EdgeInsets.symmetric(horizontal: 4), - height: 115, - width: 148, - margin: EdgeInsets.all(1), - decoration: BoxDecoration( - color: color, + padding: EdgeInsets.symmetric(horizontal: 4), + height: 110, + width: screenWidth * 0.40, + margin: EdgeInsets.all(1), + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(10), - ), - alignment: Alignment.center, - child: Stack( - children: [Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Icon( - // size:38, - // icons, // Home icon - // color: Colors.white, - // ), - buildDynamicWidget(dynamicIcon), + border: Border.all(color: color, + width: 2.0) + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Icon( + // size:38, + // icons, // Home icon + // color: Colors.white, + // ), + buildDynamicWidget(dynamicIcon, color), - SizedBox(width: 10,height: 15,), // Space between icon and text - Container( - width: double.infinity, - padding: EdgeInsets.symmetric(horizontal: 4, vertical: 4), // Padding inside the text background - decoration: BoxDecoration( - color: Colors.white, // Background color for text - borderRadius: BorderRadius.circular(5), // Rounded corners for text background - ), - child: Text( - text, - style: TextStyle( - color: color, // Text color - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), + SizedBox(width: 10,height: 10,), // Space between icon and text + Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 4, vertical: 5), // Padding inside the text background + decoration: BoxDecoration( + color: color, // Background color for text + borderRadius: BorderRadius.circular(5), + ), + child: Text( + text, + style: TextStyle( + color: Colors.white, // Text color + fontWeight: FontWeight.bold, ), - ], + textAlign: TextAlign.center, + ), ), - Positioned( - top: 6, // Distance from the top of the container - left: 6, // Distance from the left of the container - child: Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - color: Colors.transparent, // Background color for the number - child: Text( - no, - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - ),], - ) + ], + ), ), ); } diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index bafc175f..5067f8da 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -1,25 +1,27 @@ +import 'dart:async'; import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/bottom_bar_asset_icon_path.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart'; -import '../../components/indicators/locale_provider.dart'; -import '../../components/my_toggle.dart'; - -class BaseScaffold extends ConsumerStatefulWidget { - final Widget body; - final Widget title; - final bool? showBackButton; - final bool? mycenterTitle; - final List? actions; - final Color? appbarColor, mytitleColor; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; +class BaseScaffold extends ConsumerStatefulWidget { const BaseScaffold({ - Key? key, + super.key, required this.body, required this.title, this.actions, @@ -27,7 +29,13 @@ class BaseScaffold extends ConsumerStatefulWidget { this.mycenterTitle = false, this.appbarColor, this.mytitleColor, - }) : super(key: key); + }); + final Widget body; + final Widget title; + final bool? showBackButton; + final bool? mycenterTitle; + final List? actions; + final Color? appbarColor, mytitleColor; @override _BaseScaffoldState createState() => _BaseScaffoldState(); @@ -48,14 +56,483 @@ class _BaseScaffoldState extends ConsumerState { String? collectionIds; String? collectionId; + late TutorialCoachMark tutorialCoachMark; + late List appBarTargets; + final GlobalKey bottomNavKey = GlobalKey(); + final GlobalKey toggleKey = GlobalKey(); + final GlobalKey drawerKey = GlobalKey(); + late double toggleHeight = 0; + bool isLoggedOut = false; @override void initState() { super.initState(); _checkUserId(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _startAppbarTour(); + }); + } + + //Method to Start App Tour + void _startAppbarTour() { + final scaffoldProvider = ref.watch(scaffoldTourProvider); + _calculatetooglePosition(); + if (!scaffoldProvider) { + // Show Home Tour + _initTarget(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: appBarTargets, // Ensure `homeTargets` is defined for Home Tour + onFinish: () { + ref.read(homeTourProvider.notifier).state = true; + debugPrint('Home Tutorial Finished'); + }, + )..show(context: context); + } + } + + void _calculatetooglePosition() { + final RenderBox cardRenderBox = + toggleKey.currentContext!.findRenderObject() as RenderBox; + final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); + final Size cardSize = cardRenderBox.size; + toggleHeight = cardSize.height; + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read(previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + } + + void _initTarget() { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + appBarTargets = [ + TargetFocus( + identify: 'bottomNav', + keyTarget: bottomNavKey, + shape: ShapeLightFocus.RRect, // Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.tour_navBar, + alignment: ContentAlign.top, + space: 0, + gap: 100, + ), + TargetContent( + align: ContentAlign.top, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * + 0.30, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( + '5/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, + color: Colors.white), + onPressed: () { + tutorialCoachMark.finish(); + ref + .read(scaffoldTourProvider.notifier) + .state = true; + ref + .read(previousChartsTourProvider + .notifier) + .state = false; + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'drawerKey', + keyTarget: drawerKey, + shape: ShapeLightFocus.RRect, + radius: 0, // No rounded corners + paddingFocus: 1, // Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.mainMenu, + alignment: ContentAlign.bottom, + gap: 55, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * 0.80, + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( + '6/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, + color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + icon: const Icon(Icons.arrow_forward, + color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only( + right: screenWidth * 0.7, top: 30), + align: ContentAlign.right, + child: Container( + width: 200, + height: screenHeight / 4, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'toggleKey', + keyTarget: toggleKey, + shape: ShapeLightFocus.RRect, + radius: 0, + paddingFocus: 0, // Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.toggle, + space: 0, + alignment: ContentAlign.bottom, + gap: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height * 0.80, + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( + '7/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, + color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: () { + debugPrint('Got it clicked'); + ref + .read(chartsTourProvider.notifier) + .state = true; + ref + .read( + previousChartsTourProvider.notifier) + .state = true; + ref.read(homeTourProvider.notifier).state = + true; + ref + .read(previousHomeTourProvider.notifier) + .state = true; + ref + .read(scaffoldTourProvider.notifier) + .state = true; + ref + .read(previousScaffoldTourProvider + .notifier) + .state = true; + tutorialCoachMark.finish(); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.white, + elevation: 0, + ), + child: const Text( + 'Got it', + style: TextStyle( + color: Color(0xFF7DAFBC), + fontSize: 14, + ), + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: + EdgeInsets.only(left: screenWidth * 0.7, top: toggleHeight), + align: ContentAlign.left, + child: Container( + width: 200, + height: screenHeight / 4, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/down_right.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + ]; + } + + Future _getAppVersion() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return packageInfo.version; // Returns the app version } - // Method to retrieve userId from SharedPreferences Future getUserId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('userId'); // Retrieve the userId @@ -69,125 +546,39 @@ class _BaseScaffoldState extends ConsumerState { userId = fetchedUserId; }); //print('NAVUser ID: $userId'); - _initializeUserData(); + _fetchUserData(); } else { print('No userId found'); // Handle the case where userId is not available } } - // Future _fetchUserData() async { - // try { - // final adminAuth = await _pb.admins - // .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); - // final adminToken = adminAuth.token; - // //print('adminToken- ${adminToken}'); - // final userDetailsResponse = await _pb.collection('users').getOne( - // userId!, - // headers: { - // 'Authorization': 'Bearer $adminToken', - // }, - // ); - // print('NAVuserDetails: $userDetailsResponse'); - // - // setState(() { - // userName = userDetailsResponse.data['uname'] ?? ''; - // userEmail = userDetailsResponse.data['email'] ?? ''; - // userAvatar = userDetailsResponse.data['avatar'] ?? ''; - // role = userDetailsResponse.data['role'] ?? ''; - // String recordId = userId; - // String collectionId = - // userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; - // - // if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { - // _avatarUrl = - // 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; - // } else { - // _avatarUrl = ''; // Reset to default or empty - // } - // }); - // } catch (e) { - // print('Error fetching user details: $e'); - // } - // } - - Future _initializeUserData() async { - print("Laoding 1"); - final prefs = await SharedPreferences.getInstance(); - // final userDataString = prefs.getString('userData'); - - if (prefs.containsKey('userData')) { - // print("Loading user data from local storage: $userData"); - final String? userDataString = prefs.getString('userData'); - - if (userDataString != null) { - // Decode the JSON string into a Map - final Map userData = jsonDecode(userDataString); - - // Access the userName field - setState(() { - userName = userData['userNames']; - userEmail = userData['userEmails']; - userAvatar = userData['userAvatars']; - role = userData['roles']; - String recordId = userId; - String collectionId = userData['collectionIds']; - - if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { - _avatarUrl = - 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; - } else { - _avatarUrl = ''; // Reset to default or empty - } - }); - } else { - print("No user data found in local storage."); - } - } else { - // Fetch data from server and save it to local storage - - await _fetchUserData(); - } - } - Future _fetchUserData() async { try { - // print("Fetching _fetchUserData"); final adminAuth = await _pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - + //print('adminToken- ${adminToken}'); final userDetailsResponse = await _pb.collection('users').getOne( userId!, headers: { 'Authorization': 'Bearer $adminToken', }, ); - // print('User details fetched from server: $userDetailsResponse'); - - final userData = { - 'userNames': userDetailsResponse.data['uname'] ?? '', - 'userEmails': userDetailsResponse.data['email'] ?? '', - 'userAvatars': userDetailsResponse.data['avatar'] ?? '', - 'roles': userDetailsResponse.data['role'] ?? '', - 'collectionIds': - userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_', - }; - - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('userData', jsonEncode(userData)); + print('NAVuserDetails: $userDetailsResponse'); setState(() { - userNames = userData['userNames']; - userEmails = userData['userEmails']; - userAvatars = userData['userAvatars']; - roles = userData['roles']; + userName = userDetailsResponse.data['uname'] ?? ''; + userEmail = userDetailsResponse.data['email'] ?? ''; + userAvatar = userDetailsResponse.data['avatar'] ?? ''; + role = userDetailsResponse.data['role'] ?? ''; String recordId = userId; - collectionIds = userData['collectionIds']; + String collectionId = + userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; - if (userAvatars!.isNotEmpty && recordId.isNotEmpty) { + if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = - 'https://pb.venbait.in/api/files/$collectionIds/$recordId/$userAvatars'; + 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; } else { _avatarUrl = ''; // Reset to default or empty } @@ -199,6 +590,9 @@ class _BaseScaffoldState extends ConsumerState { Future logout() async { final prefs = await SharedPreferences.getInstance(); + // setState(() { + // isLoggedOut = true; + // }); prefs.clear(); context.go('/'); } @@ -221,14 +615,17 @@ class _BaseScaffoldState extends ConsumerState { // onPressed: () {}, // icon: const Icon(Icons.toggle_off_outlined), // ), - MyToggle(isOn: locale?.languageCode == 'en', + MyToggle( + key: toggleKey, + isOn: locale?.languageCode == 'en', knobTextWhenOn: 'ع', knobTextWhenOff: 'EN', pathColorWhenOn: Colors.grey.shade300, pathColorWhenOff: Colors.grey.shade300, - onTap: (){ + onTap: () { ref.read(localeProvider.notifier).toggleLocale(); - },), + }, + ), ], leading: Stack( children: [ @@ -248,6 +645,7 @@ class _BaseScaffoldState extends ConsumerState { child: Builder( builder: (BuildContext context) { return IconButton( + key: drawerKey, color: (widget.showBackButton == true) ? Colors.white : Colors.black, @@ -263,120 +661,236 @@ class _BaseScaffoldState extends ConsumerState { ), ), drawer: Drawer( - child: ListView( - children: [ - DrawerHeader( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - SizedBox( - width: mywidth / 8, - child: Image( - image: AssetImage('assets/logos/fcsc.png'))), - ], - ), - Divider(), - InkWell( - onTap: () => context.go('/editProfile'), - child: Row( + child: Column(children: [ + Expanded( + child: ListView( + children: [ + DrawerHeader( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( children: [ SizedBox( width: mywidth / 8, - height: mywidth / 8, - child: ClipOval( - child: _avatarUrl.isNotEmpty - ? Image( - image: NetworkImage(_avatarUrl), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ) - : Image( - image: AssetImage( - 'assets/edit_profile/profile.png'), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ), + child: Image( + image: AssetImage('assets/logos/fcsc.png'), ), - //child: Image(image: AssetImage('assets/edit_profile/profile.png')) ), - SizedBox( - width: mywidth / 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Text(userId ?? 'Loading user...'), // Display userId here - // Text('Mohammad@fcsc.com') - Text(userName ?? 'Loading...'), - Text( - userEmail ?? 'Loading...', - style: TextStyle(fontSize: 12), - ), - ], - ) ], ), - ) - ], + Divider(), + InkWell( + onTap: () { + if (userId != 'guest') context.go('/editProfile'); + }, + child: Row( + children: [ + SizedBox( + width: mywidth / 8, + height: mywidth / 8, + child: ClipOval( + child: _avatarUrl.isNotEmpty + ? Image( + image: NetworkImage(_avatarUrl), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ) + : Image( + image: AssetImage( + 'assets/edit_profile/profile.png', + ), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + //child: Image(image: AssetImage('assets/edit_profile/profile.png')) + ), + SizedBox( + width: mywidth / 20, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text(userId ?? 'Loading user...'), // Display userId here + // Text('Mohammad@fcsc.com') + if (userId == "guest") ...[ + Text(userName ?? 'Guest User'), + ] else ...[ + Text(userName ?? 'Loading...'), + Text( + userEmail ?? 'Loading...', + style: TextStyle(fontSize: 12), + ), + ] + ], + ), + ], + ), + ), + ], + ), ), - ), - ListTile( - leading: Icon(Icons.feedback), - title: const Text('Feedback'), - onTap: () => context.go('/feedback'), - ), - if (role == 'admin') + if (userId != 'guest') + ListTile( + leading: Image.asset( + DrawerAssetIconPath.feedback, + color: Colors.black , + width: 20, + height: 20, + ), + // title: const Text('Feedback'), + title: Text(AppLocalizations.of(context)!.feedback_title), + onTap: () => context.go('/feedback'), + ), + if (role == 'admin') + ListTile( +leading: Image.asset( + DrawerAssetIconPath.manageUser, + color: Colors.black , + width: 20, + height: 20, + ), + // title: Text('Manage User'), + title: Text( + AppLocalizations.of(context)!.manage_user, + ), + onTap: () => context.go('/manageuser'), + ), ListTile( - leading: Icon(Icons.manage_accounts), - // title: Text('Manage User'), - title: Text(AppLocalizations.of(context)!.manage_user,), - onTap: () => context.go('/manageuser'), + leading: Icon(Icons.collections_bookmark_rounded), + title: const Text('Bookmark'), + onTap: () => context.go('/bookmark'), ), - ListTile( - leading: Icon(Icons.book), - title: const Text('User Guide'), - onTap: () => context.go('/user-guide'), - ), - ListTile( - leading: Icon(Icons.logout), - title: const Text('Logout'), - onTap: () => logout(), - ), - ], + ListTile( + leading: Image.asset( + DrawerAssetIconPath.guide, + color: Colors.black , + width: 20, + height: 20, + ), + // title: const Text('User Guide'), + title: Text(AppLocalizations.of(context)!.guide_title), + onTap: () => context.go('/user-guide'), + ), + if (userId != 'guest') + ListTile( + leading: Icon(Icons.logout), + // title: const Text('Logout'), + title: Text(AppLocalizations.of(context)!.logout), + onTap: () => logout(), + ), + if (userId == 'guest') + ListTile( + leading: Icon(Icons.login), + // title: const Text('Login'), + title: Text(AppLocalizations.of(context)!.login_title), + onTap: () => context.go('/login'), + ), + if (userId == 'guest') + ListTile( + leading: Icon(Icons.app_registration), + // title: const Text('Register'), + title: Text(AppLocalizations.of(context)!.register_title), + onTap: () => context.go('/register'), + ), + ], + ), ), - ), + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: FutureBuilder( + future: _getAppVersion(), // Function to get the app version + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Text( + 'Loading version...', + style: TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.center, + ); + } else if (snapshot.hasError) { + return Text( + 'Error fetching version', + style: TextStyle(fontSize: 12, color: Colors.red), + textAlign: TextAlign.center, + ); + } else { + return Text( + 'Version ${snapshot.data}', + style: TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.center, + ); + } + }, + ), + ), + ])), bottomNavigationBar: BottomNavigationBar( currentIndex: _getSelectedIndex(currentRoute), onTap: (index) => _onItemTapped(context, index), type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( - icon: Icon(Icons.home), + icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.home, + color: _getSelectedIndex(currentRoute) == 0 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), // label: 'Home', label: AppLocalizations.of(context)!.nav_home, ), BottomNavigationBarItem( - icon: Icon(Icons.map), + // icon: Icon(Icons.map), + icon:Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.uaeMap, + color: _getSelectedIndex(currentRoute) == 1 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), // label: 'UAE Numbers' label: AppLocalizations.of(context)!.nav_uae, ), BottomNavigationBarItem( - icon: Icon(Icons.bar_chart), - // label: 'Competitiveness', +icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.ranking, + color: _getSelectedIndex(currentRoute) == 2 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), + // label: 'Competitiveness', label: AppLocalizations.of(context)!.nav_competitiveness, ), BottomNavigationBarItem( - icon: Icon(Icons.public), - // label: 'Country Profile', + icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.globe, + color: _getSelectedIndex(currentRoute) == 2 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), + // label: 'Country Profile', label: AppLocalizations.of(context)!.country_profile, ), ], + key: bottomNavKey, selectedItemColor: Colors.black, unselectedItemColor: Colors.grey, + selectedLabelStyle: TextStyle( + fontSize: 10, // Font size for unselected label + ), + unselectedLabelStyle: TextStyle( + fontSize: 10, // Font size for unselected label + ), showUnselectedLabels: true, ), body: widget.body, diff --git a/lib/presentation/routes/drawer_routes/user_guide_route.dart b/lib/presentation/routes/drawer_routes/user_guide_route.dart index 341037ec..3c250988 100644 --- a/lib/presentation/routes/drawer_routes/user_guide_route.dart +++ b/lib/presentation/routes/drawer_routes/user_guide_route.dart @@ -1,99 +1,100 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:video_player/video_player.dart'; - -import 'package:uae_stat/domain/use_cases/language.dart'; -import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart'; -import 'package:uae_stat/presentation/components/space.dart'; -import 'package:uae_stat/presentation/components/themed_app_bar.dart'; - -typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc}); - -class UserGuideRoute extends HookConsumerWidget { - const UserGuideRoute({super.key}); - - static const List<_UserGuideType> _data = [ - ( - videoPath: UserGuideAssetPath.toggleLanguage, - enDesc: - 'You can toggle between Arabic and English by click the toggle button.', - arDesc: - 'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.', - ), - ( - videoPath: UserGuideAssetPath.searchIndicators, - enDesc: - 'You can browse indicators by category or use the search functionality', - arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث', - ), - ( - videoPath: UserGuideAssetPath.bookmarkIndicators, - enDesc: - 'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.', - arDesc: - 'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.', - ), - ]; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final e = useState<_UserGuideType>(_data.first); - final carouselOptions = CarouselOptions( - autoPlay: true, - clipBehavior: Clip.none, - onPageChanged: (index, reason) => e.value = _data[index], - aspectRatio: 9 / 17, - disableCenter: true, - enlargeCenterPage: true, - floatingIndicator: true, - autoPlayInterval: const Duration(seconds: 10), - ); - final bodyContent = Column( - children: [ - Expanded( - flex: 3, - child: FlutterCarousel( - options: carouselOptions, - items: _data - .map( - (e) => VideoPlayer( - VideoPlayerController.asset( - e.videoPath, - )..initialize(), - ), - ) - .toList(), - ), - ), - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(36, 24, 36, 36), - child: Text( - context.translate(e.value.enDesc, e.value.arDesc), - ), - ), - ), - 98.verticalSpace, - ], - ); - final body = Column( - children: [ - ThemedAppBar( - titleText: context.translate( - 'User Guide', - 'دليل المستخدم', - ), - ), - Expanded( - child: bodyContent, - ), - ], - ); - return ColoredBox( - color: Colors.white, - child: SafeArea(child: body), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:hooks_riverpod/hooks_riverpod.dart'; +// import 'package:video_player/video_player.dart'; +// +// import 'package:uae_stat/domain/use_cases/language.dart'; +// import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart'; +// import 'package:uae_stat/presentation/components/space.dart'; +// import 'package:uae_stat/presentation/components/themed_app_bar.dart'; +// +// typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc}); +// +// class UserGuideRoute extends HookConsumerWidget { +// const UserGuideRoute({super.key}); +// +// static const List<_UserGuideType> _data = [ +// ( +// videoPath: UserGuideAssetPath.toggleLanguage, +// enDesc: +// 'You can toggle between Arabic and English by click the toggle button.', +// arDesc: +// 'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.', +// ), +// ( +// videoPath: UserGuideAssetPath.searchIndicators, +// enDesc: +// 'You can browse indicators by category or use the search functionality', +// arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث', +// ), +// ( +// videoPath: UserGuideAssetPath.bookmarkIndicators, +// enDesc: +// 'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.', +// arDesc: +// 'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.', +// ), +// ]; +// +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// final e = useState<_UserGuideType>(_data.first); +// final carouselOptions +// // final carouselOptions = CarouselOptions( +// // autoPlay: true, +// // clipBehavior: Clip.none, +// // onPageChanged: (index, reason) => e.value = _data[index], +// // aspectRatio: 9 / 17, +// // disableCenter: true, +// // enlargeCenterPage: true, +// // floatingIndicator: true, +// // autoPlayInterval: const Duration(seconds: 10), +// // ); +// final bodyContent = Column( +// children: [ +// Expanded( +// flex: 3, +// child: FlutterCarousel( +// options: carouselOptions, +// items: _data +// .map( +// (e) => VideoPlayer( +// VideoPlayerController.asset( +// e.videoPath, +// )..initialize(), +// ), +// ) +// .toList(), +// ), +// ), +// Expanded( +// child: SingleChildScrollView( +// padding: const EdgeInsets.fromLTRB(36, 24, 36, 36), +// child: Text( +// context.translate(e.value.enDesc, e.value.arDesc), +// ), +// ), +// ), +// 98.verticalSpace, +// ], +// ); +// final body = Column( +// children: [ +// ThemedAppBar( +// titleText: context.translate( +// 'User Guide', +// 'دليل المستخدم', +// ), +// ), +// Expanded( +// child: bodyContent, +// ), +// ], +// ); +// return ColoredBox( +// color: Colors.white, +// child: SafeArea(child: body), +// ); +// } +// } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c5426f77..cee82ebe 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import connectivity_plus import file_selector_macos import flutter_secure_storage_macos +import package_info_plus import path_provider_foundation import share_plus import shared_preferences_foundation @@ -16,13 +17,14 @@ import video_player_avfoundation import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) - FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index c5834e23..10d21d20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -194,18 +194,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" + sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.1.2" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" url: "https://pub.dev" source: hosted - version: "1.2.4" + version: "2.0.1" convert: dependency: transitive description: @@ -258,18 +258,18 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "6d509673c4dd0baa90e60dc8366bc2acc6690f16a7d44bfae31294d82c5d2a62" + sha256: "7e6a73e1722ad90b48f77918994c17a5b177b0869804ea3c4ce9c9199de019f6" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "8cc525c7b160eb47bb1ded8b2633c0f8b907930eb986ac577aded87cdd2835fe" + sha256: e26941131416b0bc81219dc8ddc68b34c6319e20abc001dd4855412749309d3b url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" custom_lint_core: dependency: transitive description: @@ -298,10 +298,10 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" equatable: dependency: transitive description: @@ -409,10 +409,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" + sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" url: "https://pub.dev" source: hosted - version: "0.69.2" + version: "0.70.2" flutter: dependency: "direct main" description: flutter @@ -438,10 +438,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" + sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "0.14.3" flutter_lints: dependency: "direct dev" description: @@ -557,10 +557,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "24467dc20bbe49fd63e57d8e190798c4d22cbbdac30e54209d153a15273721d1" + sha256: "8971efe7e59585e9149052e33718d84bca51e806f063d1467622b3dcb2878b6c" url: "https://pub.dev" source: hosted - version: "8.2.10" + version: "8.2.11" freezed: dependency: "direct dev" description: @@ -597,18 +597,18 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" go_router: dependency: "direct main" description: name: go_router - sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d" + sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" url: "https://pub.dev" source: hosted - version: "14.6.3" + version: "14.7.2" graphs: dependency: transitive description: @@ -629,10 +629,10 @@ packages: dependency: transitive description: name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" html: dependency: transitive description: @@ -645,10 +645,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: @@ -717,10 +717,10 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_platform_interface: dependency: transitive description: @@ -929,6 +929,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254 + url: "https://pub.dev" + source: hosted + version: "8.2.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" path: dependency: transitive description: @@ -1140,18 +1156,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a + sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bf808be89fe9dc467475e982c1db6c2faf3d2acf54d526cd5ec37d86c99dbd84 + sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" shared_preferences_foundation: dependency: transitive description: @@ -1296,18 +1312,18 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_charts - sha256: "117823c9e2ffcb7fb9868c73263df88751e3bb1f3d81f617cbaf63112f530e9c" + sha256: "672c17a7f4cb3020c06bb6a28d2d033623007b8e2a19704ad34b412e9bdd2dab" url: "https://pub.dev" source: hosted - version: "28.1.39" + version: "28.2.4+1" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "794870919ca73e29c6cb25392a097cdfe58da4d6f3f3d3eccc529ecf52f78752" + sha256: "3c1876b0a245de23de3b17a19e3106fed57d88f4fd2c8dc9bc1976705b1c31d5" url: "https://pub.dev" source: hosted - version: "28.1.39" + version: "28.2.4" term_glyph: dependency: transitive description: @@ -1340,6 +1356,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tutorial_coach_mark: + dependency: "direct main" + description: + name: tutorial_coach_mark + sha256: df450c88d4c812bc221afd3ff948da3dc0f44c0b4fa5dbc046d6d86f2cfc9e71 + url: "https://pub.dev" + source: hosted + version: "1.2.12" typed_data: dependency: transitive description: @@ -1464,18 +1488,18 @@ packages: dependency: transitive description: name: video_player_platform_interface - sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "6.3.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.3.4" vm_service: dependency: transitive description: @@ -1512,10 +1536,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webview_flutter: dependency: "direct main" description: @@ -1528,10 +1552,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f + sha256: "5568f17a9c25c0fdd0737900fa1c2d1fee2d780bc212d9aec10c2d1f48ef0f59" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.3.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1544,18 +1568,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: "8e0593559bfecd35eb1757d6907ed6b995a41ef82607d6113df897c2805ce6be" url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.0" win32: dependency: transitive description: name: win32 - sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.10.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3194705a..48939a6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.69.2 + fl_chart: ^0.70.2 marquee: ^2.2.3 url_launcher: ^6.3.1 @@ -60,7 +60,9 @@ dependencies: material_charts: ^0.0.23 flutter_staggered_grid_view: ^0.7.0 syncfusion_flutter_charts: ^28.1.39 - connectivity_plus: ^5.0.1 + connectivity_plus: ^6.1.2 + tutorial_coach_mark: ^1.2.12 + package_info_plus: ^8.2.0 dependency_overrides: fading_edge_scrollview: ^4.1.1