diff --git a/android/app/build.gradle b/android/app/build.gradle index 64443c26..20b03edd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,6 +3,7 @@ plugins { id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" + id "com.google.gms.google-services" } def localProperties = new Properties() @@ -13,14 +14,18 @@ if (localPropertiesFile.exists()) { } } +if (project.hasProperty('google-services.json')) { + apply plugin: 'com.google.gms.google-services' +} + def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "28" + flutterVersionCode = "31" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.27" + flutterVersionName = "1.0.30" } def keystorePropertiesFile = rootProject.file("key.properties") @@ -46,7 +51,7 @@ android { applicationId = "ae.gov.fcsc.frontend" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutterVersionCode.toInteger() versionName = flutterVersionName @@ -75,3 +80,9 @@ android { flutter { source = "../.." } + +dependencies { + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + implementation 'com.google.firebase:firebase-messaging:22.0.0' + implementation("com.google.firebase:firebase-auth:23.0.0") +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..2dfa060b --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "62132243005", + "project_id": "fcsc-a161c", + "storage_bucket": "fcsc-a161c.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:62132243005:android:18b7ca94608afaeb7c7854", + "android_client_info": { + "package_name": "ae.gov.fcsc.frontend" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyB-QQbnq366f7rrKLVVFJKnSZgR9LcDWFI" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a570820b..ee29d442 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/android/build.gradle b/android/build.gradle index 8e9de9d8..0f6859f8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,14 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' // Update this if needed + classpath 'com.google.gms:google-services:4.3.10' // ✅ Required for Firebase + } +} + allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 2db302b0..f01fbe2d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -20,7 +20,7 @@ plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false - + id("com.google.gms.google-services") version "4.4.2" apply false } include ":app" diff --git a/assets/FCSC-GIF.gif b/assets/FCSC-GIF.gif new file mode 100644 index 00000000..46e243b0 Binary files /dev/null and b/assets/FCSC-GIF.gif differ diff --git a/assets/backgrounds/Notification/App-Update.png b/assets/backgrounds/Notification/App-Update.png index 8767ad9b..a19e94e9 100644 Binary files a/assets/backgrounds/Notification/App-Update.png and b/assets/backgrounds/Notification/App-Update.png differ diff --git a/assets/backgrounds/Notification/Update_notific.png b/assets/backgrounds/Notification/Update_notific.png index c772f7a6..e1b87b59 100644 Binary files a/assets/backgrounds/Notification/Update_notific.png and b/assets/backgrounds/Notification/Update_notific.png differ diff --git a/lib/config/api_config.dart b/lib/config/api_config.dart index 07b7c7f4..6ec553f9 100644 --- a/lib/config/api_config.dart +++ b/lib/config/api_config.dart @@ -1,3 +1,4 @@ // api_config.dart // const String apiUrl = 'http://192.168.1.26:8090'; -const String apiUrl = 'https://pbdev.venbait.in/'; +// const String apiUrl = 'https://pbdev.venbait.in/'; +const String apiUrl = 'https://pb.venbait.in/'; diff --git a/lib/config/loading_overlay.dart b/lib/config/loading_overlay.dart new file mode 100644 index 00000000..6830314b --- /dev/null +++ b/lib/config/loading_overlay.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class LoadingOverlay { + static final ValueNotifier _isLoading = ValueNotifier(false); + + static void show() { + _isLoading.value = true; + } + + static void hide() { + _isLoading.value = false; + } + + static Widget buildLoader() { + return ValueListenableBuilder( + valueListenable: _isLoading, + builder: (context, isLoading, child) { + return isLoading + ? Stack( + children: [ + ModalBarrier( + color: Colors.black.withOpacity(0.5), dismissible: false), + Center( + child: Image.asset( + "assets/images/loading.gif", // Make sure to add the GIF in assets + width: 100, + height: 100, + ), + ), + ], + ) + : const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 00f2c6a0..a003bdec 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -307,6 +307,7 @@ 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/drawer_routes/Drawer%20Items/notification/notification.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/notification/notification_details.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/AboutTheApp/AboutFCSC.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/AboutTheApp/getStarted.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/KeyFeatures/AppFeatures.dart'; @@ -366,7 +367,6 @@ final GoRouter router = GoRouter( path: '/', //builder: (context, state) => LoginRoute(), builder: (context, state) => SessionCheckScreen(), - ), // GoRoute( // path: '/', @@ -426,8 +426,7 @@ final GoRouter router = GoRouter( state.uri.queryParameters['filter_data']!.isNotEmpty) { try { filterData = List>.from( - jsonDecode(state.uri.queryParameters['filter_data']!) - ); + jsonDecode(state.uri.queryParameters['filter_data']!)); } catch (e) { print('Error parsing filter_data: $e'); filterData = []; // Assign an empty list to prevent null issues @@ -436,8 +435,6 @@ final GoRouter router = GoRouter( filterData = []; // Ensure it's a valid list } - - print('Router dataSets: $dataSets'); print('Router bgColor: $bgColor'); print('Router mainTopic: $mainTopic'); @@ -521,6 +518,18 @@ final GoRouter router = GoRouter( return NotificationPage(); }, ), + GoRoute( + path: '/notification_details', + builder: (context, state) { + final Map data = state.extra as Map; + return NotificationDetails( + title: data['title'] ?? '', + message: data['message'] ?? '', + date: data['date'] ?? '', + category: data['category'] ?? '', + ); + }, + ), GoRoute( path: '/user-guide', builder: (context, state) => Userguide(), diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 6e680d81..0d4c6344 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -51,6 +51,7 @@ "full_name": "الاسم الكامل", "dob": "تاريخ الميلاد", "region": "الدولة/المنطقة", + "preferredLang": "اللغة المفضلة", "cancel": "إلغاء", "confirm": "تأكيد", "change_password": "تغيير كلمة المرور", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 10d131d5..1ca84b01 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -49,6 +49,7 @@ "full_name": "Full Name*", "dob": "Date of Birth*", "region": "Country/Region", + "preferredLang": "Preferred Language", "cancel": "Cancel", "confirm": "Confirm", "change_password": "Change Password", diff --git a/lib/main.dart b/lib/main.dart index a795ed32..31f051b6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:external_repos/external_repos.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,8 +18,6 @@ import 'package:uae_stat/presentation/Screens/auth_verification/registration.dar import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - - // void main() async { // WidgetsFlutterBinding.ensureInitialized(); // // await (await SharedPreferences.getInstance()).clear(); @@ -46,8 +46,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // } // } - - // void main() async { // WidgetsFlutterBinding.ensureInitialized(); // // await (await SharedPreferences.getInstance()).clear(); @@ -91,10 +89,31 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // } // } - +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // Handle background messages + print("Handling background message: ${message.messageId}"); +} void main() async { WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: const FirebaseOptions( + apiKey: 'AIzaSyB-QQbnq366f7rrKLVVFJKnSZgR9LcDWFI', + appId: '1:62132243005:android:18b7ca94608afaeb7c7854', + messagingSenderId: '62132243005', + projectId: 'fcsc-a161c')); + + FirebaseMessaging messaging = FirebaseMessaging.instance; + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + // Request permission for notifications + NotificationSettings settings = await messaging.requestPermission( + alert: true, + badge: true, + sound: true, + ); + + print('User granted permission: ${settings.authorizationStatus}'); configureDependencies(); runApp( const ProviderScope( @@ -106,9 +125,6 @@ void main() async { class MainApp extends ConsumerWidget { const MainApp({super.key}); - - - @override Widget build(BuildContext context, WidgetRef ref) { final locale = ref.watch(localeProvider); @@ -117,14 +133,13 @@ class MainApp extends ConsumerWidget { return true; // Replace with actual logic }); - - // 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, @@ -137,7 +152,8 @@ class MainApp extends ConsumerWidget { void handleConnectivityChange(BuildContext context, bool hasInternet) { //final currentPath = GoRouter.of(context).location; - final currentPath = GoRouter.of(context).routerDelegate.currentConfiguration.fullPath; + final currentPath = + GoRouter.of(context).routerDelegate.currentConfiguration.fullPath; if (!hasInternet && currentPath != '/internetcheck') { print("Status : Not connected"); context.go('/internetcheck'); @@ -147,6 +163,3 @@ class MainApp extends ConsumerWidget { } } } - - - 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 9f419cfa..b81677dd 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 @@ -12,7 +12,7 @@ import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes. import 'package:uae_stat/presentation/components/constant/constant.dart'; import 'package:http/http.dart' as http; import 'package:uae_stat/config/api_config.dart'; - +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class UaeNumbers extends StatelessWidget { const UaeNumbers({super.key}); @@ -198,14 +198,17 @@ class _UaenumberWidgetState extends ConsumerState { value); // Call filter function directly on input change }, decoration: InputDecoration( - hintText: "Search", - hintStyle: TextStyle(color: Color(0xFF898C81)), - prefixIconConstraints: BoxConstraints(maxWidth: 42, maxHeight: 42), + hintText: AppLocalizations.of(context)!.search, + hintStyle: TextStyle(color: Color(0xFFAA8E83)), + prefixIconConstraints: + BoxConstraints(maxWidth: 42, maxHeight: 42), prefixIcon: Container( - padding: EdgeInsets.only(right: 5), + padding: EdgeInsets.only(right: 5, left: 5), child: SvgPicture.asset( MiscIconAssetPath.Search, semanticsLabel: 'Search', + colorFilter: + ColorFilter.mode(Color(0xFFAA8E83), BlendMode.srcIn), ), ), @@ -215,7 +218,7 @@ class _UaenumberWidgetState extends ConsumerState { // ), border: InputBorder.none, contentPadding: - EdgeInsets.symmetric(vertical: 8.0, horizontal: 18.0), + EdgeInsets.symmetric(vertical: 0.5, horizontal: 18.0), ), ), ), diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 692800f1..da83f816 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -54,7 +54,7 @@ class ChartScreen1 extends ConsumerStatefulWidget { class _ChartScreen1State extends ConsumerState { // final _pb = PocketBase('https://pb.venbait.in'); final _pb = PocketBase(apiUrl); - List> selectedFiltersStorage = []; + final GlobalKey _scaffoldKey = GlobalKey(); final ApiService apiService = ApiService(); bool isLoading = true; @@ -68,6 +68,8 @@ class _ChartScreen1State extends ConsumerState { List tabFilteredCardData = []; List cardData = []; List> filteredAndSortedData = []; + List> selectedFiltersStorage = []; + List> selectedFiltersApi = []; List originalChartsData = []; List originalCardData = []; List originalTabCardData = []; @@ -75,11 +77,12 @@ class _ChartScreen1State extends ConsumerState { late Color backgroundColor; final ScrollController _scrollController = ScrollController(); int _activeTabIndex = 0; - dynamic tabWiseKpi=[]; - dynamic tabFilteredKpi=[]; - List> formattedFilters=[]; + dynamic tabWiseKpi = []; + dynamic tabFilteredKpi = []; + List> formattedFilters = []; dynamic _tabsData = []; + dynamic currentTab = []; // List> _tabsData = []; late List marriageTargets; late List previousMarriageTargets; @@ -90,6 +93,7 @@ class _ChartScreen1State extends ConsumerState { bool isBookmarked = false; // Track bookmark state String? bookmarkId; // Stores the ID of the bookmark record in PocketBase late final Locale locale; + late String mainTopic; @override void initState() { @@ -98,10 +102,12 @@ class _ChartScreen1State extends ConsumerState { print(' bgColor $bgColor'); checkIfBookmarked(); locale = ref.read(localeProvider) ?? const Locale('en'); + print('locale-$locale'); - fetchChartData(widget.dataSets, locale?.languageCode ?? 'en', widget.kpi, widget.filter_data) - .then((_) { - if(_tabsData.isNotEmpty) { + fetchChartData(widget.dataSets, locale?.languageCode ?? 'en', widget.kpi, + widget.filter_data) + .then((_) { + if (_tabsData.isNotEmpty) { // Call onTabSelected for the first tab onTabSelected(_tabsData[0]['id']); } @@ -313,7 +319,8 @@ class _ChartScreen1State extends ConsumerState { backgroundColor: Color(0xFFAA8E83), // Set background color foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), // Set text color + borderRadius: + BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -324,7 +331,6 @@ class _ChartScreen1State extends ConsumerState { ), ], ) - ], ), ); @@ -371,29 +377,30 @@ class _ChartScreen1State extends ConsumerState { SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ SizedBox( - width: 100, // Set the desired width - child: TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 10.0), // Adjust the radius as needed + children: [ + SizedBox( + width: 100, // Set the desired width + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 10.0), // Adjust the radius as needed + ), + side: BorderSide( + color: Color(0xFFAA8E83), // Set the outline color + width: 1, // Set the border width + ), ), - side: BorderSide( - color: Color(0xFFAA8E83), // Set the outline color - width: 1, // Set the border width - ), - ), - child: Text( - context.translate('No', 'لا'), - style: TextStyle( - color: Color(0xFFAA8E83), - fontSize: 16, + child: Text( + context.translate('No', 'لا'), + style: TextStyle( + color: Color(0xFFAA8E83), + fontSize: 16, + ), ), ), ), - ), SizedBox( width: 100, // Set the desired width child: TextButton( @@ -405,7 +412,8 @@ class _ChartScreen1State extends ConsumerState { backgroundColor: Color(0xFFAA8E83), // Set background color foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), // Set text color + borderRadius: + BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -413,7 +421,8 @@ class _ChartScreen1State extends ConsumerState { style: TextStyle(color: Colors.white, fontSize: 16), ), ), - ),], + ), + ], ), ], ), @@ -1268,13 +1277,28 @@ class _ChartScreen1State extends ConsumerState { }); } - Future fetchChartData(String dataSets, locale, kpi, filter_data ) async { - var data = await apiService.fetchChartData(dataSets, locale,kpi,filter_data); + Future fetchChartData(String dataSets, locale, kpi, filter_data) async { + var data = + await apiService.fetchChartData(dataSets, locale, kpi, filter_data); tabFilteredKpi = kpi; print('FETCH KPI- $kpi'); print('FETCH KFilter- $filter_data'); + print('locale1 - $locale'); + // print('locale11 - $loacleData'); + // Store filter_data if it has a value + if (filter_data != null && filter_data is List && filter_data.isNotEmpty) { + selectedFiltersApi = filter_data.map>((entry) { + if (entry is Map) { + return entry.map( + (key, value) => MapEntry(key.toString(), value)); + } + return {}; + }).toList(); + + print('FETVCFILTEr: $selectedFiltersApi'); + } if (data.containsKey('filterData')) { var filterData = data['filterData']; @@ -1316,7 +1340,6 @@ class _ChartScreen1State extends ConsumerState { void applyFilters(BuildContext context, List filters, List data, List dataCard, List selectedFilters) { - print('applyFilters called'); print('Selected ApplyFilters: $selectedFilters'); @@ -1332,37 +1355,38 @@ class _ChartScreen1State extends ConsumerState { } // Transform the selectedFilters list into the required format - formattedFilters = selectedFilters - .where((filter) => filter['filter_data'] != null && filter['filter_data'].isNotEmpty) - .map((filter){ - return{ + formattedFilters = selectedFilters + // .where((filter) => filter['filter_data'] != null && filter['filter_data'].isNotEmpty) + .map((filter) { + return { 'filter_key': filter['filter_key'], - 'filter_data':filter['filter_data'].map((item)=> item.toString()).toList() + 'filter_data': + filter['filter_data'].map((item) => item.toString()).toList() }; }).toList(); print('Formatted Filters1: $formattedFilters'); // Convert to JSON format - String selectedFormatFilters = jsonEncode({ - 'kpi': tabWiseKpi, - 'filter_data': formattedFilters - }); + String selectedFormatFilters = + jsonEncode({'kpi': tabWiseKpi, 'filter_data': formattedFilters}); print('Formatted Filters2: $selectedFormatFilters'); + final locale = ref.watch(localeProvider)?.languageCode ?? 'en'; - // Call fetchChartData with correct parameters + print('locale2- $locale'); + + // Call fetchChartData with correct parameters fetchChartData( widget.dataSets, - locale.languageCode ?? 'en', + locale, tabWiseKpi, // Ensure you are passing the correct KPI here formattedFilters // Pass the formatted filter data here - ); + ); Navigator.pop(context); selectedFiltersStorage = List.from(selectedFilters); print('selectedFiltersStoraged- $selectedFiltersStorage'); - } void applyFilters1(BuildContext context, List filters, List data, @@ -1503,7 +1527,6 @@ class _ChartScreen1State extends ConsumerState { } void showRightSideModal(BuildContext context, List filters, List data) { - print('selectedFiltersStoraged1- $selectedFiltersStorage'); // Initialize selected filters structure from the stored value, if exists @@ -1532,7 +1555,7 @@ class _ChartScreen1State extends ConsumerState { // Header Section Padding( padding: const EdgeInsets.only( - top: 16.0, bottom: 0.0, left: 16.0, right: 16.0), + top: 26.0, bottom: 0.0, left: 16.0, right: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -1541,7 +1564,8 @@ class _ChartScreen1State extends ConsumerState { SvgPicture.asset( UaeNumbersAssetPath.filterCharts, semanticsLabel: 'Filter', - colorFilter:ColorFilter.mode(Color(0xFF8E8E8E),BlendMode.srcIn), + colorFilter: ColorFilter.mode( + Color(0xFF8E8E8E), BlendMode.srcIn), width: 25, height: 25, ), @@ -1617,6 +1641,7 @@ class _ChartScreen1State extends ConsumerState { builder: (context, dialogSetState) { final locale = ref.watch(localeProvider); + print('localelocale $locale'); return AlertDialog( title: Text( @@ -1786,13 +1811,13 @@ class _ChartScreen1State extends ConsumerState { cardData = List.from(originalTabCardData); }); selectedFiltersStorage.clear(); - formattedFilters = []; + formattedFilters = []; fetchChartData( widget.dataSets, locale.languageCode ?? 'en', tabWiseKpi, // Ensure you are passing the correct KPI here formattedFilters // Pass the formatted filter data here - ); + ); Navigator.pop(context); }, style: ElevatedButton.styleFrom( @@ -1877,9 +1902,14 @@ class _ChartScreen1State extends ConsumerState { void onTabSelected(String tabId) { print('Selected Tab on: $tabId'); tabWiseKpi = tabId; + print('Selected Tab tabWiseKpi: $tabWiseKpi'); + + selectedFiltersStorage.clear(); + + if (selectedFiltersApi.isNotEmpty && tabWiseKpi == tabFilteredKpi) { + selectedFiltersStorage = List.from(selectedFiltersApi); + } - // selectedFiltersStorage.clear(); - // Filter the filterDataSet filterData = filterDataSet .where((item) => item['key'] == tabId) .map((item) => item['value']) @@ -1887,9 +1917,63 @@ class _ChartScreen1State extends ConsumerState { .expand((item) => item) .toList(); - print("FilteringDAtss START1 - $filterData"); + print("FilteringDAtss START1d - $filterData"); print('TABId1 - $tabId'); + Map monthMap = { + 'jan': 1, + 'feb': 2, + 'mar': 3, + 'apr': 4, + 'may': 5, + 'jun': 6, + 'jul': 7, + 'aug': 8, + 'sep': 9, + 'oct': 10, + 'nov': 11, + 'dec': 12 + }; + + for (var item in filterData) { + if (item['filter_key'] == 'TIME_PERIOD') { + List timePeriods = List.from(item['filter_data']); + + timePeriods.sort((a, b) { + List aParts = a.split(RegExp(r'[-_]')); + List bParts = b.split(RegExp(r'[-_]')); + + int yearA = int.parse(aParts[0]); + int yearB = int.parse(bParts[0]); + + // if (yearA != yearB) return yearA.compareTo(yearB); + if (yearA != yearB) + return yearB.compareTo(yearA); // Sort years in descending order + + if (aParts.length == 1) return -1; // Year-only comes first + if (bParts.length == 1) return 1; // Year-only comes first + + int monthA = + int.tryParse(aParts[1]) ?? monthMap[aParts[1].toLowerCase()] ?? 0; + int monthB = + int.tryParse(bParts[1]) ?? monthMap[bParts[1].toLowerCase()] ?? 0; + + return monthB.compareTo( + monthA); // Sort months in descending order within the same year + // return monthA.compareTo(monthB); + }); + + item['filter_data'] = timePeriods; + } else if (item['filter_key'] == 'REF_AREA') { + continue; + } else { + // Sort alphabetically for all other cases + List otherFilters = List.from(item['filter_data']); + otherFilters.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase())); + item['filter_data'] = otherFilters; + } + } + // for (var item in filterData) { // if (item['filter_key'] == 'TIME_PERIOD') { // // Convert the values to integers, sort them, and convert back to strings @@ -1914,7 +1998,6 @@ class _ChartScreen1State extends ConsumerState { } void performActionForTab(String tabId) { - print('Selected Tab perform: $tabId'); // Filter the chartsData array based on the tabId @@ -2007,25 +2090,39 @@ class _ChartScreen1State extends ConsumerState { 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); + print('Saving currentTab: $tabWiseKpi before locale change'); + print('localeCodeWgt- $localeCode'); + currentTab = tabWiseKpi; + print('Saving currentTab: $currentTab After locale change'); - - final List> safeFilterData = widget.filter_data ?? []; + final List> safeFilterData = + widget.filter_data ?? []; final String safeKpi = widget.kpi ?? ''; - fetchChartData(widget.dataSets,localeCode, safeKpi, safeFilterData).then((_) { + fetchChartData(widget.dataSets, localeCode, safeKpi, safeFilterData) + .then((_) { print('DEBUG KPI: $safeKpi'); - if (_tabsData.isNotEmpty) { + + print('TAB11: $currentTab'); + + if (currentTab.isNotEmpty) { // Call onTabSelected for the first tab + onTabSelected(currentTab); + print('TAB1: $currentTab'); + } else if (_tabsData.isNotEmpty) { + // Call onTabSelected for the first tab + print('TAB2: $_tabsData'); onTabSelected(_tabsData[0]['id']); } }); - - }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; + mainTopic = chartScreenData['main_topic'] ?? ''; int crossAxisCount = cardData.isNotEmpty ? (cardData.length / 2).ceil().clamp(1, 2) : 1; @@ -2046,8 +2143,10 @@ class _ChartScreen1State extends ConsumerState { (chartScreenData['body_color'] ?? '#898C81').replaceFirst('#', '0xff'), ), ); + final chartHeader = chartScreenData['main_topic'] ?? ''; print('ChartScrnBodyColor'); + print('chartHeader - $chartHeader'); print(chartScreenData['body_color']); print( Color(int.parse((chartScreenData['body_color'] ?? '#898C81') @@ -2098,7 +2197,8 @@ class _ChartScreen1State extends ConsumerState { children: [ Text( // widget.mainTopic ?? '', - chartScreenData['main_topic'] ?? '', + // chartScreenData['main_topic'] ?? '', + mainTopic, style: TextStyle( color: Colors.white, fontSize: 26, @@ -2223,28 +2323,29 @@ class _ChartScreen1State extends ConsumerState { Row( children: [ GestureDetector( - onTap: () => shareCurrentPage(context), - - child: Row( + onTap: () => shareCurrentPage(context), + child: Row( children: [ - Text( - context.translate( - 'Share', - 'يشارك', + Text( + context.translate( + 'Share', + 'يشارك', + ), + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), ), - style: const TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - SizedBox(width: 5), - SvgPicture.asset( - UaeNumbersAssetPath.share, - semanticsLabel: 'share', - width: 24, - height: 24, - ) - ],),), + SizedBox(width: 5), + SvgPicture.asset( + UaeNumbersAssetPath.share, + semanticsLabel: 'share', + width: 24, + height: 24, + ) + ], + ), + ), ], ), SizedBox( @@ -2353,7 +2454,8 @@ class _ChartScreen1State extends ConsumerState { // ), // nonChartData Cards Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.only( + top: 8.0, left: 1.0, right: 1.0), child: LayoutBuilder( builder: (context, constraints) { List rows = []; @@ -2431,23 +2533,32 @@ class _ChartScreen1State extends ConsumerState { children: [ // ConstrainedBox Container( - height: (chartsData[index] - ['chart_type'] == - 'pie_chart') - ? 500 - : (chartsData[index][ - 'chart_type'] == - 'bar_chart_horizontal') - ? 370 - : (chartsData[index]['chart_type'] == 'fl_stacked_bar') - ? 390 - : 350, + height: (chartsData[index] + ['chart_type'] == + 'pie_chart') + ? 500 + : (chartsData[index][ + 'chart_type'] == + 'bar_chart_horizontal') + ? 370 + : (chartsData[index][ + 'chart_type'] == + 'horizontal_rotate') + ? 385 + : (chartsData[index] + [ + 'chart_type'] == + 'fl_stacked_bar') + ? 390 + : 350, - // child: buildChart(chartsData[index]), - child: ChartWidget( - chartData: - chartsData[index], - bodyColor: bodyColor)) + // child: buildChart(chartsData[index]), + child: ChartWidget( + chartData: + chartsData[index], + bodyColor: bodyColor, + chartHeader: chartHeader), + ) ], ), ), @@ -2595,16 +2706,14 @@ class CardWidget extends StatelessWidget { final bodyColor = Color( int.parse( - (chartScreenData['body_color'] ?? '#898C81').replaceFirst('#', '0xff'), + (chartScreenData['border_color'] ?? '#898C81') + .replaceFirst('#', '0xff'), ), ); - - if(response.isEmpty) - { - return SizedBox.shrink(); - } - else if (response.length == 1) { + if (response.isEmpty) { + return SizedBox.shrink(); + } else if (response.length == 1) { return Container( margin: const EdgeInsets.all(10), // width: card_full_length ? double.infinity : null, @@ -2626,7 +2735,7 @@ class CardWidget extends StatelessWidget { elevation: 2, child: Container( // height: cardHeight, - height: 160, + height: 175, padding: const EdgeInsets.only( left: 16.0, right: 16.0, bottom: 2.0, top: 1.0), decoration: BoxDecoration( @@ -2757,7 +2866,7 @@ class CardWidget extends StatelessWidget { elevation: 0, // Remove shadow to keep only the outli child: Container( - height: 160, + height: 175, padding: const EdgeInsets.all(10.0), // padding: const EdgeInsets.only( // left: 10.0, right: 10.0, bottom: 5.0, top: 1.0), @@ -2767,7 +2876,8 @@ class CardWidget extends StatelessWidget { BorderRadius.circular(12), // Ensure border radius is applied ), child: Column( - mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: + MainAxisSize.min, // Adjust card height based on content mainAxisAlignment: MainAxisAlignment.center, children: [ // const Icon(Icons.public, @@ -2840,8 +2950,6 @@ class CardWidget extends StatelessWidget { ), const SizedBox(height: 1), - - Row( mainAxisAlignment: MainAxisAlignment .center, // Center-aligns the row contents @@ -2870,19 +2978,15 @@ class CardWidget extends StatelessWidget { ], ), - SizedBox( height: 2, // Height of the divider - width: 100, + width: 100, child: Divider( color: Color(0xFFBBBCBD), thickness: 1, // Divider line thickness ), ), - - - Row( mainAxisAlignment: MainAxisAlignment .center, // Center-aligns the row contents @@ -2897,7 +3001,8 @@ class CardWidget extends StatelessWidget { : _getColorFromHex(response[1]['font_color']), ), textAlign: TextAlign.center, // Ensure proper alignment - overflow: TextOverflow.ellipsis, // Truncate if text overflows + overflow: + TextOverflow.ellipsis, // Truncate if text overflows ), const SizedBox(width: 1), // Space between text and icon if (response[1]['font_color'] != '') ...[ @@ -2911,7 +3016,6 @@ class CardWidget extends StatelessWidget { ], ), - Text( // '(${response[1]['display_value'] ?? 'NA'})', RegExp(r'\d').hasMatch(response[1]['display_value'] ?? '') diff --git a/lib/presentation/Screens/charts/services/api_service.dart b/lib/presentation/Screens/charts/services/api_service.dart index dfc29379..634e0793 100644 --- a/lib/presentation/Screens/charts/services/api_service.dart +++ b/lib/presentation/Screens/charts/services/api_service.dart @@ -16,7 +16,7 @@ class ApiService { // static const String baseUrl = 'https://pb.venbait.in/api/getDataSet'; static const String baseUrl = '$apiUrl/api/getDataSet'; - Future> fetchChartData(String dataSets, locale,kpi, + Future> fetchChartData(String dataSets, locale, kpi, List> filterData) async { List isChartData = []; List nonChartData = []; @@ -28,14 +28,14 @@ class ApiService { // '$baseUrl?dataset=$dataSets&language=$locale&kpi=$kpi&filter_data=${Uri.encodeComponent(jsonEncode(filterData))}' // ); - final url = Uri.parse(baseUrl); // No query parameters in URL for POST final Map requestBody = { 'dataset': dataSets, 'language': locale, 'kpi': kpi.isNotEmpty ? kpi : null, // Avoid sending empty kpi - 'filter_data': filterData.isNotEmpty ? filterData : null, // Avoid empty list + 'filter_data': + filterData.isNotEmpty ? filterData : [], // Avoid empty list }; // final url = Uri.https('$baseUrl?dataset=$dataSets&language=$locale'); @@ -66,8 +66,9 @@ class ApiService { nonChartData.add(item); } } - final Map chartScreenData = jsonData['screen_heading_and_color']; - + final Map chartScreenData = + jsonData['screen_heading_and_color']; + // Save original data for reset // originalChartsData = List.from(isChartData); // Store original data // originalCardData = List.from(nonChartData); // Store original data @@ -77,7 +78,7 @@ class ApiService { 'isChartData': isChartData, 'nonChartData': nonChartData, 'filterData': jsonData['new_filter_data'], - 'chartScreenData' :chartScreenData + 'chartScreenData': chartScreenData // 'filterData': jsonData['filter_data'] // 'originalChartsData': originalChartsData, // 'originalCardData': originalCardData, @@ -195,4 +196,4 @@ class ApiService { return '$amount'; } } -} \ No newline at end of file +} diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 8c95e730..8926cf5a 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -7,10 +7,114 @@ import 'package:flutter/material.dart'; // import 'package:syncfusion_flutter_charts/charts.dart'; class ChartWidget extends StatelessWidget { - ChartWidget({Key? key, required this.chartData, required this.bodyColor}) + ChartWidget( + {Key? key, + required this.chartData, + required this.bodyColor, + required this.chartHeader}) : super(key: key); final dynamic chartData; final Color bodyColor; + final String chartHeader; + + List showingTooltipOnSpots = []; // To store indices of active tooltips + late LineChartBarData tooltipsOnBar; // Needs proper initialization + + // Define color lists + final Map> colorMap = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + Color(0xFFC6A885), + Color(0xFFA28565), + Color(0xFFBFB29B), + Color(0xFFE8D8BB), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + Color(0xFF7DAFBC), + Color(0xFF86C7D9), + Color(0xFF989898), + ], + }; + + final Map> colorMapForThree = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + Color(0xFFC6A885), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + Color(0xFF7DAFBC), + ], + }; + + // Define color lists + final Map> colorMapForTwo = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + ], + }; + + // List get uniqueColors => colorMap[chartHeader] ?? [Colors.grey]; // Default to grey if header is unknown +// Function to get colors for both English & Arabic keys + List get uniqueColors { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMap[key] ?? [Colors.grey]; + } + + List get uniqueColorsForTwo { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMapForTwo[key] ?? [Colors.grey]; + } + + List get uniqueColorsForThree { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMapForThree[key] ?? [Colors.grey]; + } String capitalizeAndSplit(String input) { return input @@ -19,6 +123,18 @@ class ChartWidget extends StatelessWidget { .join(' '); } + Color _hexToColor(String hexColor) { + hexColor = hexColor.toUpperCase().replaceAll('#', ''); // Remove # + if (hexColor.length == 6) { + hexColor = 'FF$hexColor'; // Add alpha if missing + } + return Color(int.parse(hexColor, radix: 16)); + } + + String formatVerticalText(String text) { + return text.split('').join('\n'); // Inserts a newline after each character + } + String formatNumber(double value) { if (value >= 1e12) { return '${(value / 1e12).toStringAsFixed(2)}T'; @@ -34,7 +150,10 @@ class ChartWidget extends StatelessWidget { } String formatNumberConversion( - double value, String chartConversion, String numberFormat,) { + double value, + String chartConversion, + String numberFormat, + ) { // Convert the input value to base unit (actual value) double baseValue = value; @@ -91,11 +210,20 @@ class ChartWidget extends StatelessWidget { }).toList(); } + Color hexToColor(String hex) { + hex = hex.replaceFirst('#', ''); // Remove # + if (hex.length == 6) { + hex = 'FF$hex'; // Add full opacity + } + return Color(int.parse(hex, radix: 16)); + } + Color adjustColor(Color baseColor, int index) { final HSLColor hsl = HSLColor.fromColor(baseColor); // Lightness variation (keeping it light, between 0.6 and 0.9) - double lightnessFactor = 0.6 + (index % 4) * 0.1; // Varies between 0.6 to 0.8 + double lightnessFactor = + 0.6 + (index % 4) * 0.1; // Varies between 0.6 to 0.8 final HSLColor adjustedHSL = hsl.withLightness( lightnessFactor.clamp(0.6, 0.9), // Ensures light shades only ); @@ -103,6 +231,23 @@ class ChartWidget extends StatelessWidget { return adjustedHSL.toColor(); } + Color adjustColorlineTrend(Color baseColor, int index) { + final HSLColor hsl = HSLColor.fromColor(baseColor); + + // Darken the color slightly by reducing lightness + double lightnessFactor = hsl.lightness - ((index % 4) * 0.05); + lightnessFactor = lightnessFactor.clamp(0.3, 0.7); // Keeps colors darker + + // Adjust opacity: Making it darker with alpha between 160-230 + int alphaFactor = + (230 - (index % 4) * 20).clamp(160, 230); // Darker opacity + + return hsl + .withLightness(lightnessFactor) + .toColor() + .withAlpha(alphaFactor); // Use withAlpha() instead of withOpacity() + } + List parsePieChartData( dynamic chartData, double totalValue, @@ -138,7 +283,8 @@ class ChartWidget extends StatelessWidget { return PieChartSectionData( value: value, // color: Colors.primaries[index % Colors.primaries.length], - color: adjustColor(bodyColor, index), // Adjust alpha dynamically + // color: adjustColor(bodyColor, index), // Adjust alpha dynamically + color: uniqueColors[index % uniqueColors.length], title: '${percentage.toStringAsFixed(1)}%', radius: isTouched ? 60 : 50, @@ -170,7 +316,7 @@ class ChartWidget extends StatelessWidget { width: 10, height: 10, decoration: BoxDecoration( - color: adjustColor(bodyColor, index), + color: uniqueColors[index % uniqueColors.length], shape: BoxShape.circle, ), ), @@ -194,9 +340,9 @@ class ChartWidget extends StatelessWidget { style: TextStyle( fontSize: 12, ), - // softWrap: true, - // maxLines: 2, - overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow + softWrap: true, + maxLines: 2, + // overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow ), ), ), @@ -207,6 +353,38 @@ class ChartWidget extends StatelessWidget { }).toList(); } + List generateIndicatorsBar( + Map chartData, List groupByValues) { + List indicators = []; + + for (int i = 0; i < groupByValues.length; i++) { + String groupLabel = groupByValues[i]; + + indicators.add( + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, // Replace with dynamic color if needed + ), + ), + SizedBox(width: 5), + Text( + groupLabel, // Displaying groupLabel + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ], + ), + ); + } + + return indicators; + } + Set extractGroupByValues(dynamic chartData, String groupByKey) { return (chartData['response'] as List).map((entry) { var value = entry['ObsKey'][groupByKey]; @@ -216,6 +394,12 @@ class ChartWidget extends StatelessWidget { }).toSet(); } + // @override + // void initState() { + // super.initState(); + // tooltipsOnBar = lineBars.first; // Example: Assign the first line bar + // } + Widget buildChart(dynamic chartData, BuildContext context) { // print('bodyColor- $bodyColor'); print('chartDataccccccc $chartData'); @@ -225,13 +409,23 @@ class ChartWidget extends StatelessWidget { } print(chartData['chart_type_json']['TIME_PERIOD']); + print(chartData['chart_type_json']['x_group']); - String groupByKey = chartData['group_by'] ?? ''; + String groupByKey = ''; + + if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || + chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { + groupByKey = chartData['chart_type_json']['y_group'] ?? ''; + } else { + groupByKey = chartData['chart_type_json']['x_group'] ?? ''; + } + + // String groupByKey = chartData['group_by'] ?? ''; print('groupByKey $groupByKey'); Set groupByValues = extractGroupByValues(chartData, groupByKey); print('groupByValues $groupByValues'); - switch (chartData['chart_type']) { + switch (chartData['chart_type_json']['chart_type']) { // case 'stacked_bar': // String chartTitle = // capitalizeAndSplit(chartData['chart_heading'] ?? ''); @@ -385,30 +579,31 @@ class ChartWidget extends StatelessWidget { // child: SingleChildScrollView( // scrollDirection: Axis.vertical, - child: Center( - child: Container( - width: double.infinity, - constraints: - BoxConstraints(minHeight: 3), // Allow dynamic height - child: Padding( - padding: const EdgeInsets.only( - left: 2.0, right: 2.0, bottom: 8.0, top: 1.0), - child: Column( // Change Row to Column - crossAxisAlignment: CrossAxisAlignment.start, - children: generateIndicators(chartData, chartData['group_by']), - ), - - - // child: Wrap( - // spacing: 2, - // runSpacing: 5, - // children: - // generateIndicators(chartData, chartData['group_by']), - // ), + child: Center( + child: Container( + width: double.infinity, + constraints: + BoxConstraints(minHeight: 3), // Allow dynamic height + child: Padding( + padding: const EdgeInsets.only( + left: 2.0, right: 2.0, bottom: 8.0, top: 1.0), + child: Column( + // Change Row to Column + crossAxisAlignment: CrossAxisAlignment.start, + children: + generateIndicators(chartData, chartData['group_by']), ), + + // child: Wrap( + // spacing: 2, + // runSpacing: 5, + // children: + // generateIndicators(chartData, chartData['group_by']), + // ), ), ), ), + ), // ), ], ); @@ -462,12 +657,11 @@ class ChartWidget extends StatelessWidget { horizontal: 4, vertical: 8), // Optional tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipMargin: 16, - + getTooltipColor: (colorMap) => Colors.black, getTooltipItem: (groupData, groupIndex, rodData, rodIndex) { // Get the list of group names dynamically - List groupNames = []; for (var entry in chartData['response']) { String groupValue = entry['ObsKey'][groupByKey]; @@ -501,8 +695,6 @@ class ChartWidget extends StatelessWidget { Color groupColor = _getColorForGroup( i); // Replace with your color logic - - // Add a TextSpan for the colored circle and the group name with value tooltipTextSpans.addAll([ TextSpan( @@ -587,7 +779,8 @@ class ChartWidget extends StatelessWidget { waitDuration: Duration(milliseconds: 500), showDuration: Duration(seconds: 2), decoration: BoxDecoration( - color: Colors.blueGrey[900], + color: Colors.black, + // color: Colors.blueGrey[900], borderRadius: BorderRadius.circular(4), ), textStyle: TextStyle(color: Colors.white), @@ -619,11 +812,14 @@ class ChartWidget extends StatelessWidget { ]; Map groupColorMap = {}; int colorIndex = 0; - for (String group in groupByValues) { - groupColorMap[group] = uniqueColorsLine_trend_2[ - colorIndex % uniqueColorsLine_trend_2.length]; - colorIndex++; - } + + // for (String group in groupByValues) { + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } print('LnTrnd1'); // Extract all years from the chart data @@ -699,8 +895,18 @@ class ChartWidget extends StatelessWidget { } else if (RegExp(r'^\d{4}-[A-Za-z]{3}$').hasMatch(timePeriod)) { // Year-MonthAbbr (2019-Nov) Map monthMap = { - 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, - 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + 'Jan': 1, + 'Feb': 2, + 'Mar': 3, + 'Apr': 4, + 'May': 5, + 'Jun': 6, + 'Jul': 7, + 'Aug': 8, + 'Sep': 9, + 'Oct': 10, + 'Nov': 11, + 'Dec': 12 }; List parts = timePeriod.split('-'); @@ -721,9 +927,41 @@ class ChartWidget extends StatelessWidget { print('LnTrnd2.1'); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + print('parsedChartBarColors1 - $parsedChartBarColors'); + + for (String group in groupByValues) { + if (hasBarColors && chartData['dataset'] == 'population') { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart - List lineBars = - lineBarsData(filteredData, groupByValues, groupByKey); + List lineBars = lineBarsData( + filteredData, groupByValues, groupByKey, parsedChartBarColors); print('LnTrnd3'); @@ -749,6 +987,16 @@ class ChartWidget extends StatelessWidget { // Chart Expanded( child: LineChart(LineChartData( + // showingTooltipIndicators: showingTooltipOnSpots.map((index) { + // return ShowingTooltipIndicators([ + // LineBarSpot( + // tooltipsOnBar, + // lineBarsData.indexOf(tooltipsOnBar), + // tooltipsOnBar.spots[index], + // ), + // ]); + // }).toList(), + lineTouchData: lineTouchData1(), gridData: gridData(), // titlesData: titlesData1(uniqueXValues), @@ -797,11 +1045,13 @@ class ChartWidget extends StatelessWidget { 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++; - } + // for (String group in groupByValues) { + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // colorIndex++; + // } // Extract all years from the chart data List years = (chartData['response'] as List) @@ -825,7 +1075,7 @@ class ChartWidget extends StatelessWidget { selectedYears.add(latestYear); // Include the latest year in selection } -// Filter chart data to include only the selected years + // 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; @@ -837,9 +1087,39 @@ class ChartWidget extends StatelessWidget { (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) .toSet(); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + for (String group in groupByValues) { + if (parsedChartBarColors.containsKey(group)) { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart - List lineBars = - lineBarsData(filteredData, groupByValues, groupByKey); + List lineBars = lineBarsData( + filteredData, groupByValues, groupByKey, parsedChartBarColors); return Column(children: [ Text( @@ -864,7 +1144,7 @@ class ChartWidget extends StatelessWidget { Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling - padding: const EdgeInsets.only(right: 40), + padding: const EdgeInsets.only(right: 40, top: 10), child: SizedBox( width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically @@ -907,22 +1187,24 @@ class ChartWidget extends StatelessWidget { // Chart ]); case 'line_trend_2': - final List uniqueColorsLine_trend_2 = [ - Color(0xFF6097CD), - Color(0xFFD086A7), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // 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++; - } + // for (String group in groupByValues) { + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } // Extract all years from the chart data List years = (chartData['response'] as List) @@ -960,11 +1242,39 @@ class ChartWidget extends StatelessWidget { print('uniqueXValuesLineTrend2- $uniqueXValues'); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + for (String group in groupByValues) { + if (parsedChartBarColors.containsKey(group)) { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart List lineBars = lineBarsData2(filteredData); - - return Column(children: [ Text( chartData['chart_heading'] ?? '', @@ -990,7 +1300,8 @@ class ChartWidget extends StatelessWidget { scrollDirection: Axis.horizontal, // Enable horizontal scrolling padding: const EdgeInsets.only(right: 40, top: 20), child: SizedBox( - width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically + width: (uniqueXValues.length * 50) + + 50, // Adjust width dynamically child: LineChart( LineChartData( lineTouchData: lineTouchData1(), @@ -1037,6 +1348,7 @@ class ChartWidget extends StatelessWidget { print('yValue-$yValue'); print('unitMsr-$unitMsr'); + //Handled For Quarter Chart Case (sort by Year and Quarter) if (xadditionalgrp == 'TIME_PERIOD') { chartData['response'].sort((a, b) { // Convert TIME_PERIOD to int for proper sorting @@ -1074,7 +1386,6 @@ class ChartWidget extends StatelessWidget { } xAxisData.add(xLabel); - yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); // yAxisLabels.add(unitMsr ?? ''); } @@ -1099,179 +1410,226 @@ class ChartWidget extends StatelessWidget { ), 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, - // touchCallback: (FlTouchEvent event, barTouchResponse) { - // if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { - // return; - // } - // }, - // touchTooltipData: BarTouchTooltipData( - // getTooltipColor: (group) => Colors.transparent, - // getTooltipItem: (group, groupIndex, rod, rodIndex) { - // if (rod.toY == 0) return null; // Hide for zero values - // return BarTooltipItem( - // formatNumber(rod.toY), - // TextStyle(color: Colors.black), - // ); - // }, - // ), - // ), + Expanded(child: LayoutBuilder( + builder: (context, constraints) { + double chartWidth = + xAxisData.length * (40 + 10); // Compute chart width + double screenWidth = constraints.maxWidth; - // barTouchData: BarTouchData(enabled: true), - barTouchData: BarTouchData( - enabled: false, - handleBuiltInTouches: false, - touchTooltipData: BarTouchTooltipData( - getTooltipColor: (group) => Colors.transparent, - // fitInsideHorizontally: true, - // fitInsideVertically: true, - // tooltipPadding: const EdgeInsets.all(8), - tooltipMargin: 1, - getTooltipItem: (group, groupIndex, rod, rodIndex) { - // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length - // ? yAxisLabels[groupIndex] - // : ''; - if (rod.toY == 0) return null; - - return BarTooltipItem( - (chartConversion != null && - chartConversion.isNotEmpty && - number_format != null && - chartConversion.isNotEmpty) - ? formatNumberConversion(rod.toY, chartConversion, - number_format) // If condition is true - : formatNumber(rod.toY), - const TextStyle( - color: Colors.black, - fontSize: 12, - fontWeight: FontWeight.w400, + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.only(top: 10, left: 5), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: + screenWidth, // Ensure it at least fills available width + maxWidth: chartWidth > screenWidth + ? chartWidth + : screenWidth, // Prevent non-normalized constraints ), - ); - }, - ), - ), - 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; + child: Center( + child: SizedBox( + width: xAxisData.length * + (40 + 10), // Bar width + manual spacing + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData + .reduce((a, b) => a > b ? a : b) * + 1.2 + : 10, + // barTouchData: BarTouchData( + // enabled: true, + // touchCallback: (FlTouchEvent event, barTouchResponse) { + // if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { + // return; + // } + // }, + // touchTooltipData: BarTouchTooltipData( + // getTooltipColor: (group) => Colors.transparent, + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0) return null; // Hide for zero values + // return BarTooltipItem( + // formatNumber(rod.toY), + // TextStyle(color: Colors.black), + // ); + // }, + // ), + // ), - return Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 55.0), - child: SizedBox( - width: 60, // Limit width to force wrapping - child: Transform.rotate( - // angle: -0.5, - angle: -1.5, + // barTouchData: BarTouchData(enabled: true), + barTouchData: BarTouchData( + enabled: false, + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => + Colors.transparent, + // fitInsideHorizontally: true, + // fitInsideVertically: true, + // tooltipPadding: const EdgeInsets.all(8), + tooltipMargin: 1, + getTooltipItem: + (group, groupIndex, rod, rodIndex) { + // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length + // ? yAxisLabels[groupIndex] + // : ''; + if (rod.toY == 0) return null; - child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color - borderRadius: BorderRadius.circular( - 8), // Optional: rounded corners - ), - textStyle: TextStyle( - color: Colors - .white), // Change text color - ), - child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color - borderRadius: BorderRadius.circular( - 8), // Optional: rounded corners + return BarTooltipItem( + (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null && + chartConversion.isNotEmpty) + ? formatNumberConversion( + rod.toY, + chartConversion, + number_format) // If condition is true + : formatNumber(rod.toY), + const TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, ), - textStyle: TextStyle( - color: Colors - .white), // Change text color - ), - child: Tooltip( - message: title, - child: Text( - // title, - displayTitle, - softWrap: true, - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 10, - ), - overflow: TextOverflow.ellipsis, - ), - ), + ); + }, + ), + ), + 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, ), ), - ))); - } - return Container(); - }, - reservedSize: 110, - ), - ), - 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: true, - border: const Border( - // left: BorderSide(color: Colors.grey), - bottom: BorderSide(color: Colors.grey), - ), - ), - barGroups: List.generate( - xAxisData.length, - (index) => BarChartGroupData( - x: index, - barRods: [ - BarChartRodData( - toY: yAxisData[index], - color: bodyColor, - borderRadius: BorderRadius.circular(4), - width: 20, - ), - ], - showingTooltipIndicators: [0], - ), - ), - ), + 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, left: 75.0), + child: SizedBox( + width: + 60, // Limit width to force wrapping + child: Transform.rotate( + // angle: -0.5, + angle: -1.5, + + child: TooltipTheme( + data: TooltipThemeData( + decoration: + BoxDecoration( + color: Colors + .blueGrey[ + 800], // Change background color + borderRadius: + BorderRadius.circular( + 8), // Optional: rounded corners + ), + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), + child: TooltipTheme( + data: TooltipThemeData( + decoration: + BoxDecoration( + color: Colors + .black, // Change background color + // color: Colors.blueGrey[800], // Change background color + borderRadius: + BorderRadius + .circular( + 8), // Optional: rounded corners + ), + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), + child: Tooltip( + message: title, + child: Text( + // title, + displayTitle, + softWrap: true, + textAlign: + TextAlign.end, + style: TextStyle( + fontSize: 10, + ), + overflow: + TextOverflow + .ellipsis, + ), + ), + ), + ), + ))); + } + return Container(); + }, + reservedSize: 110, + ), + ), + 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: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.grey), + ), + ), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barsSpace: 10, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: bodyColor, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + showingTooltipIndicators: [0], + ), + ), + ), + )), + ))); + }, )) ]); case 'bar_chart_horizontal': @@ -1279,6 +1637,8 @@ class ChartWidget extends StatelessWidget { List xAxisData = []; List yAxisData = []; List> sortedData = []; + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; for (var entry in chartData['response']) { String? timePeriod = entry['ObsKey']['TIME_PERIOD']; @@ -1299,15 +1659,16 @@ class ChartWidget extends StatelessWidget { String timeA = a['ObsKey']['TIME_PERIOD']; String timeB = b['ObsKey']['TIME_PERIOD']; - return timeA.compareTo(timeB); // Lexicographical sorting works for YYYY-MM + return timeA + .compareTo(timeB); // Lexicographical sorting works for YYYY-MM }); // If no valid TIME_PERIOD with a month is found, return original response - List> finalData = sortedData.isNotEmpty ? sortedData : List.from(chartData['response']); - + List> finalData = sortedData.isNotEmpty + ? sortedData + : List.from(chartData['response']); for (var entry in finalData) { - var xValue = entry['ObsKey'][groupByKey]; var yValue = entry['ObsValue']['Value']; if (xValue != null && yValue != null) { @@ -1345,9 +1706,10 @@ class ChartWidget extends StatelessWidget { child: BarChart( BarChartData( // alignment: BarChartAlignment.spaceAround, - maxY: yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 - : 10, + // maxY: yAxisData.isNotEmpty + // ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + // : 10, + maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( enabled: false, @@ -1355,15 +1717,21 @@ class ChartWidget extends StatelessWidget { touchTooltipData: BarTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, - tooltipPadding: const EdgeInsets.all(8), - tooltipMargin: 16, + tooltipPadding: const EdgeInsets.all(5), + tooltipMargin: 5, getTooltipColor: (group) => Colors.transparent, getTooltipItem: (group, groupIndex, rod, rodIndex) { if (rod.toY == 0) return null; return BarTooltipItem( - formatNumber(rod.toY), - const TextStyle( + // formatNumber(rod.toY), + (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null) + ? formatNumberConversion( + rod.toY, chartConversion, number_format) + : formatNumber(rod.toY), + TextStyle( color: Colors.black, fontSize: 12, fontWeight: FontWeight.w400, @@ -1407,10 +1775,10 @@ class ChartWidget extends StatelessWidget { xAxisData[value.toInt()], textAlign: TextAlign.right, style: const TextStyle( - fontSize: 12, + fontSize: 10, ), softWrap: true, - maxLines: 3, + maxLines: 2, ), ), ), @@ -1442,12 +1810,12 @@ class ChartWidget extends StatelessWidget { xAxisData.length, (index) => BarChartGroupData( x: index, - // barsSpace: 2, + barsSpace: 20, barRods: [ BarChartRodData( toY: yAxisData[index], // color: Colors.blueAccent, - color: bodyColor, + color: uniqueColors[1], borderRadius: BorderRadius.circular(4), width: 20, ), @@ -1465,8 +1833,43 @@ class ChartWidget extends StatelessWidget { const double barWidth = 30; // Width for each bar, including spacing return totalBars * barWidth; // Calculate total chart width } + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + if (kDebugMode) { + print('Converted barColorsMap: $barColorsMap'); + print('hasBarColors: $hasBarColors'); + } print('multi_bar'); + if (chartData.containsKey('chart_bar_color')) { + print('bar_colors - $chartBarColors'); + } + if (kDebugMode) { + print('chartBarColors: $chartBarColors'); + print('chartBarColors Type: ${chartBarColors.runtimeType}'); + print('chartBarColors keys: ${chartBarColors?.keys}'); + print('chartBarColors is null? ${chartBarColors == null}'); + print('chartBarColors is empty? ${chartBarColors?.isEmpty}'); + } String cropKey = chartData['chart_type_json']['x_sub_group']; // Group crops by CROP_TYPE @@ -1535,13 +1938,20 @@ class ChartWidget extends StatelessWidget { 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 + // barGroups: _buildHorizontalRotateBarGroups( + // chartData, groupByValues), // Build bar groups + barGroups: hasBarColors + ? _buildHorizontalRotateBarGroupsBarColors( + chartData, groupByValues, parsedChartBarColors) + : _buildHorizontalRotateBarGroups( + chartData, groupByValues), + titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), @@ -1549,7 +1959,7 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 80, // Added space for rotated titles + reservedSize: 130, // Added space for rotated titles getTitlesWidget: (value, meta) { if (value < groupByValues.length) { String title = @@ -1560,30 +1970,35 @@ class ChartWidget extends StatelessWidget { : title; return Padding( - padding: const EdgeInsets.only(top: 8.0,left: 30.0), + padding: const EdgeInsets.only( + top: 8.0, left: 95.0), child: SizedBox( - width: 60, // Limit width to force wrapping + width: + 80, // Limit width to force wrapping child: Transform.rotate( angle: -1.5, child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color + color: Colors + .black, // Change background color + // color: Colors.blueGrey[800], // Change background color borderRadius: BorderRadius.circular( 8), // Optional: rounded corners ), textStyle: TextStyle( - color: Colors.white), // Change text color + color: Colors + .white), // Change text color ), child: Tooltip( message: title, child: Text( - displayTitle, + title, softWrap: true, - maxLines:2 , + maxLines: 2, + textAlign: TextAlign.end, style: TextStyle(fontSize: 10), - overflow: TextOverflow.ellipsis, + // overflow: TextOverflow.ellipsis, )), ), ))); @@ -1596,6 +2011,7 @@ class ChartWidget extends StatelessWidget { // ), // ); } + return const SizedBox.shrink(); }, ), @@ -1606,8 +2022,11 @@ class ChartWidget extends StatelessWidget { AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + enabled: true, touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => Colors.black, tooltipHorizontalAlignment: FLHorizontalAlignment.center, tooltipRoundedRadius: 8, @@ -1616,11 +2035,16 @@ class ChartWidget extends StatelessWidget { 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 (rod.toY == 0 || touchedGroupIndex == -1) { + // return null; // Don't show the tooltip if the value is 0 or there's no touch + // } + if (rod.toY == 0) { + return null; // Don't show for zero values } + if (groupIndex == touchedGroupIndex) { // Get the group label dynamically String groupLabel = @@ -1651,19 +2075,6 @@ class ChartWidget extends StatelessWidget { // '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( @@ -1672,10 +2083,10 @@ class ChartWidget extends StatelessWidget { ), children: [ TextSpan( - // text: ' Value: ${rod.toY}', - text: ' Value: $formattedValue', + text: + '- ${(chartConversion != null && chartConversion.isNotEmpty && number_format != null) ? formatNumberConversion(rod.toY, chartConversion, number_format) : formatNumber(rod.toY)}', style: const TextStyle( - color: Colors.yellow, + color: Colors.white, fontWeight: FontWeight.w500, ), ), @@ -1744,6 +2155,25 @@ class ChartWidget extends StatelessWidget { ((numberOfBars * barHeight) + ((numberOfBars - 1) * barSpacing)) .clamp(minHeight, maxHeight); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + return Column(children: [ Text( chartData['chart_heading'] ?? '', @@ -1777,12 +2207,14 @@ class ChartWidget extends StatelessWidget { child: Stack(children: [ BarChart( BarChartData( - maxY: 400000, + // maxY: 400000, + maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( // enabled: true, enabled: false, touchTooltipData: BarTouchTooltipData( + // getTooltipColor: (group) => Colors.black12, getTooltipColor: (group) => Colors.transparent, tooltipHorizontalAlignment: FLHorizontalAlignment.center, @@ -1790,7 +2222,8 @@ class ChartWidget extends StatelessWidget { fitInsideHorizontally: true, // Ensure it fits within the screen fitInsideVertically: true, - tooltipPadding: EdgeInsets.all(8), + tooltipPadding: EdgeInsets.only( + left: 3, right: 3, top: 0.5, bottom: 1), tooltipMargin: 16, // Only show tooltip when touched getTooltipItem: (group, groupIndex, rod, rodIndex) { @@ -1826,13 +2259,35 @@ class ChartWidget extends StatelessWidget { 0); // for values smaller than 1000 } + // return BarTooltipItem( + // // '$groupLabel\n$crop', + // '$formattedValue', + // const TextStyle( + // color: Colors.black, + // fontWeight: FontWeight.w400, + // fontSize: 12, + // ), + // ); + return BarTooltipItem( - '$formattedValue', + '$formattedValue', // This is your main tooltip text + const TextStyle( color: Colors.black, fontWeight: FontWeight.w400, fontSize: 12, ), + children: [ + TextSpan( + text: '', // Add extra information here + // text: '\n$crop', // Add extra information here + style: TextStyle( + color: Colors.grey[ + 700], // Optional: Different color for extra text + fontSize: 8, + ), + ), + ], ); // return BarTooltipItem( @@ -1950,16 +2405,26 @@ class ChartWidget extends StatelessWidget { ); }, ), - barGroups: _buildHorizontalRotateBarGroups( - chartData, groupByValues), + // barGroups: _buildHorizontalRotateBarGroups( + // chartData, groupByValues), + + barGroups: hasBarColors + ? _buildHorizontalRotateBarGroupsBarColors( + chartData, groupByValues, parsedChartBarColors) + : _buildHorizontalRotateBarGroups( + chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, ), ), ])), - - // ), - // ) ), + + // ), + // ) + + const SizedBox(height: 10), // Space between chart and legend + _buildChartLegend(groupedCrops, parsedChartBarColors, uniqueColors), ]); default: @@ -2026,7 +2491,7 @@ class ChartWidget extends StatelessWidget { yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; } - if (gender == 'Male'|| gender == 'ذكر') { + if (gender == 'Male' || gender == 'ذكر') { yearGenderMap[year]!['M'] = value; } else if (gender == 'Female' || gender == 'أنثى') { yearGenderMap[year]!['F'] = value; @@ -2042,16 +2507,21 @@ class ChartWidget extends StatelessWidget { } }); - // Add a line for the ratio data lineBars.add( LineChartBarData( spots: spots, isCurved: true, - color: Colors.blue, // Set color for the ratio line + // color: bodyColor?? Colors.grey, // Set color for the ratio line + color: uniqueColors[1] ?? Colors.grey, barWidth: 3, isStrokeCapRound: true, - belowBarData: BarAreaData(show: true), + belowBarData: BarAreaData( + show: true, + // color: bodyColor , + // color: (bodyColor ?? Colors.grey).withAlpha(100), + color: (uniqueColors[1] ?? Colors.grey).withAlpha(100), + ), ), ); @@ -2094,25 +2564,41 @@ class ChartWidget extends StatelessWidget { throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); } - List lineBarsData(List filteredData, - Set groupByValues, String groupByKey) { + List lineBarsData( + List filteredData, + Set groupByValues, + String groupByKey, + Map chartBarColors) { List lineBars = []; + print('LineParsedChartBarColors $chartBarColors'); + print('filteredData $filteredData'); print('filteredData222 $groupByValues'); - final List uniqueColors = [ - Color(0xFF6097CD), - Color(0xFFD086A7), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // 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]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } for (String group in groupByValues) { - groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + if (chartBarColors.isNotEmpty && chartBarColors.containsKey(group)) { + // Use predefined color from chartBarColors + groupColorMap[group] = chartBarColors[group]!; + } else { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + } colorIndex++; } @@ -2163,7 +2649,8 @@ class ChartWidget extends StatelessWidget { } else if (value is double) { yValue = value; } else if (value is String) { - yValue = double.tryParse(value) ?? 0.0; // Handle invalid strings safely + yValue = + double.tryParse(value) ?? 0.0; // Handle invalid strings safely } else { throw Exception("Unexpected value type: ${value.runtimeType}"); } @@ -2192,6 +2679,7 @@ class ChartWidget extends StatelessWidget { // Add a line for this group lineBars.add( LineChartBarData( + show: true, spots: spots, isCurved: true, color: groupColorMap[group], @@ -2208,7 +2696,10 @@ class ChartWidget extends StatelessWidget { // Helper functions for chart styles LineTouchData lineTouchData1() { + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; return LineTouchData( + touchSpotThreshold: 10, touchTooltipData: LineTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background tooltipRoundedRadius: 8, @@ -2216,6 +2707,7 @@ class ChartWidget extends StatelessWidget { fitInsideVertically: true, tooltipPadding: EdgeInsets.all(8), tooltipMargin: 16, // Adds margin to prevent clipping + getTooltipColor: (spot) => Colors.black, getTooltipItems: (List lineBarsSpot) { return lineBarsSpot.map((lineBarSpot) { Color lineColor = lineBarSpot.bar is LineChartBarData @@ -2231,7 +2723,13 @@ class ChartWidget extends StatelessWidget { color: lineColor), // Set circle color to match bar ), TextSpan( - text: formatNumber(lineBarSpot.y), + // text: formatNumber(lineBarSpot.y), + text: (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null) + ? formatNumberConversion( + lineBarSpot.y, chartConversion, number_format) + : formatNumber(lineBarSpot.y), // text: ' ${lineBarSpot.y}', // Keep the value white style: const TextStyle(color: Colors.white), ), @@ -2244,6 +2742,78 @@ class ChartWidget extends StatelessWidget { ); } + // + // LineTouchData lineTouchData1() { + // var chartConversion = chartData['chart_type_json']['conversion']; + // var number_format = chartData['chart_type_json']['number_format']; + // return LineTouchData( + // enabled: true, + // handleBuiltInTouches: true, + // touchCallback: + // (FlTouchEvent event, LineTouchResponse? response) { + // if (response == null || response.lineBarSpots == null) { + // return; + // } + // // if (event is FlTapUpEvent) { + // // final spotIndex = response.lineBarSpots!.first.spotIndex; + // // setState(() { + // // if (showingTooltipOnSpots.contains(spotIndex)) { + // // showingTooltipOnSpots.remove(spotIndex); + // // } else { + // // showingTooltipOnSpots.add(spotIndex); + // // } + // // }); + // // } + // }, + // mouseCursorResolver: + // (FlTouchEvent event, LineTouchResponse? response) { + // if (response == null || response.lineBarSpots == null) { + // return SystemMouseCursors.basic; + // } + // return SystemMouseCursors.click; + // }, + // touchSpotThreshold: 5, + // touchTooltipData: LineTouchTooltipData( + // tooltipRoundedRadius: 0, + // getTooltipColor: (spot) => Colors.transparent, + // fitInsideHorizontally: true, // Ensure it fits within the screen + // fitInsideVertically: true, + // tooltipPadding: EdgeInsets.all(8), + // tooltipMargin: 16, + // getTooltipItems: (List touchedSpots) { + // return touchedSpots.map((LineBarSpot touchedSpot) { + // return LineTooltipItem( + // // formatNumber(touchedSpot.y), + // (chartConversion != null && + // chartConversion.isNotEmpty && + // number_format != null) + // ? formatNumberConversion(touchedSpot.y, chartConversion, number_format) + // : formatNumber(touchedSpot.y), + // TextStyle( + // color: Colors.black87, + // fontWeight: FontWeight.w400, + // fontSize: 15, + // ), + // ); + // }).toList(); + // }, + // ), + // getTouchedSpotIndicator: ( + // _, + // indicators, + // ) { + // return indicators + // .map((int index) => const TouchedSpotIndicatorData( + // FlLine(color: Colors.transparent), + // FlDotData(show: true), + // )) + // .toList(); + // }, + // distanceCalculator: (Offset touchPoint, Offset spotPixelCoordinates) => + // (touchPoint - spotPixelCoordinates).distance, + // ); + // } + FlGridData gridData() { return FlGridData( show: false, @@ -2270,80 +2840,127 @@ class ChartWidget extends StatelessWidget { reservedSize: 40, getTitlesWidget: (value, meta) { // Format values as millions (M) - String formattedValue; + // String formattedValue; + String? formattedValue; print('LineTrend2val1 - $value '); - if (value % 10 == 0) { - // Check if the value is in the millions or thousands range - if (kDebugMode) { - print('LineTrend2valKmode - $value '); - } - if (value >= 1000000000000) { + // if (value % 10 == 0) { + // // Check if the value is in the millions or thousands range + // if (kDebugMode) { + // print('LineTrend2valKmode - $value '); + // } + // if (value >= 1000000000000) { + // formattedValue = + // '${(value / 1000000000000).toStringAsFixed(0)}T'; + // } else if (value >= 1000000000) { + // formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; + // } else 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('LINETREND_POP- $formattedValue'); + // // if (int.parse(formattedValue.replaceAll(RegExp(r'[^0-9]'), '')) % 10 != 0) { + // // return const SizedBox.shrink(); + // // } + // + // + // // print('formattedValueLine - $formattedValue'); + // return Text( + // formattedValue, + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 10, + // )); + // + // } + + // Ensure value is a multiple of 10k, 2M, 100B, or 1T before processing + if (value >= 1000000000000) { + // Trillions (T) + if (value % 1000000000000 == 0) { + // Only multiples of 1T formattedValue = '${(value / 1000000000000).toStringAsFixed(0)}T'; - } else if (value >= 1000000000) { + } + } else if (value >= 1000000000) { + // Billions (B) + if (value % 100000000000 == 0) { + // Only multiples of 100B formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; - } else if (value >= 1000000) { + } + } else if (value >= 1000000) { + // Millions (M) + if (value % 2000000 == 0) { + // Only multiples of 2M formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; - } else if (value >= 1000) { + } + } else if (value >= 10000) { + // Thousands (k) + if (value % 10000 == 0) { + // Only multiples of 10k 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, textAlign: TextAlign.center, - style: TextStyle( - fontSize: 10, - - )); } - return const SizedBox.shrink(); - // return Text( - // 'LINETRENTD2', - // style: TextStyle(color: Colors.black, fontSize: 12), - // ); + print( + 'Value: $value | Formatted: ${formattedValue ?? "Hidden"}'); // Debugging + + if (formattedValue != null) { + return Text( + formattedValue, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 10), + ); + } + + return const SizedBox.shrink(); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( - showTitles: true, - interval: null, - // interval: 10, // Ensure each year is shown only once - getTitlesWidget: (value, meta) { - // print('BtmTiles :-$value'); - // print('BtmTilesmeta :-$meta'); - // if (xValues.contains(value)) - double closestValue = findClosest(value, xValues); - if ((closestValue - value).abs() < 0.15) { - // print("Bottomtiles"); - // print(value); - return Padding( - padding: const EdgeInsets.only(top: 1.1), - child: Transform.rotate( - angle: 0.0, - // angle: -0.5, // Slight rotation to improve readability - child: Text( - value.toInt().toString(), - style: TextStyle(color: Colors.black, fontSize: 12), + showTitles: true, + interval: null, + // interval: 10, // Ensure each year is shown only once + getTitlesWidget: (value, meta) { + // print('BtmTiles :-$value'); + // print('BtmTilesmeta :-$meta'); + // if (xValues.contains(value)) + double closestValue = findClosest(value, xValues); + if ((closestValue - value).abs() < 0.15) { + // print("Bottomtiles"); + // print(value); + return Padding( + padding: const EdgeInsets.only(top: 3.1, left: 20.0), + child: SizedBox( + width: 40, + child: Transform.rotate( + // angle: 0.0, + angle: -1.5, // Slight rotation to improve readability + child: Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ), + ), ), - ), - ); - } else { - return SizedBox.shrink(); // Hide non-relevant labels - } - }, - ), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } + }, + reservedSize: 40), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide top titles @@ -2369,14 +2986,31 @@ class ChartWidget extends StatelessWidget { // End Of line trend chart // Define the list of unique colors - final List uniqueColors = [ - Color(0xFF648CBA), - Color(0xFF90B0D5), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // final List uniqueColors1 = [ + // Color(0xFF648CBA), + // Color(0xFF90B0D5), + // Color(0xFF98BCE5), + // Color(0xFFA7B5C5), + // Color(0xFFBED3EC), + // Color(0xFFD4E3F4), + // ]; + // + // final List uniqueColors2 = [ + // Color(0xFFD9BB99), + // Color(0xFF87766E), + // Color(0xFFC6A885), + // Color(0xFFA28565), + // Color(0xFFBFB29B), + // Color(0xFFE8D8BB), + // ]; + // + // final List uniqueColors = [ + // Color(0xFF376C79), + // Color(0xFF578D9C), + // Color(0xFF7DAFBC), + // Color(0xFF86C7D9), + // Color(0xFF989898), + // ]; Color _getColorForGroup(int index) { // Use the index to get a color from the uniqueColors list @@ -2404,7 +3038,6 @@ class ChartWidget extends StatelessWidget { value = double.tryParse(rawValue.toString()) ?? 0.0; } - print('flStackedBar3.1'); // print('fl_groupValue $groupValue'); @@ -2427,7 +3060,6 @@ 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 @@ -2466,7 +3098,8 @@ class ChartWidget extends StatelessWidget { width: 20, // width: barWidth, borderRadius: BorderRadius.zero, - color: Colors.black // This will act as a container for stacked items + color: + Colors.black // This will act as a container for stacked items ), ], ); @@ -2586,13 +3219,14 @@ class ChartWidget extends StatelessWidget { // double value = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; double value = (entry['ObsValue']['Value'] as num?)?.toDouble() ?? 0; - if (value > maxY) { maxY = value; } } - return maxY * 1.1; // Add 10% buffer for better visualization + // Apply different buffers based on maxY value + return maxY < 500 ? maxY * 3.5 : maxY * 1.5; + // return maxY * 1.5; // Add 10% buffer for better visualization } List _buildHorizontalRotateBarGroups( @@ -2618,19 +3252,29 @@ class ChartWidget extends StatelessWidget { double value = double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; print('multi_bar1.1'); - final List uniqueColorsForTwo = [ - Color(0xFF648CBA), - Color(0xFF90B0D5), - ]; + // final List uniqueColorsForTwo = [ + // Color(0xFF648CBA), + // Color(0xFF90B0D5), + // ]; + + // Use bodyColor instead of hardcoded colors + final barColor; if (chartData['dataset'] == 'general_education' || chartData['dataset'] == 'higher_education' || chartData['dataset'] == 'air_transport' || chartData['dataset'] == 'labour_force' || chartData['dataset'] == 'gdp' || + chartData['dataset'] == 'hotels' || + chartData['dataset'] == 'hotel_guests' || + chartData['dataset'] == 'health_services' || chartData['dataset'] == 'clinics') { barColor = uniqueColorsForTwo[colorIndex % uniqueColorsForTwo.length]; colorIndex++; + } else if (chartData['dataset'] == 'cropsk') { + barColor = + uniqueColorsForThree[colorIndex % uniqueColorsForThree.length]; + colorIndex++; } else { barColor = uniqueColors[colorIndex % uniqueColors.length]; colorIndex++; @@ -2663,6 +3307,73 @@ class ChartWidget extends StatelessWidget { return barGroups; } + List _buildHorizontalRotateBarGroupsBarColors( + dynamic chartData, + Set groupByValues, + Map chartBarColors) { + List responseData = chartData['response']; + List barGroups = []; + // Define two shades for alternating colors + List uniqueColorsForTwo = [ + chartBarColors['default'] ?? Colors.blue, // Original color + (chartBarColors['default'] ?? Colors.blue) + .withAlpha(180) // Slightly modified shade + ]; + + 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 + 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) { + double value = + double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; + + // Get gender-based color + String gender = data['ObsKey']['GENDER'] ?? ""; + + List barGroups = []; + + // Determine color dynamically + Color barColor; + if (chartData['dataset'] == 'health_services') { + barColor = uniqueColorsForTwo[ + colorIndex % 2]; // Alternate between two colors + colorIndex++; // Update index for next bar + } else { + barColor = chartBarColors[gender] ?? + Colors.grey; // Default gender-based color + } + + print('GEG- $gender'); + + return BarChartRodData( + toY: value, // Use the parsed value + color: barColor, // Gender-based dynamic color + width: 20, + ); + }).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 @@ -2671,6 +3382,47 @@ class ChartWidget extends StatelessWidget { } } +Widget _buildChartLegend( + Map> groupedCrops, + Map parsedChartBarColors, + List uniqueColors, +) { + // Extract unique crop types + Set uniqueCropTypes = + groupedCrops.values.expand((list) => list).toSet(); + + return Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 6, + children: uniqueCropTypes.map((cropType) { + // Color cropColor = parsedChartBarColors[cropType] ?? Colors.grey; // Fetch color for each crop type + int index = uniqueCropTypes + .toList() + .indexOf(cropType); // Get index for cycling colors + Color cropColor = parsedChartBarColors.isNotEmpty && + parsedChartBarColors.containsKey(cropType) + ? parsedChartBarColors[cropType]! + : uniqueColors[index % uniqueColors.length]; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar( + radius: 6, + backgroundColor: cropColor, + ), + const SizedBox(width: 4), + Text( + cropType, + style: const TextStyle(fontSize: 12), + ), + ], + ); + }).toList(), + ); +} + class BarValuePainter extends CustomPainter { BarValuePainter(this.barGroups, this.constraints); final List barGroups; diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index bbdc7403..e70c4ee6 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -75,7 +75,9 @@ class _ProfileScreenState extends ConsumerState { 'India', 'Canada' ]; + final List preferred_language = ['English', 'Arabic']; String? _selectedCountry; + String? _selectedLanguage; bool isChecked = false; bool showError = false; final _picker = ImagePicker(); @@ -86,6 +88,7 @@ class _ProfileScreenState extends ConsumerState { late Future userDetails; bool _isHoveringDate = false; bool _isHoveringDropdown = false; + bool _isHoveringPreferredLang = false; @override void initState() { @@ -187,10 +190,10 @@ class _ProfileScreenState extends ConsumerState { _isLoading = true; // Start loading }); final XFile? pickedFile = - await _picker.pickImage(source: ImageSource.gallery); + await _picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { final String fileExtension = - pickedFile.path.split('.').last.toLowerCase(); + pickedFile.path.split('.').last.toLowerCase(); if (fileExtension == 'jpg' || fileExtension == 'jpeg' || fileExtension == 'png' || @@ -227,7 +230,8 @@ class _ProfileScreenState extends ConsumerState { builder: (context, child) { return Theme( data: Theme.of(context).copyWith( - dialogBackgroundColor: Colors.white, // Background color of the date picker + dialogBackgroundColor: + Colors.white, // Background color of the date picker textButtonTheme: TextButtonThemeData( style: ButtonStyle( shape: WidgetStateProperty.all(RoundedRectangleBorder( @@ -237,8 +241,10 @@ class _ProfileScreenState extends ConsumerState { ), datePickerTheme: DatePickerThemeData( confirmButtonStyle: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Color(0xFFAA8E83)), // OK button background color - foregroundColor: WidgetStateProperty.all(Colors.white), // OK button text color + backgroundColor: WidgetStateProperty.all( + Color(0xFFAA8E83)), // OK button background color + foregroundColor: WidgetStateProperty.all( + Colors.white), // OK button text color // minimumSize: WidgetStateProperty.all(Size(150, 50)), minimumSize: WidgetStateProperty.all(Size(100, 40)), shape: WidgetStateProperty.all(RoundedRectangleBorder( @@ -247,12 +253,14 @@ class _ProfileScreenState extends ConsumerState { ), cancelButtonStyle: ButtonStyle( // backgroundColor: WidgetStateProperty.all(Colors.white), // Cancel button background color - foregroundColor: WidgetStateProperty.all(Color(0xFFAA8E83)), // Cancel button text color + foregroundColor: WidgetStateProperty.all( + Color(0xFFAA8E83)), // Cancel button text color // minimumSize: WidgetStateProperty.all(Size(150, 50)), minimumSize: WidgetStateProperty.all(Size(100, 40)), shape: WidgetStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), // Rounded corners - side: const BorderSide(color: Color(0xFFAA8E83), width: 2), // Outline color + side: const BorderSide( + color: Color(0xFFAA8E83), width: 2), // Outline color )), ), ), @@ -262,7 +270,6 @@ class _ProfileScreenState extends ConsumerState { }, ); - if (pickedDate != null && pickedDate != _selectedDate) { setState(() { _selectedDate = pickedDate; @@ -282,7 +289,8 @@ class _ProfileScreenState extends ConsumerState { // Check if the selected date is in the future if (selectedDate.isAfter(today)) { - return context.translate('Date of birth cannot be in the future','لا يمكن أن يكون تاريخ الميلاد في المستقبل'); + return context.translate('Date of birth cannot be in the future', + 'لا يمكن أن يكون تاريخ الميلاد في المستقبل'); } return null; } @@ -315,6 +323,7 @@ class _ProfileScreenState extends ConsumerState { // String email = 'surendarsuri30@gmail.com'; String dateOfBirth = _dateController.text; // in DD/MM/YYYY format String countryRegion = _selectedCountry ?? ''; + String preferredLang = _selectedLanguage == 'English' ? 'en' : 'ar'; bool termsAccepted = isChecked; // Parse the date from dd/MM/yyyy format and convert to yyyy-MM-dd @@ -324,13 +333,14 @@ class _ProfileScreenState extends ConsumerState { // Create a multipart request final uri = - Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); + Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); final request = http.MultipartRequest('PATCH', uri); // Add other fields request.fields['full_name'] = fullName; request.fields['dob'] = formattedDob; request.fields['country_region'] = countryRegion; + request.fields['language'] = preferredLang; request.fields['is_profile_completed'] = 'True'; // Add the file, if available @@ -371,6 +381,7 @@ class _ProfileScreenState extends ConsumerState { _fullNameController.clear(); _dateController.clear(); _selectedCountry = null; + _selectedLanguage = null; isChecked = false; // Reset profile image @@ -399,14 +410,15 @@ class _ProfileScreenState extends ConsumerState { 'Logout', 'تسجيل الخروج', ), - style: TextStyle(color: Color(0xFF898C81),fontWeight: FontWeight.w700), + style: + TextStyle(color: Color(0xFF898C81), fontWeight: FontWeight.w700), ), content: Text( context.translate( 'Are you sure you want to logout?', 'هل أنت متأكد أنك تريد تسجيل الخروج؟', ), - style: TextStyle(color:Color(0xFF898C81)), + style: TextStyle(color: Color(0xFF898C81)), ), actions: [ TextButton( @@ -414,7 +426,7 @@ class _ProfileScreenState extends ConsumerState { style: TextButton.styleFrom( // backgroundColor: // Color(0xFFAA8E83), // Set background color - side: BorderSide(color: Color(0xFFAA8E83), width: 2), + side: BorderSide(color: Color(0xFFAA8E83), width: 2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), // Set text color ), @@ -432,12 +444,10 @@ class _ProfileScreenState extends ConsumerState { TextButton( onPressed: () => logout(context), // Exit the app style: TextButton.styleFrom( - backgroundColor: - Color(0xFFAA8E83), // Set background color + backgroundColor: Color(0xFFAA8E83), // Set background color // foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10.0), // Set text color + borderRadius: BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -560,7 +570,7 @@ class _ProfileScreenState extends ConsumerState { backgroundImage: _profileImage != null ? FileImage(_profileImage!) : AssetImage("assets/edit_profile/profile.png") - as ImageProvider, + as ImageProvider, child: Align( alignment: Alignment.bottomRight, child: GestureDetector( @@ -584,7 +594,7 @@ class _ProfileScreenState extends ConsumerState { child: CircularProgressIndicator( strokeWidth: 2, valueColor: - AlwaysStoppedAnimation(Colors.grey), + AlwaysStoppedAnimation(Colors.grey), ), ), ], @@ -673,19 +683,18 @@ class _ProfileScreenState extends ConsumerState { r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); if (!nameRegex.hasMatch(value)) { - return AppLocalizations.of(context)!.invalid_characters; + return AppLocalizations.of(context)! + .invalid_characters; } return null; }, controller: _fullNameController, focusNode: _focusNodes[2], decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) + color: MyTheme.topicColor(IndicatorTopic.economy) .shade400, width: 1), // Enabled border ), @@ -695,7 +704,8 @@ class _ProfileScreenState extends ConsumerState { fontSize: 14, ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + borderSide: BorderSide( + color: Color(0xFF7296BE), ), borderRadius: BorderRadius.circular(8), ), @@ -723,17 +733,17 @@ class _ProfileScreenState extends ConsumerState { controller: _dateController, focusNode: _focusNodes[3], decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, width: 1), // Enabled border ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + borderSide: BorderSide( + color: Color(0xFF7296BE), ), borderRadius: BorderRadius.circular(8), ), @@ -748,10 +758,11 @@ class _ProfileScreenState extends ConsumerState { width: 45, padding: EdgeInsets.only(right: 1), alignment: - Alignment.center, // Center the icon vertically + Alignment.center, // Center the icon vertically child: Icon( Icons.keyboard_arrow_down_sharp, - color: _isHoveringDate ? Colors.black : Colors.grey, + color: + _isHoveringDate ? Colors.black : Colors.grey, ), ), ), @@ -773,35 +784,37 @@ class _ProfileScreenState extends ConsumerState { ), SizedBox(height: 10), MouseRegion( - onEnter: (_) => setState(() => _isHoveringDropdown = true), - onExit: (_) => setState(() => _isHoveringDropdown = false), + onEnter: (_) => + setState(() => _isHoveringDropdown = true), + onExit: (_) => + setState(() => _isHoveringDropdown = false), child: DropdownButtonFormField( value: _selectedCountry, decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), - borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, - width: 1), // Enabled border - ), - border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border ), - borderRadius: BorderRadius.circular(8), - ), - labelText: context.translate('Select',' اختيار') - ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), + borderRadius: BorderRadius.circular(8), + ), + labelText: context.translate('Select', ' اختيار')), icon: Icon(Icons.keyboard_arrow_down_sharp, - color: - _isHoveringDropdown ? Colors.black : Colors.grey), + color: _isHoveringDropdown + ? Colors.black + : Colors.grey), items: _countries .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) + value: item, + child: Text(item), + )) .toList(), onChanged: (String? newValue) { setState(() { @@ -811,6 +824,59 @@ class _ProfileScreenState extends ConsumerState { validator: _validateDropdown, ), ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.preferredLang, + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => + setState(() => _isHoveringPreferredLang = true), + onExit: (_) => + setState(() => _isHoveringPreferredLang = false), + child: DropdownButtonFormField( + value: _selectedLanguage, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), + borderRadius: BorderRadius.circular(8), + ), + labelText: context.translate('Select', ' اختيار')), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: _isHoveringPreferredLang + ? Colors.black + : Colors.grey), + items: preferred_language + .map((item) => DropdownMenuItem( + value: item, + child: Text(item), + )) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedLanguage = newValue; + }); + }, + validator: _validateDropdown, + ), + ), SizedBox(height: 20), Row( children: [ @@ -823,53 +889,48 @@ class _ProfileScreenState extends ConsumerState { }); }, side: BorderSide( - color: - showError ? Color(0xFFb22222) : Color(0xFF7296BE), + color: showError + ? Color(0xFFb22222) + : Color(0xFF7296BE), width: 1, ), ), Expanded( child: Padding( padding: const EdgeInsets.only( - top: 14.0,), + top: 14.0, + ), child: Text.rich( TextSpan( - text: AppLocalizations.of( - context)! - .agree, + text: AppLocalizations.of(context)!.agree, // text: 'I agree to ', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 14, color: Color( - 0xFF898C81,), // Change to your desired color + 0xFF898C81, + ), // Change to your desired color ), children: [ TextSpan( - recognizer:TapGestureRecognizer() - ..onTap = () => context.push('/terms&conditions'), - text: AppLocalizations.of( - context)! + recognizer: TapGestureRecognizer() + ..onTap = () => + context.push('/terms&conditions'), + text: AppLocalizations.of(context)! .terms_conditions, // text: 'Terms & Conditions', style: TextStyle( fontSize: 14, - fontWeight: - FontWeight.bold, - color: - Color(0xFF985400), + fontWeight: FontWeight.bold, + color: Color(0xFF985400), // Makes the text bold // decoration: // TextDecoration.underline, - decorationColor: - Color(0xFF985400), - decorationThickness: - 1), + decorationColor: Color(0xFF985400), + decorationThickness: 1), ), TextSpan( - text: AppLocalizations.of( - context)! - .t_and, + text: AppLocalizations.of(context)!.t_and, style: TextStyle( color: Color( 0xFF898C81), // Change to your desired color @@ -877,31 +938,26 @@ class _ProfileScreenState extends ConsumerState { ), TextSpan( recognizer: TapGestureRecognizer() - ..onTap = () => context.push('/privacy_policy'), - text: AppLocalizations.of( - context)! + ..onTap = + () => context.push('/privacy_policy'), + text: AppLocalizations.of(context)! .privacy_policy, style: TextStyle( - fontWeight: - FontWeight.bold, + fontWeight: FontWeight.bold, fontSize: 14, fontFamily: 'Roboto', - color: - Color(0xFF985400), + color: Color(0xFF985400), // decoration: // TextDecoration.underline, - decorationColor: - Color(0xFF648CBA), - decorationThickness: - 1), + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), ), TextSpan( - text: AppLocalizations.of( - context)! + text: AppLocalizations.of(context)! .conditions, style: TextStyle( fontWeight: FontWeight.w500, - fontSize: 14, + fontSize: 14, color: Color( 0xFF898C81), // Change to your desired color ), @@ -918,13 +974,14 @@ class _ProfileScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.only(left: 10.0, right: profileLocale - ?.languageCode == - 'ar' - ? 15 - : 0.0,), + padding: EdgeInsets.only( + left: 10.0, + right: profileLocale?.languageCode == 'ar' + ? 15 + : 0.0, + ), child: Text( - context.translate('Required', 'مطلوب'), + context.translate('Required', 'مطلوب'), style: TextStyle( color: Color(0xFFb22222), fontSize: 12, @@ -960,7 +1017,7 @@ class _ProfileScreenState extends ConsumerState { ElevatedButton( onPressed: () { setState(() { - hasValidated = true; + hasValidated = true; }); if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { @@ -969,7 +1026,7 @@ class _ProfileScreenState extends ConsumerState { } else { setState(() { showError = - !isChecked; // Show error if the checkbox is not checked + !isChecked; // Show error if the checkbox is not checked }); } }, @@ -982,7 +1039,7 @@ class _ProfileScreenState extends ConsumerState { ), child: Row( mainAxisAlignment: - MainAxisAlignment.center, // Center the content + MainAxisAlignment.center, // Center the content children: [ Text( AppLocalizations.of(context)!.save, @@ -1041,10 +1098,10 @@ class ConfirmationDialog extends StatelessWidget { TextSpan( text: AppLocalizations.of(context)!.name, style: - TextStyle(fontWeight: FontWeight.w700), // Bold for "name" + TextStyle(fontWeight: FontWeight.w700), // Bold for "name" ), TextSpan( - text: context.translate(' or ','أو'), + text: context.translate(' or ', 'أو'), ), TextSpan( text: AppLocalizations.of(context)!.dob, @@ -1111,4 +1168,4 @@ class ConfirmationDialog extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/components/constant/constant.dart b/lib/presentation/components/constant/constant.dart index 4b65a141..55e3e055 100644 --- a/lib/presentation/components/constant/constant.dart +++ b/lib/presentation/components/constant/constant.dart @@ -7,12 +7,14 @@ const Color environmentboldColor = Color(0xFF90B0D5); const TextStyle subtitleStyle = TextStyle( fontFamily: 'Roboto', // Font family set to Roboto fontWeight: FontWeight.w400, // Regular weight (w400) - fontSize: 11, +// fontSize: 11, + fontSize: 11 * 1.2, // Increase by 20% color: Color(0xFF8E8E8E) // Font size 11 ); const TextStyle robotoRegular11 = TextStyle( fontFamily: 'Roboto', // Font family set to Roboto fontWeight: FontWeight.w400, // Regular weight (w400) - fontSize: 11, + fontSize: 11 * 1.1, color: Color(0xFF000000) // Font size 11 + ); diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 7ee43de6..557f0ae3 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:external_repos/external_repos.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -27,7 +28,6 @@ import 'package:pocketbase/pocketbase.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:uae_stat/config/api_config.dart'; - import '../../components/indicators/locale_provider.dart'; import '../../components/my_toggle.dart'; @@ -42,7 +42,8 @@ class LoginRoute extends HookConsumerWidget { dynamic userData; String? role; int? loginCount; - + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; + // final FirebaseAuth _auth = FirebaseAuth.instance; static final formKey = GlobalKey(); Future profileStatus(String userId) async { @@ -97,6 +98,89 @@ class LoginRoute extends HookConsumerWidget { await prefs.setString('login_count', count.toString()); } + // Future getDeviceToken() async { + // String? token = await FirebaseMessaging.instance.getToken(); + // print("FCM Token: $token"); + // } + + Future getDeviceToken(BuildContext context, String userId) async { + try { + // await _firebaseMessaging.requestPermission(); + print("FCM Token Entry"); + String? token = await FirebaseMessaging.instance.getToken(); + print("FCM Token: $token"); + + if (token != null) { + await updateDeviceToken(userId, token); + + // Automatically copy to clipboard + // Clipboard.setData(ClipboardData(text: token)); + + // Show token in Alert Dialog + // if (context.mounted) { + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // title: Text("FCM Token"), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // SelectableText(token), // Allows manual selection + // SizedBox(height: 10), + // Text( + // "Token copied to clipboard ✅", + // style: TextStyle(color: Colors.green), + // ), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text("OK"), + // ), + // ], + // ); + // }, + // ); + // } + } + } catch (e) { + print("Error getting FCM Token: $e"); + } + } + + Future updateDeviceToken(String userId, String token) async { + final url = "${pb.baseUrl}/api/collections/users/records/$userId"; + + try { + // Authenticate admin (Persist session globally instead) + await pb.admins.authWithPassword( + 'pb@venbainfotech.com', + 'pb@venbainfotech.com', + ); + + final response = await http.patch( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', // Add this + 'Authorization': pb.authStore.token, // Correct way to send auth token + }, + body: jsonEncode({ + "device_token": token, + }), + ); + + if (response.statusCode == 200) { + print("✅ Device token updated successfully!"); + } else { + print("❌ Failed to update device token: ${response.body}"); + } + } catch (e) { + print("🚨 Error updating device token: $e"); + } + } + // Future authenticateAndStoreData() async { // try { // // Perform authentication @@ -244,15 +328,13 @@ class LoginRoute extends HookConsumerWidget { 'تم إرسال بريد إلكتروني إلى $email يتضمن المزيد من التفاصيل.', ), ); - }, style: TextButton.styleFrom( padding: EdgeInsets.zero, // maximumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.compact, - overlayColor: Colors.white - ), + overlayColor: Colors.white), child: Text( context.translate( 'Forgot Password?', @@ -273,7 +355,7 @@ class LoginRoute extends HookConsumerWidget { width: double.infinity, child: ElevatedButton( onPressed: () async { - hasValidated.value=true; + hasValidated.value = true; final isValid = formKey.currentState!.validate(); if (!isValid) return; final session = await context.loaderWithErrorDialog( @@ -367,6 +449,7 @@ class LoginRoute extends HookConsumerWidget { } await saveUserId(userId, loginCount); + await getDeviceToken(context, userId); } try { final bool isProfileComplete = await profileStatus(userId); @@ -451,14 +534,14 @@ class LoginRoute extends HookConsumerWidget { 'Login', 'تسجيل الدخول', ), - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'Roboto', - ), - fontSize: locale?.languageCode == 'ar' ? 12: 18, - fontWeight: FontWeight.w500, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'Roboto', ), + fontSize: locale?.languageCode == 'ar' ? 12 : 18, + fontWeight: FontWeight.w500, + ), ), 6.horizontalSpace, const Icon( @@ -478,16 +561,15 @@ class LoginRoute extends HookConsumerWidget { 'Email', 'بريد إلكتروني', ), - validator: (value) { final emailRegex = r'^[a-zA-Z0-9àèìòùÀÈÌÒÙéàç~!#$%^&*()_+=-{}|;,.?<>]+@[a-zA-Z0-9àèìòùÀÈÌÒÙéàç~!#$%^&*()_+=-{}|;,.?<>]+\.[a-zA-Z]{2,}$'; if (value == null || value.isEmpty) { return context.translate('Required', 'مطلوب'); - } - else if (!RegExp(emailRegex).hasMatch(value)) { + } else if (!RegExp(emailRegex).hasMatch(value)) { return AppLocalizations.of(context)!.invalid_email; - } return null; + } + return null; }, imgPath: MiscIconAssetPath.person, controller: emailCtl, @@ -662,48 +744,47 @@ class LoginRoute extends HookConsumerWidget { final listViewHorizontalPadding = screenWidth < 700 ? 30 : 30 + (screenWidth - 700) / 2; final scaffoldBody = SingleChildScrollView( - child: Column( - children:[ - Padding( - padding: EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - 3.verticalSpace, - Align( - alignment: AlignmentDirectional.topEnd, - child: MyToggle( - isOn: locale?.languageCode == 'en', - knobTextWhenOn: 'ع', - knobTextWhenOff: 'EN', - pathColorWhenOn: Colors.grey.shade300, - pathColorWhenOff: Colors.grey.shade300, - onTap: () { - final formState = formKey.currentState; - ref.read(localeProvider.notifier).toggleLocale(); - Future.delayed(Duration(milliseconds: 100), () { - if (hasValidated.value && formState?.validate() == false) { - formState?.validate(); - } - }); - }, - ), - ), - 34.verticalSpace, - helloAndPleaseLoginTexts, - 40.verticalSpace, - form, - 15.verticalSpace, - dontHaveAnAccountRegisterBtn, - // continueAsGuestBtn, - // Spacer(), - 15.verticalSpace, - fcscBanner, - ], + child: Column(children: [ + Padding( + padding: EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 3.verticalSpace, + Align( + alignment: AlignmentDirectional.topEnd, + child: MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + final formState = formKey.currentState; + ref.read(localeProvider.notifier).toggleLocale(); + Future.delayed(Duration(milliseconds: 100), () { + if (hasValidated.value && + formState?.validate() == false) { + formState?.validate(); + } + }); + }, + ), ), - ) - ] - ), + 34.verticalSpace, + helloAndPleaseLoginTexts, + 40.verticalSpace, + form, + 15.verticalSpace, + dontHaveAnAccountRegisterBtn, + // continueAsGuestBtn, + // Spacer(), + 15.verticalSpace, + fcscBanner, + ], + ), + ) + ]), ); // final bgScaffold = Scaffold( // backgroundColor: Colors.white, @@ -783,4 +864,4 @@ class LoginRoute extends HookConsumerWidget { // ), // ); // } -} \ No newline at end of file +} 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 61b1714e..8e142b0a 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 @@ -1456,7 +1456,8 @@ class InfoCard extends StatelessWidget { child: Text( title, style: const TextStyle( - fontSize: 11, + // fontSize: 11, + fontSize: 11 * 1.1, fontWeight: FontWeight.w400, // color: Colors.black, fontFamily: 'Roboto', @@ -1478,7 +1479,8 @@ class InfoCard extends StatelessWidget { subtitle, textAlign: TextAlign.center, style: const TextStyle( - fontSize: 11, + // fontSize: 11, + fontSize: 11 * 1.1, fontFamily: 'Roboto', fontWeight: FontWeight.w400, color: Color(0xFF8E8E8E), 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 fd90a183..776231d3 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -204,7 +204,7 @@ class _EditProfileState extends State { if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = - '$apiUrl/$collectionId/$recordId/$avatarFilename'; + '$apiUrl/api/files/$collectionId/$recordId/$avatarFilename'; } else { _avatarUrl = ''; // Reset to default or empty } @@ -308,7 +308,8 @@ class _EditProfileState extends State { // Check if the selected date is in the future if (selectedDate.isAfter(today)) { - return context.translate('Date of birth cannot be in the future','لا يمكن أن يكون تاريخ الميلاد في المستقبل'); + return context.translate('Date of birth cannot be in the future', + 'لا يمكن أن يكون تاريخ الميلاد في المستقبل'); } return null; } @@ -691,7 +692,8 @@ class _EditProfileState extends State { enabled: false, validator: (value) { if (value == null || value.isEmpty) { - return context.translate('Required', 'مطلوب'); + return context.translate( + 'Required', 'مطلوب'); } //RegExp(r"^[a-zA-Z\s]+$"); @@ -699,7 +701,8 @@ class _EditProfileState extends State { r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$", ); if (!nameRegex.hasMatch(value)) { - return AppLocalizations.of(context)!.invalid_characters; + return AppLocalizations.of(context)! + .invalid_characters; } return null; }, @@ -787,18 +790,19 @@ class _EditProfileState extends State { ), value: _selectedCountry, decoration: InputDecoration( - enabledBorder:OutlineInputBorder( + enabledBorder: OutlineInputBorder( borderRadius: - BorderRadius.circular(10), + BorderRadius.circular(10), borderSide: BorderSide( color: MyTheme.topicColor( - IndicatorTopic.economy) + IndicatorTopic.economy) .shade400, width: 1), // Enabled border ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), - ), + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), borderRadius: BorderRadius.circular(8), ), @@ -845,8 +849,9 @@ class _EditProfileState extends State { color: showError ? Color(0xFFb22222) : MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, + IndicatorTopic + .economy) + .shade400, width: 1.5, ), ), @@ -880,8 +885,11 @@ class _EditProfileState extends State { TextDecoration .underline, ), - recognizer:TapGestureRecognizer() - ..onTap = () => context.push('/terms&conditions'), + recognizer: + TapGestureRecognizer() + ..onTap = () => + context.push( + '/terms&conditions'), ), TextSpan( text: AppLocalizations @@ -907,9 +915,11 @@ class _EditProfileState extends State { TextDecoration .underline, ), - recognizer: TapGestureRecognizer() - ..onTap = () => context.push('/privacy_policy'), - + recognizer: + TapGestureRecognizer() + ..onTap = () => + context.push( + '/privacy_policy'), ), TextSpan( text: AppLocalizations @@ -1151,4 +1161,4 @@ class ConfirmationDialog extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart index f8d5d5cf..1b10dbc7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart @@ -342,14 +342,18 @@ class _ManageUserRouterState extends State { padding: EdgeInsets.only(left: 12.0), child: TextField( decoration: InputDecoration( - hintText: "Search", - hintStyle: TextStyle(color: Color(0xFF898C81)), - prefixIconConstraints: BoxConstraints(maxWidth: 42, maxHeight: 42), + hintText: AppLocalizations.of(context)!.search, + hintStyle: TextStyle(color: Color(0xFFAA8E83)), + // hintStyle: TextStyle(color: Color(0xFFAA8E83)), + prefixIconConstraints: + BoxConstraints(maxWidth: 42, maxHeight: 42), prefixIcon: Container( padding: EdgeInsets.only(right: 5), child: SvgPicture.asset( MiscIconAssetPath.Search, semanticsLabel: 'Search', + colorFilter: ColorFilter.mode( + Color(0xFFAA8E83), BlendMode.srcIn), ), ), @@ -359,7 +363,7 @@ class _ManageUserRouterState extends State { // ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( - vertical: 8.0, horizontal: 18.0), + vertical: 0.5, horizontal: 18.0), ), onChanged: filterUsers, ), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart index c1b276cb..889200a7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart @@ -1,22 +1,29 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:uae_stat/config/api_config.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import '../../custom_drawer_routes.dart'; +import 'package:http/http.dart' as http; -class NotificationPage extends StatefulWidget { +class NotificationPage extends ConsumerStatefulWidget { @override _NotificationPageState createState() => _NotificationPageState(); } -class _NotificationPageState extends State { +class _NotificationPageState extends ConsumerState { // Example notification data final _pb = PocketBase(apiUrl); List> notifications = []; // Current selected tab index int selectedTabIndex = 0; + late final Locale locale; // Tab categories final List tabs = [ @@ -28,6 +35,13 @@ class _NotificationPageState extends State { "Favourites" ]; + @override + void initState() { + super.initState(); + locale = ref.read(localeProvider) ?? const Locale('en'); + fetchNotifications(locale?.languageCode ?? 'en'); + } + String formatDate(String dateString) { try { DateTime dateTime = @@ -39,36 +53,61 @@ class _NotificationPageState extends State { } } - Future fetchNotifications() async { + Future fetchNotifications(locale) async { + final baseUrl = apiUrl + '/api/getNotification'; try { - await _pb.admins - .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final response = await http.get(Uri.parse('$baseUrl?language=$locale')); - final result = await _pb.collection('notification').getFullList(); + if (response.statusCode == 200) { + final Map jsonResponse = + jsonDecode(response.body); // Decode JSON - setState(() { - notifications = result.map((record) => record.toJson()).toList(); - }); + if (jsonResponse['message'] == 'Success') { + setState(() { + notifications = List>.from( + jsonResponse['data']); // Assign decoded data + }); + } else { + throw Exception('Failed to load data'); + } + } else { + throw Exception( + 'Failed to load data with status code ${response.statusCode}'); + } } catch (e) { - print('Error fetching notifications: $e'); + print('Error fetching data: $e'); } } - @override - void initState() { - super.initState(); - fetchNotifications(); - } + // Future fetchNotifications() async {/api/getNotification?language=ar + // try { + // await _pb.admins + // .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + // + // final result = await _pb.collection('notification').getFullList(); + // + // setState(() { + // notifications = result.map((record) => record.toJson()).toList(); + // }); + // } catch (e) { + // print('Error fetching notifications: $e'); + // } + // } @override Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchNotifications(localeCode); + }); // Filter notifications based on the selected tab List> filteredNotifications = selectedTabIndex == 0 ? notifications - : notifications - .where((notification) => - notification["category"] == tabs[selectedTabIndex]) - .toList(); + : notifications.where((notification) { + print( + "Comparing: '${notification["category"]}' with '${tabs[selectedTabIndex]}'"); + return notification["category"] == tabs[selectedTabIndex]; + }).toList(); return PopScope( canPop: false, @@ -77,7 +116,12 @@ class _NotificationPageState extends State { context.go('/myhomepage'); }, child: BaseScaffold( - title: Text('Notification'), + title: Text( + context.translate( + 'Notification', + 'إشعار', + ), + ), body: Column( children: [ TabBarHeader( @@ -90,53 +134,41 @@ class _NotificationPageState extends State { }, ), Expanded( - child: selectedTabIndex == 1 + child: filteredNotifications.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset( - 'assets/backgrounds/Notification/Update_notific.png', - height: 200, - ), + Icon(Icons.info_outline, + size: 100, color: Colors.grey[400]), SizedBox(height: 16), Text( - filteredNotifications.isNotEmpty - ? filteredNotifications[0]['title'] - : 'No updates available.', + 'No notifications available.', style: TextStyle(fontSize: 16, color: Colors.grey), - textAlign: TextAlign.center, ), ], ), ) - : filteredNotifications.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.info_outline, - size: 100, color: Colors.grey[400]), - SizedBox(height: 16), - Text( - 'No notifications available.', - style: - TextStyle(fontSize: 16, color: Colors.grey), - ), - ], + : ListView.builder( + itemCount: filteredNotifications.length, + itemBuilder: (context, index) { + final notification = filteredNotifications[index]; + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Color(0xFFDEDEDE), + width: 1), // Bottom border only + ), ), - ) - : ListView.builder( - itemCount: filteredNotifications.length, - itemBuilder: (context, index) { - final notification = filteredNotifications[index]; - return NotificationTile( + child: NotificationTile( title: notification['title'] ?? 'No Title', date: formatDate(notification['created'] ?? ''), category: notification['category'] ?? 'Unknown', - ); - }, - ), + message: notification['message'] ?? ''), + ); + }, + ), ), ], ), @@ -205,22 +237,43 @@ class TabBarHeader extends StatelessWidget { class NotificationTile extends StatelessWidget { final String title; + final String message; final String date; final String category; const NotificationTile({ required this.title, + required this.message, required this.date, required this.category, }); @override Widget build(BuildContext context) { - return ListTile( - leading: Icon(Icons.circle, - size: 12, color: category == "Social" ? Colors.red : Colors.grey), - title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text(date), + return GestureDetector( + onTap: () { + context.go('/notification_details', extra: { + 'title': title, + 'message': message, + 'date': date, + 'category': category, + }); + }, + child: ListTile( + // leading: Icon(Icons.circle, + // size: 12, color: category == "Social" ? Colors.red : Colors.grey), + leading: Icon(Icons.circle, size: 12, color: Colors.red), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text( + date, + style: const TextStyle( + color: Color(0xFF8E8E8E), + fontSize: 12, + fontWeight: FontWeight.w400), + ), + trailing: const Icon(Icons.arrow_forward_ios, + size: 16, color: Color(0xFF898C81)), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart new file mode 100644 index 00000000..83f5c21c --- /dev/null +++ b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; + +class NotificationDetails extends ConsumerWidget { + final String title; + final String message; + final String date; + final String category; + + const NotificationDetails({ + super.key, + required this.title, + required this.message, + required this.date, + required this.category, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + context.go('/notification'); + }, + child: BaseScaffold( + title: Text( + context.translate( + 'Notification', + 'إشعار', + ), + ), + showBackButton: true, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Color(0xFF414042)), + ), + const SizedBox(height: 20), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + category == 'App updates' + ? 'assets/backgrounds/Notification/App-Update.png' + : 'assets/backgrounds/Notification/Update_notific.png', + height: 200, + ), + ], + ), + ), + const SizedBox(height: 20), + Text( + message.isNotEmpty + ? message + : "No additional details available.", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF414042)), + ), + const SizedBox(height: 10), + Text( + "Date: $date", + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Color(0xFF8E8E8E)), + ), + ], + ), + ), + )); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0c4642aa..ec843094 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,8 @@ import Foundation import connectivity_plus import file_selector_macos import firebase_core -import flutter_secure_storage_macos +import firebase_messaging +import flutter_secure_storage_darwin import package_info_plus import path_provider_foundation import share_plus @@ -21,12 +22,13 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) 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 0b5097c2..db2b6d12 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: + version: "80.0.0" + _flutterfire_internals: dependency: transitive - description: dart - source: sdk - version: "0.3.3" + description: + name: _flutterfire_internals + sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" + url: "https://pub.dev" + source: hosted + version: "1.3.53" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.3.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: "1d460d14e3c2ae36dc2b32cef847c4479198cf87704f63c3c3c8150ee50c3916" url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.12.0" ansicolor: dependency: transitive description: @@ -58,18 +61,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" boxy: dependency: "direct main" description: @@ -82,50 +85,50 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -138,18 +141,18 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.9.4" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -178,10 +181,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -194,18 +197,18 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412" + sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.3" connectivity_plus_platform_interface: dependency: transitive description: @@ -266,42 +269,42 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9" + sha256: "021897cce2b6c783b2521543e362e7fe1a2eaab17bf80514d8de37f99942ed9e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759" + sha256: e4235b9d8cef59afe621eba086d245205c8a0a6c70cd470be7cb17494d6df32d url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" + sha256: "6dcee8a017181941c51a110da7e267c1d104dc74bec8862eeb8c85b5c8759a9e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+7.3.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.0.1" dartz: dependency: transitive description: @@ -330,10 +333,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" equatable: dependency: transitive description: @@ -369,26 +372,26 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" fast_immutable_collections: dependency: "direct main" description: name: fast_immutable_collections - sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c + sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" url: "https://pub.dev" source: hosted - version: "10.2.4" + version: "11.0.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -425,18 +428,18 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.3+4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b url: "https://pub.dev" source: hosted - version: "2.32.0" + version: "3.12.1" firebase_core_platform_interface: dependency: transitive description: @@ -449,10 +452,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41 + sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 url: "https://pub.dev" source: hosted - version: "2.21.0" + version: "2.21.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281" + url: "https://pub.dev" + source: hosted + version: "15.2.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94 + url: "https://pub.dev" + source: hosted + version: "4.6.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4 + url: "https://pub.dev" + source: hosted + version: "3.10.4" fixnum: dependency: transitive description: @@ -486,10 +513,10 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "0.21.2" flutter_launcher_icons: dependency: "direct dev" description: @@ -523,10 +550,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.27" flutter_rating_bar: dependency: "direct main" description: @@ -547,50 +574,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: f7eceb0bc6f4fd0441e29d43cab9ac2a1c5ffd7ea7b64075136b718c46954874 url: "https://pub.dev" source: hosted - version: "9.2.4" + version: "10.0.0-beta.4" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: f226f2a572bed96bc6542198ebaec227150786e34311d455a7e2d3d06d951845 + url: "https://pub.dev" + source: hosted + version: "0.1.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + sha256: "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c" url: "https://pub.dev" source: hosted - version: "1.2.2" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" + version: "2.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.0.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21 url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.0.0" flutter_staggered_grid_view: dependency: "direct main" description: @@ -621,18 +648,18 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "8971efe7e59585e9149052e33718d84bca51e806f063d1467622b3dcb2878b6c" + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" url: "https://pub.dev" source: hosted - version: "8.2.11" + version: "8.2.12" freezed: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -669,10 +696,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550 + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 url: "https://pub.dev" source: hosted - version: "14.7.1" + version: "14.8.1" graphs: dependency: transitive description: @@ -725,10 +752,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" iconify_design: dependency: "direct main" description: @@ -757,10 +784,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c + sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" url: "https://pub.dev" source: hosted - version: "0.8.12+20" + version: "0.8.12+21" image_picker_for_web: dependency: transitive description: @@ -821,10 +848,10 @@ packages: dependency: "direct dev" description: name: injectable_generator - sha256: af403d76c7b18b4217335e0075e950cd0579fd7f8d7bd47ee7c85ada31680ba1 + sha256: b04673a4c88b3a848c0c77bf58b8309f9b9e064d9fe1df5450c8ee1675eaea1a url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" intl: dependency: "direct main" description: @@ -845,10 +872,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -861,10 +888,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.9.4" jwt_decoder: dependency: "direct main" description: @@ -877,18 +904,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -901,10 +928,10 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: transitive description: @@ -913,14 +940,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" mailer: dependency: "direct main" description: @@ -941,10 +960,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_charts: dependency: "direct main" description: @@ -965,10 +984,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1005,26 +1024,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254 + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.2.0" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1085,10 +1104,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: @@ -1141,10 +1160,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" recase: dependency: transitive description: @@ -1173,10 +1192,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.5.9" riverpod_annotation: dependency: "direct main" description: @@ -1189,18 +1208,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99" + sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" rxdart: dependency: transitive description: @@ -1244,18 +1263,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" + sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.6" shared_preferences_foundation: dependency: transitive description: @@ -1284,10 +1303,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -1300,18 +1319,18 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -1328,10 +1347,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: @@ -1344,10 +1363,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -1360,10 +1379,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -1376,10 +1395,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1392,42 +1411,42 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" syncfusion_flutter_charts: dependency: "direct main" description: name: syncfusion_flutter_charts - sha256: "672c17a7f4cb3020c06bb6a28d2d033623007b8e2a19704ad34b412e9bdd2dab" + sha256: c53e8616368c1a88397b7b180d0ded7d9e960564cdba01509cc5fa2d503ed727 url: "https://pub.dev" source: hosted - version: "28.2.4+1" + version: "28.2.9" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "3c1876b0a245de23de3b17a19e3106fed57d88f4fd2c8dc9bc1976705b1c31d5" + sha256: "5a1a76eef118e511443b299925d4a802eb8259388d6a580e0ad6413d1a70f987" url: "https://pub.dev" source: hosted - version: "28.2.4" + version: "28.2.9" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" the_validator: dependency: "direct main" description: @@ -1448,10 +1467,10 @@ packages: dependency: "direct main" description: name: tutorial_coach_mark - sha256: df450c88d4c812bc221afd3ff948da3dc0f44c0b4fa5dbc046d6d86f2cfc9e71 + sha256: "2c77c0b00bbe7d5b8a6d31cb9e03d44bf77dfe7ba6514cc2b546886d024a9945" url: "https://pub.dev" source: hosted - version: "1.2.12" + version: "1.2.13" typed_data: dependency: transitive description: @@ -1520,10 +1539,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: @@ -1576,26 +1595,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + sha256: "48941c8b05732f9582116b1c01850b74dbee1d8520cd7e34ad4609d6df666845" url: "https://pub.dev" source: hosted - version: "2.9.2" + version: "2.9.3" video_player_android: dependency: transitive description: name: video_player_android - sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" url: "https://pub.dev" source: hosted - version: "2.7.16" + version: "2.7.17" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "8a4e73a3faf2b13512978a43cf1cdda66feeeb900a0527f1fbfd7b19cf3458d3" + sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" url: "https://pub.dev" source: hosted - version: "2.6.7" + version: "2.7.0" video_player_platform_interface: dependency: transitive description: @@ -1616,10 +1635,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -1632,10 +1651,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -1664,10 +1683,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f + sha256: "512c26ccc5b8a571fd5d13ec994b7509f142ff6faf85835e243dde3538fdc713" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.3.2" webview_flutter_platform_interface: dependency: transitive description: @@ -1680,18 +1699,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: c49a98510080378b1525132f407a92c3dcd3b7145bef04fb8137724aadcf1cf0 url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.4" win32: dependency: transitive description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.11.0" xdg_directories: dependency: transitive description: @@ -1717,5 +1736,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.2 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4248f8ff..d8c68214 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" -version: 1.0.27+28 +version: 1.0.30+31 environment: sdk: ">=3.2.3 <4.0.0" @@ -9,9 +9,9 @@ environment: dependencies: flutter: sdk: flutter - fast_immutable_collections: ^10.1.0 + fast_immutable_collections: ^11.0.3 # flutter_cache_manager: ^3.3.1 - flutter_hooks: ^0.20.4 + flutter_hooks: ^0.21.2 flutter_riverpod: ^2.4.0 get_it: ^8.0.1 go_router: ^14.1.3 @@ -33,10 +33,10 @@ dependencies: csv: ^6.0.0 excel: ^4.0.2 intl: ^0.19.0 - firebase_core: ^2.27.0 + firebase_core: ^3.12.1 the_validator: ^2.0.0 pocketbase: ^0.18.1 - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^10.0.0-beta.4 jwt_decoder: ^2.0.1 fluttertoast: ^8.2.11 webview_flutter: ^4.5.0 @@ -66,6 +66,7 @@ dependencies: package_info_plus: ^8.2.0 flutter_svg: ^2.0.17 boxy: ^2.2.1 + firebase_messaging: ^15.2.4 dependency_overrides: fading_edge_scrollview: ^4.1.1 @@ -139,6 +140,7 @@ flutter: - assets/icons/misc/group.png - assets/icons/misc/search.png - assets/icons/bookmark/ + - assets/FCSC-GIF.gif fonts: - family: Segoe