From 3dd2aa40fe80a64fdde5dc15e28dcd98c5cf2657 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Mon, 10 Feb 2025 18:22:40 +0530 Subject: [PATCH] bookmark added --- android/app/build.gradle | 4 +- .../uae_numbers/bookmarksSelectedUae.png | Bin 0 -> 266 bytes lib/config/toast_util.dart | 29 + .../icons/uae_numbers_asset_path.dart | 3 +- .../Screens/charts/screens/chart_screen.dart | 593 ++++++++++++++---- .../drawer_routes/Drawer Items/bookmark.dart | 386 ++++++++---- pubspec.yaml | 5 +- 7 files changed, 788 insertions(+), 232 deletions(-) create mode 100644 assets/icons/uae_numbers/bookmarksSelectedUae.png create mode 100644 lib/config/toast_util.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 6938a51f..1359f22c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,12 +15,12 @@ if (localPropertiesFile.exists()) { def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "11" + flutterVersionCode = "12" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.10" + flutterVersionName = "1.0.11" } def keystorePropertiesFile = rootProject.file("key.properties") diff --git a/assets/icons/uae_numbers/bookmarksSelectedUae.png b/assets/icons/uae_numbers/bookmarksSelectedUae.png new file mode 100644 index 0000000000000000000000000000000000000000..f4657f7daeabe3615ea4eda18dd6af3e4e8e5b02 GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRn!3HE-lJ=GZDb50q$YKTtZeb8+WSBKa0w~B> z9OUlAut69hLg#j1k{2Oh3zADA<@SJEd<7P>yOBVm} zs}uGY3QU?}BcrD&B%Eu`v{C3_tm(;|2i?uKv*vs+3@UHjePAi8NM-Wgy~RN@U7OaK z>Mjd>Q_rs|_k6M{dsN)t#Efv=kLhR6PM&gO9=lrn0_p0#Q5&AfaYVi { + final _pb = PocketBase('https://pb.venbait.in'); List> selectedFiltersStorage = []; final GlobalKey _scaffoldKey = GlobalKey(); final ApiService apiService = ApiService(); @@ -60,7 +64,9 @@ class _ChartScreen1State extends ConsumerState { late TutorialCoachMark tutorialCoachMark; final GlobalKey chartKey = GlobalKey(); final GlobalKey bookMarkKey = GlobalKey(); - final GlobalKey cardKey= GlobalKey(); + final GlobalKey cardKey = GlobalKey(); + bool isBookmarked = false; // Track bookmark state + String? bookmarkId; // Stores the ID of the bookmark record in PocketBase @override void initState() { @@ -68,6 +74,7 @@ class _ChartScreen1State extends ConsumerState { final String bgColor = widget.bgColor; print(' bgColor $bgColor'); // fetchChartData(widget.dataSets); + checkIfBookmarked(); final locale = ref.read(localeProvider); fetchChartData(widget.dataSets, locale?.languageCode ?? 'en').then((_) { if (_tabsData.isNotEmpty) { @@ -81,13 +88,317 @@ class _ChartScreen1State extends ConsumerState { }); } + /// Check if the item is already bookmarked + Future checkIfBookmarked() async { + final prefs = await SharedPreferences.getInstance(); + final userID = prefs.getString('userId'); + try { + final adminAuth = await _pb.admins.authWithPassword( + 'pb@venbainfotech.com', + 'pb@venbainfotech.com', + ); + + final adminToken = adminAuth.token; + + final result = await _pb.collection('bookmark').getList( + filter: "user_id = '${userID}' && dataset = '${widget.dataSets}'", + headers: { + 'Authorization': adminToken, // Pass the admin token + }, + ); + + if (result.items.isNotEmpty) { + setState(() { + isBookmarked = true; + bookmarkId = result.items.first.id; + }); + } + } catch (e) { + print("Error checking bookmark: $e"); + } + } + + /// Add bookmark to PocketBase + Future addBookmark() async { + final prefs = await SharedPreferences.getInstance(); + final userID = prefs.getString('userId'); + try { + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + final response = await _pb.collection('bookmark').create(body: { + 'user_id': userID, + 'dataset': widget.dataSets, + 'bgColor': widget.bgColor, + 'mainTopic': widget.mainTopic, + 'title': widget.title, + 'keyParam': widget.keyParam, + }, headers: { + 'Authorization': adminToken + }); + + setState(() { + isBookmarked = true; + bookmarkId = response.id; + }); + + // ToastUtil.showSuccessToast("Added to Bookmark."); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Added to Bookmark.', + style: TextStyle( + color: Color(0xFF2F692C), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ), + ); + + print("Bookmark added: ${response.id}"); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Failed to Add', + style: TextStyle( + color: Color(0xFF544C4C), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFEB5F24), + duration: Duration(seconds: 2), + ), + ); + // ToastUtil.showDeleteToast("Unable to add to bookmarks. Please try again"); + print("Error adding bookmark: $e"); + } + } + + /// Remove bookmark from PocketBase + Future removeBookmark() async { + try { + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + if (bookmarkId != null) { + await _pb.collection('bookmark').delete(bookmarkId!, headers: { + 'Authorization': adminToken, + }); + + setState(() { + isBookmarked = false; + bookmarkId = null; + }); + // ToastUtil.showSuccessToast("Removed from Bookmark."); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Removed from Bookmark.', + style: TextStyle( + color: Color(0xFF2F692C), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ), + ); + print("Bookmark removed"); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Failed to Remove', + style: TextStyle( + color: Color(0xFF544C4C), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFEB5F24), + duration: Duration(seconds: 2), + ), + ); + // ToastUtil.showDeleteToast( + // "Unable to remove from bookmarks. Please try again"); + print("Error removing bookmark: $e"); + } + } + + /// Show confirmation dialog before removing bookmark + void showRemoveBookmarkDialog() { + double myheight = MediaQuery.of(context).size.height; + showDialog( + context: context, + barrierDismissible: false, // User must tap button to dismiss dialog + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), // Rounded corners + ), + contentPadding: EdgeInsets.zero, + content: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: myheight / 30, + ), + Text( + context.translate( + 'Are you sure you want to remove this bookmark?', + 'هل أنت متأكد أنك تريد إزالة هذه الإشارة المرجعية؟'), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Color(0xFF898C81), + ), + ) + ], + ), + ), + ], + ), + actions: [ + SizedBox(height: 20), + 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 + ), + ), + child: Text( + context.translate('No', 'لا'), + style: TextStyle( + color: Color(0xFFAA8E83), + fontSize: 16, + ), + ), + ), + ), + SizedBox( + width: 100, // Set the desired width + child: TextButton( + onPressed: () { + Navigator.pop(context); + removeBookmark(); // Close dialog + }, + style: TextButton.styleFrom( + backgroundColor: Color(0xFFAA8E83), // Set background color + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), // Set text color + ), + ), + child: Text( + context.translate('Yes', 'نعم'), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + ], + ), + ); + } + + void showAddBookmarkDialog() { + double myheight = MediaQuery.of(context).size.height; + showDialog( + context: context, + barrierDismissible: false, // User must tap button to dismiss dialog + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), // Rounded corners + ), + contentPadding: EdgeInsets.zero, + content: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: myheight / 30, + ), + Text( + context.translate( + 'Do you want to add this to bookmarks?', + 'هل تريد إضافة هذا إلى الإشارات المرجعية؟', + ), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Color(0xFF898C81), + ), + ) + ], + ), + ), + ], + ), + actions: [ + SizedBox(height: 20), + 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 + ), + ), + child: Text( + context.translate('No', 'لا'), + style: TextStyle( + color: Color(0xFFAA8E83), + fontSize: 16, + ), + ), + ), + ), + SizedBox( + width: 100, // Set the desired width + child: TextButton( + onPressed: () { + Navigator.pop(context); + addBookmark(); // Close dialog + }, + style: TextButton.styleFrom( + backgroundColor: Color(0xFFAA8E83), // Set background color + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), // Set text color + ), + ), + child: Text( + context.translate('Yes', 'نعم'), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + ], + ), + ); + } + void handleSkip() { tutorialCoachMark.skip(); debugPrint('Skip clicked'); - ref.read(chartsTourProvider.notifier).state = true; - ref.read(previousChartsTourProvider.notifier).state = true; - ref.read(homeTourProvider.notifier).state = true; - ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read(previousHomeTourProvider.notifier).state = true; ref.read(scaffoldTourProvider.notifier).state = true; ref.read(previousScaffoldTourProvider.notifier).state = true; context.go('/myhomepage'); @@ -101,7 +412,6 @@ class _ChartScreen1State extends ConsumerState { tutorialCoachMark = TutorialCoachMark( paddingFocus: 0, useSafeArea: true, - pulseEnable: false, hideSkip: true, targets: marriageTargets, @@ -109,26 +419,22 @@ class _ChartScreen1State extends ConsumerState { context.go('/myhomepage'); debugPrint('Marriage Tutorial Finished'); }, - )..show(context: context); } - void _startTutorialAfterRender() { final chartTour = ref.watch(chartsTourProvider); final previousChartTour = ref.watch(previousChartsTourProvider); debugPrint('render problem'); if (cardKey.currentContext != null && !chartTour) { _kpiTutorialCoachMark(); - } else if(cardKey.currentContext != null && !previousChartTour) { + } else if (cardKey.currentContext != null && !previousChartTour) { _previousKpiTutorial(); - } - else if (!chartTour || !previousChartTour){ + } else if (!chartTour || !previousChartTour) { Future.delayed(Duration(milliseconds: 100), _startTutorialAfterRender); - }else{ + } else { return; } - } void _previousKpiTutorial() { @@ -143,14 +449,13 @@ class _ChartScreen1State extends ConsumerState { context.go('/myhomepage'); debugPrint('Previous Marriage Tutorial Finished'); }, - )..show(context: context); - } - void _initmarriageTargets(){ - final double screenWidth= MediaQuery.of(context).size.width; - final double screenHeight= MediaQuery.of(context).size.height; - marriageTargets=[ + + void _initmarriageTargets() { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + marriageTargets = [ TargetFocus( identify: 'cardKey', keyTarget: cardKey, @@ -161,14 +466,14 @@ class _ChartScreen1State extends ConsumerState { createTargetContent( text: AppLocalizations.of(context)!.kpiCards, alignment: ContentAlign.top, - gap:55, + gap: 55, space: 0, ), TargetContent( align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.3, + height: MediaQuery.of(context).size.height * 0.3, child: Stack( children: [ Positioned( @@ -179,15 +484,15 @@ class _ChartScreen1State extends ConsumerState { children: [ Align( alignment: Alignment.bottomRight, - child:Padding( - padding: EdgeInsets.only(right:10), - child:Text( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( '3/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), - ) , + ), ), ), const SizedBox(height: 5), @@ -217,14 +522,21 @@ class _ChartScreen1State extends ConsumerState { Container( decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), + border: Border.all( + color: Colors.white, width: 2.0), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { - ref.read(chartsTourProvider.notifier).state=true; - ref.read(previousHomeTourProvider.notifier).state=false; + ref + .read(chartsTourProvider.notifier) + .state = true; + ref + .read( + previousHomeTourProvider.notifier) + .state = false; tutorialCoachMark.finish(); }, ), @@ -234,11 +546,13 @@ class _ChartScreen1State extends ConsumerState { decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF7DAFBC), - border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1.5), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -256,25 +570,25 @@ class _ChartScreen1State extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(bottom: 0,top : 0,), + padding: EdgeInsets.only( + bottom: 0, + top: 0, + ), align: ContentAlign.top, child: SizedBox( width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - bottom: 0, - child: Image.asset( - 'assets/app_tour/leftUp.png', - width: 100, - height: 90, - ), - ), - ] - ), - + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ]), ), ), ], @@ -397,7 +711,7 @@ class _ChartScreen1State extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.60, + height: MediaQuery.of(context).size.height * 0.60, child: Stack( children: [ Positioned( @@ -408,8 +722,9 @@ class _ChartScreen1State extends ConsumerState { children: [ Align( alignment: Alignment.bottomRight, - child: Padding(padding: EdgeInsets.only(right: 10), - child: Text( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( '4/7', style: const TextStyle( color: Colors.white, @@ -445,11 +760,13 @@ class _ChartScreen1State extends ConsumerState { Container( decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), + border: Border.all( + color: Colors.white, width: 2.0), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.previous(); }, @@ -460,14 +777,20 @@ class _ChartScreen1State extends ConsumerState { decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF7DAFBC), - border: Border.all(color: Color(0xFF7DAFBC), width: 1), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { - ref.read(chartsTourProvider.notifier).state=true; - ref.read(scaffoldTourProvider.notifier).state=false; + ref + .read(chartsTourProvider.notifier) + .state = true; + ref + .read(scaffoldTourProvider.notifier) + .state = false; tutorialCoachMark.finish(); }, ), @@ -484,10 +807,10 @@ class _ChartScreen1State extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(left:0, top:0), + padding: EdgeInsets.only(left: 0, top: 0), align: ContentAlign.right, child: Container( - width: screenWidth/4, + width: screenWidth / 4, height: screenHeight, child: Stack( children: [ @@ -513,14 +836,13 @@ class _ChartScreen1State extends ConsumerState { ), ], ), - ]; } - void _initpreviousTargets(){ - final double screenWidth= MediaQuery.of(context).size.width; - final double screenHeight= MediaQuery.of(context).size.height; - previousMarriageTargets=[ + void _initpreviousTargets() { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + previousMarriageTargets = [ TargetFocus( identify: 'BookMarkKey', keyTarget: bookMarkKey, @@ -538,7 +860,7 @@ class _ChartScreen1State extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.60, + height: MediaQuery.of(context).size.height * 0.60, child: Stack( children: [ Positioned( @@ -549,7 +871,7 @@ class _ChartScreen1State extends ConsumerState { children: [ Align( alignment: Alignment.bottomRight, - child:Padding( + child: Padding( padding: const EdgeInsets.only(right: 10.0), child: Text( '4/7', @@ -587,11 +909,13 @@ class _ChartScreen1State extends ConsumerState { Container( decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), + border: Border.all( + color: Colors.white, width: 2.0), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -602,14 +926,20 @@ class _ChartScreen1State extends ConsumerState { decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF7DAFBC), - border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1.5), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { - ref.read(chartsTourProvider.notifier).state=true; - ref.read(scaffoldTourProvider.notifier).state=false; + ref + .read(chartsTourProvider.notifier) + .state = true; + ref + .read(scaffoldTourProvider.notifier) + .state = false; tutorialCoachMark.finish(); }, ), @@ -626,10 +956,10 @@ class _ChartScreen1State extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(left:0, top: 10), + padding: EdgeInsets.only(left: 0, top: 10), align: ContentAlign.right, child: Container( - width: screenWidth/4, + width: screenWidth / 4, height: screenHeight, child: Stack( children: [ @@ -666,13 +996,13 @@ class _ChartScreen1State extends ConsumerState { text: AppLocalizations.of(context)!.kpiCards, space: 0, alignment: ContentAlign.top, - gap:50, + gap: 50, ), TargetContent( align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.30, + height: MediaQuery.of(context).size.height * 0.30, child: Stack( children: [ Positioned( @@ -681,9 +1011,10 @@ class _ChartScreen1State extends ConsumerState { right: 16, child: Column( children: [ - Align(alignment: Alignment.bottomRight, - child:Padding( - padding: const EdgeInsets.only(right:10.0), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right: 10.0), child: Text( '3/7', style: const TextStyle( @@ -691,7 +1022,8 @@ class _ChartScreen1State extends ConsumerState { fontSize: 14, ), ), - ),), + ), + ), const SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -719,14 +1051,22 @@ class _ChartScreen1State extends ConsumerState { Container( decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), + border: Border.all( + color: Colors.white, width: 2.0), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { - ref.read(previousChartsTourProvider.notifier).state=true; - ref.read(previousHomeTourProvider.notifier).state=false; + ref + .read(previousChartsTourProvider + .notifier) + .state = true; + ref + .read( + previousHomeTourProvider.notifier) + .state = false; tutorialCoachMark.finish(); }, ), @@ -736,11 +1076,13 @@ class _ChartScreen1State extends ConsumerState { decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF7DAFBC), - border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + border: Border.all( + color: Color(0xFF7DAFBC), width: 1.5), ), child: IconButton( iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.previous(); }, @@ -758,31 +1100,29 @@ class _ChartScreen1State extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(bottom: 0,top : 0,), + padding: EdgeInsets.only( + bottom: 0, + top: 0, + ), align: ContentAlign.top, child: SizedBox( width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - bottom: 0, - child: Image.asset( - 'assets/app_tour/leftUp.png', - width: 100, - height: 90, - ), - ), - ] - ), - + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ]), ), ), ], ), - - ]; } @@ -1295,8 +1635,7 @@ class _ChartScreen1State extends ConsumerState { }); selectedFiltersStorage.clear(); Navigator.pop(context); - }, - + }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF92722A), shape: RoundedRectangleBorder( @@ -1305,7 +1644,8 @@ class _ChartScreen1State extends ConsumerState { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 0.0, vertical: 14.2), // Added padding + horizontal: 0.0, + vertical: 14.2), // Added padding child: Text( context.translate('Clear', 'واضح'), style: TextStyle( @@ -1343,7 +1683,8 @@ class _ChartScreen1State extends ConsumerState { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 0.0, vertical: 16.0), // Added padding + horizontal: 0.0, + vertical: 16.0), // Added padding child: FittedBox( fit: BoxFit .scaleDown, // Ensures text resizes if necessary @@ -1360,7 +1701,7 @@ class _ChartScreen1State extends ConsumerState { maxLines: 1, // Ensures single line ), ), - ), + ), ), ), ], @@ -1602,11 +1943,35 @@ class _ChartScreen1State extends ConsumerState { ), ), SizedBox(width: 5), - Image.asset( - UaeNumbersAssetPath.bookmarksUae, - color: Colors.white, - width: 24, - height: 24, + GestureDetector( + onTap: () { + if (isBookmarked) { + showRemoveBookmarkDialog(); + } else { + showAddBookmarkDialog(); + } + }, + child: Row( + children: [ + Text( + "Bookmark", + style: const TextStyle( + fontSize: 16, + color: Colors.white), + ), + const SizedBox(width: 5), + Image.asset( + isBookmarked + ? UaeNumbersAssetPath + .bookmarksSelectedUae // Filled bookmark image + : UaeNumbersAssetPath + .bookmarksUae, // Default bookmark image + // color: Colors.white, + width: 24, + height: 24, + ), + ], + ), ), ], ), @@ -1695,7 +2060,7 @@ class _ChartScreen1State extends ConsumerState { Padding( padding: const EdgeInsets.all(3.34), child: GridView.builder( - key:cardKey, + key: cardKey, itemCount: cardData.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart index 2cec7155..45f9b0da 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:dio/dio.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; class BookMark extends ConsumerStatefulWidget { @@ -11,93 +16,284 @@ class BookMark extends ConsumerStatefulWidget { } class _BookMarkState extends ConsumerState { - // Example dynamic data - final List> dataList = [ - { - 'title': 'GDP(Constant)', - 'subtitle': '(2022)(AED)', - 'value': '1.62T', - 'valueColor': Color(0xFF80A8CD), - 'isBookmark': true - }, - { - 'title': 'Trade Value', - 'subtitle': '(Jan-Jan 2024)-AED', - 'value': '215.4B', - 'valueColor': Color(0xFF80A8CD), - 'isBookmark': true - }, - { - 'title': 'Electricity Production', - 'subtitle': '(2022) (GWh)', - 'value': '155,438', - 'valueColor': Color(0xFFAA8E83), - 'isBookmark': true - }, - { - 'title': 'Crude Oil Production', - 'subtitle': '(2022) (1000 b/d)', - 'value': '3,064', - 'valueColor': Color(0xFF80A8CD), - 'isBookmark': true - }, - { - 'title': 'Quantitiy of Export Oil', - 'subtitle': '(2022) (1000 b/d)', - 'value': '2717', - 'valueColor': Color(0xFF7DAFBC), - 'isBookmark': true - }, - { - 'title': 'Desalinated Water Production', - 'subtitle': '(2022) (MCM)', - 'value': '1823.8', - 'valueColor': Color(0xFF7DAFBC), - 'isBookmark': true - }, - ]; + final _pb = PocketBase('https://pb.venbait.in'); + List> dataList = []; + bool isLoading = true; @override - Widget build(BuildContext context) { - final filteredList = - dataList.where((item) => item['isBookmark'] == true).toList(); - return PopScope( - canPop: false, // Allow back navigation only if not login screen - onPopInvokedWithResult: (didPop, result) { - if (didPop) return; - context.go('/myhomepage'); // Show exit confirmation dialog - }, - child: BaseScaffold( - title: Text('Bookmark'), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // Two columns - crossAxisSpacing: 10.0, - mainAxisSpacing: 10.0, - mainAxisExtent: 100, - ), - itemCount: filteredList.length, - itemBuilder: (context, index) { - return _buildBox(filteredList[index], context); - }, + void initState() { + super.initState(); + final locale = ref.read(localeProvider); + fetchBookmarks(locale?.languageCode ?? 'en'); + } + + // Function to convert hex color string to int + Color _parseColor(String? colorString) { + if (colorString == null || colorString.isEmpty) { + return const Color(0xFF80A8CD); // Default color + } + + try { + return Color(int.parse(colorString.replaceFirst('#', '0xFF'))); + } catch (e) { + debugPrint("Invalid color format: $colorString"); + return const Color(0xFF80A8CD); // Fallback color + } + } + + Future fetchBookmarks(locale) async { + try { + final prefs = await SharedPreferences.getInstance(); + final userID = prefs.getString('userId') ?? ''; + + if (userID.isEmpty) { + setState(() { + isLoading = false; + }); + return; + } + + final response = await Dio().get( + 'https://pb.venbait.in/api/getUserBookmark', + queryParameters: {'user_id': userID, 'language': locale}, + ); + + if (response.statusCode == 200 && response.data != null) { + if (response.data is Map && + response.data.containsKey("error")) { + // API returned an error message instead of bookmark data + setState(() { + dataList = []; // Clear the list + }); + print("No bookmarks found."); + } else { + List fetchedData = response.data; + setState(() { + dataList = fetchedData.map((item) { + return { + 'main_topic': item['main_topic'] ?? '', + 'title': item['data_set_tile_heading'] ?? '', + 'subtitle': item['sub_topic'] ?? '', + 'value': item['value'] ?? '', + 'value_source': item['value_source'] ?? '', + 'data_set': item['data_set'] ?? '', + 'valueColor': _parseColor(item['color_pattern']), + 'isBookmark': true, + 'id': item['id'], + }; + }).toList(); + print('dataList $dataList'); + }); + } + } + } catch (e) { + setState(() { + dataList = []; // Clear the list + }); + debugPrint("Error fetching bookmarks: $e"); + } finally { + setState(() { + isLoading = false; + }); + } + } + + /// Remove bookmark from PocketBase + Future removeBookmark(bookmarkId) async { + try { + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + + await _pb.collection('bookmark').delete(bookmarkId!, headers: { + 'Authorization': adminToken, + }); + // Show success SnackBar + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Removed from Bookmark.', + style: TextStyle( + color: Color(0xFF2F692C), fontWeight: FontWeight.w600), ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), ), + ); + final locale = ref.read(localeProvider); + fetchBookmarks(locale?.languageCode ?? 'en'); + // ToastUtil.showSuccessToast("Removed from Bookmark."); + + print("Bookmark removed"); + } catch (e) { + SnackBar( + content: Text( + 'Unable to remove from bookmarks. Please try again', + style: + TextStyle(color: Color(0xFFEB5F24), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ); + print("Error removing bookmark: $e"); + } + } + + /// Show confirmation dialog before removing bookmark + void showRemoveBookmarkDialog(id) { + double myheight = MediaQuery.of(context).size.height; + showDialog( + context: context, + barrierDismissible: false, // User must tap button to dismiss dialog + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), // Rounded corners + ), + contentPadding: EdgeInsets.zero, + content: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: myheight / 30, + ), + Text( + context.translate( + 'Are you sure you want to remove this bookmark?', + 'هل أنت متأكد أنك تريد إزالة هذه الإشارة المرجعية؟'), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Color(0xFF898C81), + ), + ) + ], + ), + ), + ], + ), + actions: [ + SizedBox(height: 20), + 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 + ), + ), + child: Text( + context.translate('No', 'لا'), + style: TextStyle( + color: Color(0xFFAA8E83), + fontSize: 16, + ), + ), + ), + ), + SizedBox( + width: 100, // Set the desired width + child: TextButton( + onPressed: () { + Navigator.pop(context); + removeBookmark(id); // Close dialog + }, + style: TextButton.styleFrom( + backgroundColor: Color(0xFFAA8E83), // Set background color + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), // Set text color + ), + ), + child: Text( + context.translate('Yes', 'نعم'), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + ], + ), + ); + } + + String colorToHex(Color color) { + return '0xFF${color.red.toRadixString(16).padLeft(2, '0').toUpperCase()}' + '${color.green.toRadixString(16).padLeft(2, '0').toUpperCase()}' + '${color.blue.toRadixString(16).padLeft(2, '0').toUpperCase()}'; + } + + @override + Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchBookmarks(localeCode); + }); + final filteredList = + dataList.where((item) => item['isBookmark'] == true).toList(); + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + context.go('/myhomepage'); + }, + child: BaseScaffold( + title: Text(context.translate('Bookmark', 'إشارة مرجعية')), + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : filteredList.isEmpty + ? Center( + child: Text(context.translate('No bookmarks found', + 'لم يتم العثور على إشارات مرجعية'))) + : Padding( + padding: const EdgeInsets.all(8.0), + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + mainAxisExtent: 100, + ), + itemCount: filteredList.length, + itemBuilder: (context, index) { + return _buildBox(filteredList[index], context); + }, + ), + ), ), ); } - // Widget for creating a box Widget _buildBox(Map data, BuildContext context) { + // Color borderColor = data['valueColor']; return GestureDetector( onTap: () { + final data_set = data['data_set']; + final Color colorPattern = data['valueColor']; + final encodedMainTopic = data['main_topic']; + final encodedTitle = data['title']; + + String hexColor = colorToHex(colorPattern); // Get hex string + print(hexColor); // Prints: 0xFFAA8E83 + debugPrint("Box clicked: ${data['title']}"); + context.go( + '/chartScreen/$data_set?bgColor=$hexColor&mainTopic=$encodedMainTopic&title=$encodedTitle'); }, child: Container( width: MediaQuery.of(context).size.width * 0.4, height: 100, - padding: EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), @@ -120,54 +316,18 @@ class _BookMarkState extends ConsumerState { alignment: Alignment.center, child: Text( data['title'] ?? '', - style: TextStyle( - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, - // Truncate long text - maxLines: 1, // Limit to 1 line + maxLines: 1, ), ), ), GestureDetector( onTap: () { debugPrint("Icon clicked in: ${data['title']}"); - setState(() { - // Change isOpen to false when icon is clicked - int index = dataList.indexWhere( - (item) => item['title'] == data['title'], - ); - if (index != -1) { - dataList[index]['isBookmark'] = false; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Removed From Bookmark', - style: TextStyle( - color: Color(0xFF2F692C), - fontWeight: FontWeight.w600), - ), - backgroundColor: Color(0xFFD6E9C6), - duration: Duration(seconds: 2), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Failed to Remove', - style: TextStyle( - color: Color(0xFFEB5F24), - fontWeight: FontWeight.w600), - ), - backgroundColor: Color(0xFF544C4C), - duration: Duration(seconds: 2), - ), - ); - } - }); + showRemoveBookmarkDialog(data['id']); }, - child: Icon( + child: const Icon( Icons.bookmarks_rounded, color: Colors.black, size: 20, @@ -176,8 +336,8 @@ class _BookMarkState extends ConsumerState { ], ), Text( - data['subtitle'] ?? '', - style: TextStyle( + data['value_source'] ?? '', + style: const TextStyle( fontWeight: FontWeight.w500, color: Colors.grey), overflow: TextOverflow.ellipsis, maxLines: 1, diff --git a/pubspec.yaml b/pubspec.yaml index 86f35768..b275930a 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.10+11 +version: 1.0.11+12 environment: sdk: ">=3.2.3 <4.0.0" @@ -37,7 +37,7 @@ dependencies: pocketbase: ^0.18.1 flutter_secure_storage: ^9.0.0 jwt_decoder: ^2.0.1 - fluttertoast: ^8.2.5 + fluttertoast: ^8.2.11 webview_flutter: ^4.5.0 video_player: ^2.8.5 flutter_carousel_widget: ^3.1.0 @@ -113,6 +113,7 @@ flutter: - assets/edit_profile/ - assets/splash_screen/ - assets/icons/uae_numbers/bookmarks.png + - assets/icons/uae_numbers/bookmarksSelectedUae.png - assets/icons/uae_numbers/share.png - assets/icons/misc/vector.png - assets/icons/misc/visible_on.png