From 7d251c8ac0a6aa61d613d4bfa9cf4fca5d2cb4d8 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Wed, 22 Jan 2025 10:59:21 +0530 Subject: [PATCH 01/18] feedback bug fix --- lib/l10n/intl_ar.arb | 42 ++++----- lib/l10n/intl_en.arb | 22 ++--- .../auth_verification/registration.dart | 5 +- .../drawer_routes/Drawer Items/feedback.dart | 94 +++++++++---------- .../drawer_routes/custom_drawer_routes.dart | 3 +- 5 files changed, 83 insertions(+), 83 deletions(-) diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index e01858b5..a191b63e 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,24 +1,24 @@ { - "feedback_title": "نموذج الملاحظات", - "feedback_failed_msg": "لم يتم إرسال تعليقاتك. يرجى المحاولة مرة أخرى", + "feedback_title": "التعليقات", + "feedback_failed_msg": "لم يتم إرسال تعليقاتك. يرجى المحاولة مرة أخرى.", "guide_title": "نموذج الملاحظات", "register_title": "التسجيل", "register_details": "من فضلك ادخل بياناتك", "register_name": "اسم المستخدم", - "email_id": "الاسم الكامل", + "email_id": "معرف البريد الإلكتروني", "enter_name": "أدخل الاسم", - "register_email": "أدخل بريدك الإلكتروني", - "register_password": "دخل كلمة المرور", - "register_Confirm_password": "أكيد كلمة المرو", - "agree": " وافق على", - "t_and": "و ", - "conditions": " الخاصة بالمركز الاتحادي للتنافسية والإحصاء", - "terms_conditions": "الشروط والأحكام ", - "privacy_policy":" وسياسة الخصوصية", - "account_confirmation": "ل لديك حساب؟ سجل الدخول ", - "login_title": "سجيل الدخول", + "enter_your_email": "أدخل بريدك الإلكتروني", + "enter_your_password": "أدخل كلمة المرور", + "register_Confirm_password": "تأكيد كلمة المرور", + "agree": "أوافق على الشروط والأحكام وسياسة الخصوصية للمركز الإتحادي للتنافسية والإحصاء", + "t_and": "", + "conditions": "", + "terms_conditions": "", + "privacy_policy":"", + "account_confirmation": "هل لديك حساب؟ سجل الدخول", + "login_title": "تسجيل الدخول", "profile_title": "نموذج الملاحظات", "my_profile": "ملفي الشخصي", @@ -30,8 +30,8 @@ "confirm": "تأكيد", "change_password": "تغيير كلمة المرور", "sure_save_page": " هل أنت متأكد أنك ترغب في حفظ هذه الصفحة؟", - "not_able_to_change": " بمجرد الحفظ، لن تتمكن من تعديل اسمك أو تاريخ ", - "save": "يحفظ", + "not_able_to_change": "بمجرد الحفظ، لن تتمكن من تعديل اسمك أو تاريخ ميلادك.", + "save": "حفظ", "name": "اسم", @@ -55,25 +55,25 @@ "password_match": "كلمات المرور غير متطابقة", "only8charac": "يجب أن تتكون كلمة المرور من 8 أحرف على الأقل", - "manage_user": "دارة المستخدمين", + "manage_user": "إدارة المستخدمين", "user_name": "اسم المستخدم", "reg_date": "تاريخ التسجيل", - "status": "تمت الموافقة", + "status": "الحالة", "denied": "مرفوض", - "pending": "يد الانتظار", + "pending": " قيد الانتظار", "approve_msg": "هل أنت متأكد أنك ترغب في تغيير الحالة إلى \"{newStatus}\"؟", "yes": "نعم", "no": "لا", "no_result_found": "لم يتم العثور على نتائج", "search": "يبحث", - "approved": "موافقة", + "approved": "تمت الموافقة", "create_new_password": "إنشاء كلمة مرور جديدة", - "different_password": "يجب أن تكون كلمة المرور الجديدة مختلفة عن كلمة المرور المستخدمة سابقًا.", + "different_password":"يجب أن تكون كلمة المرور الجديدة مختلفة عن كلمة المرور المستخدمة سابقًا.", "old_password": "كلمة المرور القديمة", "enter_new_password": "أدخل كلمة المرور الجديدة", "confirm_new_password": "تأكيد كلمة المرور الجديدة", - "password_changed_successfully": "م تغيير كلمة المرور بنجاح", + "password_changed_successfully": "تم تغيير كلمة المرور بنجاح", "enter_old_password": "أدخل كلمة المرور القديمة", "your_old_password_incorrect": "كلمة المرور القديمة الخاصة بك غير صحيحة.", "failed_to_update_password": "", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 5b58e7ce..b034d44b 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -8,17 +8,17 @@ "register_details": "Please enter your details", "register_name": "Username", "email_id": "Email ID", - "enter_name": "Enter your User Name", - "register_email": "Enter your email", - "register_password": "Enter your password", - "register_Confirm_password": "Confirm password", - "agree": "I agree to ", - "t_and": "and", - "conditions": " of Fcsc", - "terms_conditions": "terms & conditions", - "privacy_policy":"privacy policy", - "account_confirmation": "Already have an account? Login", - "login_title": "Login", + "enter_name": "Enter your User Name", + "enter_your_email": "Enter your email", + "enter_your_password": "Enter your password", + "register_Confirm_password": "Confirm password", + "agree": "I agree to ", + "t_and": "and", + "conditions": " of Fcsc", + "terms_conditions": "terms & conditions", + "privacy_policy":"privacy policy", + "account_confirmation": "Already have an account? Login", + "login_title": "Login", "profile_title": "Edit Profile", "my_profile": "My Profile", diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index b6ef1088..616c128e 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -475,7 +475,7 @@ class _RegisterScreenState extends State { decoration: InputDecoration( // hintText: 'Enter your email', hintText: _showHints[1] - ? AppLocalizations.of(context)!.register_email + ? AppLocalizations.of(context)!.enter_your_email : null, // _showHints[1] ? 'Enter your email' : null, prefixIcon: Icon( @@ -500,8 +500,7 @@ class _RegisterScreenState extends State { obscureText: _obscurePassword, decoration: InputDecoration( hintText: _showHints[2] - ? AppLocalizations.of(context)! - .register_password + ? AppLocalizations.of(context)!.enter_your_password : null, // _showHints[2] ? 'Enter your password' : null, prefixIcon: Icon( diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart index 363a66d4..83ac097e 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart @@ -28,7 +28,7 @@ class FeedbackForm extends StatefulWidget { class _FeedbackFormState extends State with WidgetsBindingObserver { final _pb = - PocketBase('https://pb.venbait.in'); // Initialize PocketBase client + PocketBase('https://pb.venbait.in'); // Initialize PocketBase client // final _pb = // PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client @@ -46,32 +46,32 @@ class _FeedbackFormState extends State dynamic userId; List> get _emojiOptions => [ - { - "icon": Icons.sentiment_very_dissatisfied, - "label": context.translate("Terrible", "رهيب"), // Translate here - "value": 1, - }, - { - "icon": Icons.sentiment_dissatisfied, - "label": context.translate("Bad", "سيء"), // Translate here - "value": 2, - }, - { - "icon": Icons.sentiment_neutral, - "label": context.translate("Okay", "تمام"), // Translate here - "value": 3, - }, - { - "icon": Icons.sentiment_satisfied, - "label": context.translate("Good", "جيد"), // Translate here - "value": 4, - }, - { - "icon": Icons.sentiment_very_satisfied, - "label": context.translate("Amazing", "مدهش"), // Translate here - "value": 5, - }, - ]; + { + "icon": Icons.sentiment_very_dissatisfied, + "label": context.translate("Terrible", "سئ جدا"), // Translate here + "value": 1, + }, + { + "icon": Icons.sentiment_dissatisfied, + "label": context.translate("Bad", "سيء"), // Translate here + "value": 2, + }, + { + "icon": Icons.sentiment_neutral, + "label": context.translate("Okay", "مقبول"), // Translate here + "value": 3, + }, + { + "icon": Icons.sentiment_satisfied, + "label": context.translate("Good", "جيد"), // Translate here + "value": 4, + }, + { + "icon": Icons.sentiment_very_satisfied, + "label": context.translate("Amazing", "مذهل"), // Translate here + "value": 5, + }, + ]; static const int _characterLimit = 1200; final RegExp _allowedCharacters = RegExp(r'^[a-zA-Z0-9 .,!?-]*$'); @@ -235,8 +235,8 @@ class _FeedbackFormState extends State child: _isFeedbackSubmitted ? _buildThankYouMessage() : _isFeedbackFailed - ? _buildFailureMessage() - : _buildFeedbackForm(), + ? _buildFailureMessage() + : _buildFeedbackForm(), ), ), ); @@ -249,7 +249,7 @@ class _FeedbackFormState extends State Text( context.translate( 'Do you have a suggestion or had any problem? Let us know.', - 'هل لديك اقتراح أو واجهت أي مشكلة؟ اسمحوا لنا أن نعرف.', + 'هل لديك اقتراح أو واجهت أي مشكلة؟ أخبرنا', ), style: const TextStyle( fontSize: 18, @@ -309,7 +309,7 @@ class _FeedbackFormState extends State const SizedBox(height: 30), Text( context.translate('How good did we do in these aspects?', - 'ما مدى جودة ما قمنا به في هذه الجوانب؟'), + 'ما مدى جودة أدائنا في هذه الجوانب؟'), style: const TextStyle( fontSize: 18, fontFamily: 'Roboto', @@ -321,34 +321,34 @@ class _FeedbackFormState extends State _buildRatingRow( context.translate('Ease of use', 'سهولة الاستخدام'), _easeOfUseRating, - (rating) { + (rating) { setState(() { _easeOfUseRating = rating; }); }, ), _buildRatingRow( - context.translate('Quality', 'جودة'), + context.translate('Quality', 'الجودة'), _qualityRating, - (rating) { + (rating) { setState(() { _qualityRating = rating; }); }, ), _buildRatingRow( - context.translate('Design', 'تصميم'), + context.translate('Design', 'التصميم'), _designRating, - (rating) { + (rating) { setState(() { _designRating = rating; }); }, ), _buildRatingRow( - context.translate('Redundant', 'متكرر'), + context.translate('Redundant', 'مكرر'), _redundancyRating, - (rating) { + (rating) { setState(() { _redundancyRating = rating; }); @@ -362,14 +362,14 @@ class _FeedbackFormState extends State maxLength: _characterLimit, decoration: InputDecoration( hintText: context.translate( - 'Tell us how we can improve', 'أخبرنا كيف يمكننا تحسين'), + 'Tell us how we can improve', 'أخبرنا كيف يمكننا التحسين'), border: OutlineInputBorder( borderSide: BorderSide( color: _hasError ? Colors.red : Color(0xFF7296BE), ), ), errorText: - _hasError ? _errorMessage : null, // Show error below field + _hasError ? _errorMessage : null, // Show error below field ), onChanged: (text) { // Trigger re-validation and character limit on each change @@ -412,7 +412,7 @@ class _FeedbackFormState extends State Text( context.translate( 'Submit', - 'يُقدِّم', + 'إرسال', ), ), 6.horizontalSpace, @@ -514,10 +514,10 @@ class _FeedbackFormState extends State } Widget _buildRatingRow( - String label, - double rating, - Function(double) onRatingUpdate, - ) { + String label, + double rating, + Function(double) onRatingUpdate, + ) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -573,7 +573,7 @@ class _FeedbackFormState extends State "design": _designRating, "redundancy": _redundancyRating, "emoji_rating": _emojiOptions[_selectedEmojiIndex!] - ["label"], // Emoji rating + ["label"], // Emoji rating "feedback": _feedbackController.text, }; @@ -783,7 +783,7 @@ class LangToggle extends ConsumerWidget { ref.read(preferencesUseCaseProvider.notifier).updatePreferences( (prefs) => prefs.copyWith(language: languageAfter), - ); + ); await onLoadFeedback(); // Reload feedback text after toggling language }, diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index bafc175f..0d3e5450 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -326,7 +326,8 @@ class _BaseScaffoldState extends ConsumerState { ), ListTile( leading: Icon(Icons.feedback), - title: const Text('Feedback'), + // title: const Text('Feedback'), + title: Text(AppLocalizations.of(context)!.feedback_title), onTap: () => context.go('/feedback'), ), if (role == 'admin') From 349374a87d89108fd12f87c26d11608d4d10bcdc Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Wed, 22 Jan 2025 11:12:42 +0530 Subject: [PATCH 02/18] feedback bug fix --- .../routes/drawer_routes/Drawer Items/edit_profile.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index 3828a89e..32782d42 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -621,7 +621,8 @@ class _EditProfileState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Text( - "Date of Birth*", + // "Date of Birth*", + AppLocalizations.of(context)!.dob, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, From 6b5a4b96ae6bb12e9cb3519e77b0326d524e5c21 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 22 Jan 2025 18:36:50 +0530 Subject: [PATCH 03/18] bug fix --- lib/presentation/Screens/charts/chart.dart | 44 +- .../Screens/charts/widgets/chart_widget.dart | 160 ++- .../Drawer Items/edit_profile.dart | 942 ++++++++++-------- .../drawer_routes/Drawer Items/feedback.dart | 2 +- .../drawer_routes/custom_drawer_routes.dart | 167 +--- 5 files changed, 744 insertions(+), 571 deletions(-) diff --git a/lib/presentation/Screens/charts/chart.dart b/lib/presentation/Screens/charts/chart.dart index 5c35d379..a18a0194 100644 --- a/lib/presentation/Screens/charts/chart.dart +++ b/lib/presentation/Screens/charts/chart.dart @@ -1035,49 +1035,47 @@ // // [ // { -// chart_heading :"Guest Nights by Region" -// chart_type :"fl_stacked_bar" -// dataset :"hotels" -// group_by :"GUEST_REGION" +// chart_heading :"Population Growth Over Time" +// chart_type :"line_trend" +// dataset :"population" +// group_by :"GENDER" // is_chart :"true" -// kpi :"hotel_occupancy_rate_chart_1" -// main_id :"economy" +// kpi :"population" +// main_id :"social" // response:[ // { // "ObsKey": { // "FREQ": "A", -// "GUEST_REGION": "AF", -// "H_INDICATOR": "GUN", -// "H_TYPE": "_Z", -// "MEASURE": "H", +// "GENDER": "M", +// "MEASURE": "POP", +// "POP_IND": "_Z", // "REF_AREA": "AE", // "SOURCE_DETAIL": "FCSC", -// "TIME_PERIOD": "2016", -// "UNIT_MEASURE": "NUMBER" +// "TIME_PERIOD": "1970", +// "UNIT_MEASURE": "PS" // }, // "ObsValue": { -// "Value": "2659446" +// "Value": "149195" +// } // } -// }, // { // "ObsKey": { // "FREQ": "A", -// "GUEST_REGION": "OC", -// "H_INDICATOR": "GUN", -// "H_TYPE": "_Z", -// "MEASURE": "H", +// "GENDER": "F", +// "MEASURE": "POP", +// "POP_IND": "_Z", // "REF_AREA": "AE", // "SOURCE_DETAIL": "FCSC", -// "TIME_PERIOD": "2018", -// "UNIT_MEASURE": "NUMBER" +// "TIME_PERIOD": "1995", +// "UNIT_MEASURE": "PS" // }, // "ObsValue": { -// "Value": "1127282" +// "Value": "850654" // } // } // // ] -// sub_id :"tourism" -// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_GUEST_REGION,4.3.0/...A..GUN.OTH+OC+AF+EC+AM+AC+ASC+GCC+UAE.?startPeriod=2016&dimensionAtObservation=AllDimensions" +// sub_id :"population" +// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_POP,2.7.0/....A..?startPeriod=1970&dimensionAtObservation=AllDimensions" // } // ] diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 8061a39a..5e24182b 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -108,7 +108,7 @@ class ChartWidget extends StatelessWidget { }).toSet(); } - Widget buildChart(dynamic chartData) { + Widget buildChart(dynamic chartData, BuildContext context) { print('chartDataccccccc $chartData'); // Extract group_by dynamically from the chartData if (chartData == null || chartData['response'] == null) { @@ -379,7 +379,7 @@ class ChartWidget extends StatelessWidget { ), gridData: FlGridData(show: false), borderData: FlBorderData(show: false), - barGroups: _generateBarGroups(chartData, groupByKey), + barGroups: _generateBarGroups(context, chartData, groupByKey), ), ), ), @@ -432,6 +432,17 @@ class ChartWidget extends StatelessWidget { ) ], ); + case 'line_trend': + case 'line_trend': + return LineChart(LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(), + borderData: borderData(), + lineBarsData: lineBarsData(chartData), + minX: 1970, // Adjust based on your data range + maxX: 2025, // Adjust based on your data range + )); case 'fl_multi_bar': return BarChart( BarChartData( @@ -477,6 +488,116 @@ class ChartWidget extends StatelessWidget { } } +// Function to create LineChartBarData for Males and Females + List lineBarsData(dynamic chartData) { + List lineBars = []; + + // Filter data for males and females + List> maleData = []; + List> femaleData = []; + + // Separate the data based on gender + for (var entry in chartData['response']) { + if (entry['ObsKey']['GENDER'] == 'M') { + maleData.add(entry); + } else if (entry['ObsKey']['GENDER'] == 'F') { + femaleData.add(entry); + } + } + + // Prepare data for Male + List maleSpots = maleData.map((entry) { + double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); + double yValue = double.parse(entry['ObsValue']['Value']); + return FlSpot(xValue, yValue); + }).toList(); + + // Prepare data for Female + List femaleSpots = femaleData.map((entry) { + double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); + double yValue = double.parse(entry['ObsValue']['Value']); + return FlSpot(xValue, yValue); + }).toList(); + + // Add the Male line (Blue color) + lineBars.add( + LineChartBarData( + spots: maleSpots, + isCurved: true, + color: Colors.blue, + barWidth: 3, + belowBarData: BarAreaData(show: false), + ), + ); + + // Add the Female line (Pink color) + lineBars.add( + LineChartBarData( + spots: femaleSpots, + isCurved: true, + color: Colors.pink, + barWidth: 3, + belowBarData: BarAreaData(show: false), + ), + ); + + return lineBars; + } + +// Sample implementation for other chart details like titles, grid, etc. + LineTouchData lineTouchData1() { + return LineTouchData( + touchTooltipData: LineTouchTooltipData( + // tooltipBgColor: Colors.blueAccent, + ), + // touchCallback: (LineTouchResponse touchResponse) {}, + handleBuiltInTouches: true, + ); + } + + FlGridData gridData() { + return FlGridData( + show: true, + drawVerticalLine: true, + getDrawingHorizontalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + getDrawingVerticalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + ); + } + + FlTitlesData titlesData1() { + return FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + }, + ), + ), + ); + } + + FlBorderData borderData() { + return FlBorderData( + show: true, border: Border.all(color: Colors.black, width: 1)); + } + // Define the list of unique colors final List uniqueColors = [ Color(0xFF648CBA), @@ -493,7 +614,7 @@ class ChartWidget extends StatelessWidget { } List _generateBarGroups( - dynamic chartData, String groupByKey) { + BuildContext context, dynamic chartData, String groupByKey) { Map> groupedData = {}; // Group data by `group_by` and `TIME_PERIOD`, accumulating the values for each group and year @@ -521,6 +642,8 @@ class ChartWidget extends StatelessWidget { List timePeriods = groupedData.values.first.keys.toList(); print('timePeriods $timePeriods'); + double barWidth = calculateBarWidth(context, timePeriods.length); + // For each time period, generate a BarChartGroupData for (int i = 0; i < timePeriods.length; i++) { // Intermediate variable to track the current toY value as we stack bars @@ -555,7 +678,8 @@ class ChartWidget extends StatelessWidget { BarChartRodData( toY: currentToY, // Use the accumulated `currentToY` value rodStackItems: rodStackItems, // Add the stacked items - width: 20, + // width: 20, + width: barWidth, borderRadius: BorderRadius.zero, color: Colors .transparent, // This will act as a container for stacked items @@ -644,10 +768,36 @@ class ChartWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return buildChart(chartData); + return buildChart(chartData, context); } } +double calculateBarWidth(BuildContext context, int totalBars) { + double width = MediaQuery.of(context).size.width; // Chart width + double padding = 16.0; // Space between bars + double availableWidth = (width - 100); + if (totalBars <= 0) { + throw ArgumentError("TotalBars must be greater than 0"); + } + + double barWidth = (availableWidth - (padding * (totalBars - 1))) / totalBars; + + print('barWidth - $barWidth'); + + // Ensure bar width does not exceed availableWidth + if (barWidth > availableWidth) { + barWidth = availableWidth; + } + + // Optional: Add a minimum width constraint if needed + double minWidth = 10.0; // Example minimum width + if (barWidth < minWidth) { + barWidth = minWidth; + } + + return barWidth; +} + class ChartData { final String? x; final double y; diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index 3828a89e..283e9249 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -11,10 +11,11 @@ import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../../../Screens/auth_verification/changepassword.dart'; -import '../custom_drawer_routes.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; class EditProfile extends StatefulWidget { + const EditProfile({super.key}); + @override State createState() => _EditProfileState(); } @@ -31,7 +32,7 @@ class _EditProfileState extends State { true, true, true, - true + true, ]; // One for each TextField String _getControllerText(int index) { @@ -57,7 +58,7 @@ class _EditProfileState extends State { 'United Arab Emirates', 'United States', 'India', - 'Canada' + 'Canada', ]; String? _selectedCountry; bool isChecked = false; @@ -65,11 +66,15 @@ class _EditProfileState extends State { final _picker = ImagePicker(); File? _profileImage; String _avatarUrl = ''; + bool isPageLoad = false; // Regular expression to validate Full Name (no special characters) - final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); + final RegExp _nameRegExp = RegExp(r'^[a-zA-Z\s]+$'); late Future userDetails; dynamic userId; + dynamic recordId; + dynamic collectionId; + dynamic role; @override void initState() { @@ -147,16 +152,22 @@ class _EditProfileState extends State { Future _fetchUserData() async { try { + setState(() { + isPageLoad = true; // Show loader + print('Im isPageLoad'); + }); + print('EDIT PROFILE isPageLoad'); final adminAuth = await _pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - print('adminToken- ${adminToken}'); + print('adminToken- $adminToken'); final userDetailsResponse = await _pb.collection('users').getOne( userId, headers: { 'Authorization': 'Bearer $adminToken', }, ); + print('userDetails: $userDetailsResponse'); setState(() { _usernameController.text = userDetailsResponse.data['uname'] ?? ''; @@ -179,9 +190,10 @@ class _EditProfileState extends State { // Set the avatar URL String avatarFilename = userDetailsResponse.data['avatar'] ?? ''; - String recordId = userId; - String collectionId = + recordId = userId; + collectionId = userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; + role = userDetailsResponse.data['role']; if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = @@ -189,6 +201,7 @@ class _EditProfileState extends State { } else { _avatarUrl = ''; // Reset to default or empty } + isPageLoad = false; }); } catch (e) { print('Error fetching user details: $e'); @@ -229,7 +242,7 @@ class _EditProfileState extends State { if (fileExtension == 'jpg' || fileExtension == 'jpeg' || fileExtension == 'png' || - fileExtension == "heic") { + fileExtension == 'heic') { setState(() { _isLoading = false; _profileImage = File(pickedFile.path); @@ -250,13 +263,17 @@ class _EditProfileState extends State { final DateTime today = DateTime.now(); final DateTime initialDate = _selectedDate ?? today.subtract( - const Duration(days: 365 * 18)); // Default to 18 years ago - final DateTime firstDate = today.subtract(const Duration( - days: 365 * 100)); // Allow picking dates back to 100 years ago + const Duration(days: 365 * 18), + ); // Default to 18 years ago + final DateTime firstDate = today.subtract( + const Duration( + days: 365 * 100, + ), + ); // Allow picking dates back to 100 years ago final DateTime lastDate = today; // Allow picking dates up to today // Updated Date format to DD/MM/YYYY - final DateFormat _dateFormat = DateFormat('dd/MM/yyyy'); + final DateFormat dateFormat = DateFormat('dd/MM/yyyy'); final DateTime? pickedDate = await showDatePicker( context: context, @@ -268,7 +285,7 @@ class _EditProfileState extends State { if (pickedDate != null && pickedDate != _selectedDate) { setState(() { _selectedDate = pickedDate; - _dateController.text = _dateFormat.format(pickedDate); + _dateController.text = dateFormat.format(pickedDate); }); } } @@ -314,7 +331,7 @@ class _EditProfileState extends State { try { String userID = userId; - print("ShowConfirmationuserID - $userID "); + print('ShowConfirmationuserID - $userID '); // Retrieve data from the country/region field String countryRegion = _selectedCountry ?? ''; // Ensure the country is selected @@ -330,10 +347,12 @@ class _EditProfileState extends State { // If profile image exists, add it if (_profileImage != null) { - request.files.add(await http.MultipartFile.fromPath( - 'avatar', - _profileImage!.path, - )); + request.files.add( + await http.MultipartFile.fromPath( + 'avatar', + _profileImage!.path, + ), + ); } // Add headers (e.g., authorization) @@ -341,25 +360,26 @@ class _EditProfileState extends State { // Send the request final response = await request.send(); - print(response); - + // print(response); + print('ShowConfirmationuser response - $response '); // Handle response if (response.statusCode == 200) { _resetFormFields(); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Profile updated successfully!")), + SnackBar(content: Text('Profile updated successfully!')), ); + // print('ShowConfirmation userData - $userData '); context.go('/myhomepage'); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: - Text("Failed to update profile: ${response.statusCode}")), + content: Text('Failed to update profile: ${response.statusCode}'), + ), ); } } catch (error) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Failed to update profile: $error")), + SnackBar(content: Text('Failed to update profile: $error')), ); } } else { @@ -392,9 +412,9 @@ class _EditProfileState extends State { try { // Simulate a network delay of 2 seconds (this mimics loading an image from the internet) // await Future.delayed(Duration(seconds: 3)); - print("Image loaded from URL: $url"); + print('Image loaded from URL: $url'); } catch (e) { - print("Error loading image: $e"); + print('Error loading image: $e'); throw Exception('Failed to load image'); } } @@ -413,68 +433,140 @@ class _EditProfileState extends State { key: _formKey, child: Padding( padding: const EdgeInsets.all(20.0), - child: Column( - children: [ - // CircleAvatar( - // radius: 50, - // backgroundImage: _profileImage != null - // ? FileImage( - // _profileImage!) // If a local file is selected - // : _avatarUrl.isNotEmpty - // ? NetworkImage(_avatarUrl) // Load from URL - // : AssetImage( - // "assets/edit_profile/profile.png") - // as ImageProvider, - // - // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, - // - // child: - // Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, // Call `_pickImage` on tap - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ), + child: isPageLoad + ? Center( + child: CircularProgressIndicator(), + ) + : Column( + children: [ + // CircleAvatar( + // radius: 50, + // backgroundImage: _profileImage != null + // ? FileImage( + // _profileImage!) // If a local file is selected + // : _avatarUrl.isNotEmpty + // ? NetworkImage(_avatarUrl) // Load from URL + // : AssetImage( + // "assets/edit_profile/profile.png") + // as ImageProvider, + // + // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, + // + // child: + // Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, // Call `_pickImage` on tap + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), - Stack( - alignment: Alignment.center, - children: [ - CircleAvatar( - radius: 50, - backgroundImage: _profileImage != null - ? FileImage( - _profileImage!) // If a local file is selected - : _avatarUrl.isNotEmpty - ? NetworkImage( - _avatarUrl) // Load from URL - : AssetImage( - "assets/edit_profile/profile.png") - as ImageProvider, - child: _avatarUrl.isNotEmpty - ? FutureBuilder( - future: _loadImage(_avatarUrl), - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - return Center( - child: Icon(Icons.error), - ); - } else { - return Align( + // Stack( + // alignment: Alignment.center, + // children: [ + // // FutureBuilder to load the image + // FutureBuilder( + // future: _loadProfileImage(), + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.waiting) { + // // While the image is loading, show a progress indicator + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else if (snapshot.hasError || snapshot.data == null) { + // // If there's an error or no image, show an error icon + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else { + // // Display the loaded image + // return CircleAvatar( + // radius: 50, + // backgroundImage: snapshot.data, + // child: Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ); + // } + // }, + // ), + // ], + // ), + + Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: 50, + backgroundImage: _profileImage != null + ? FileImage( + _profileImage!) // If a local file is selected + : _avatarUrl.isNotEmpty + ? NetworkImage( + _avatarUrl) // Load from URL + : AssetImage( + 'assets/edit_profile/profile.png') + as ImageProvider, + child: _avatarUrl.isNotEmpty + ? FutureBuilder( + future: _loadImage(_avatarUrl), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Center( + child: + CircularProgressIndicator(), + ); + } else if (snapshot.hasError) { + return Center( + child: Icon(Icons.error), + ); + } else { + return Align( + alignment: + Alignment.bottomRight, + child: GestureDetector( + onTap: + _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: + Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), + ), + ), + ); + } + }, + ) + : Align( alignment: Alignment.bottomRight, child: GestureDetector( onTap: @@ -489,345 +581,352 @@ class _EditProfileState extends State { ), ), ), - ); - } - }, - ) - : Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: - _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, ), + ), + if (_isLoading) + Positioned( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation( + Colors.grey, ), ), ), - ), - if (_isLoading) - Positioned( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.grey), - ), + ], ), - ], - ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.register_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - // hintText: _showHints[0] ? 'Mohammad Hassan' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.email_id, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.full_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - enabled: false, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - - //RegExp(r"^[a-zA-Z\s]+$"); - final nameRegex = RegExp( - r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); - if (!nameRegex.hasMatch(value)) { - return 'Invalid Characters'; - } - return null; - }, - controller: _fullNameController, - focusNode: _focusNodes[2], - decoration: InputDecoration( - // hintText: _showHints[2] ? 'Mohammad' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // counterText: '', - enabled: !_isProfileCompleted, - ), - maxLength: - 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Date of Birth*", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _dateController, - focusNode: _focusNodes[3], - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // hintText: _showHints[3] ? 'Select your Date of Birth' : null, - hintStyle: TextStyle(color: Colors.grey), - suffixIcon: const Icon(Icons.arrow_drop_down_sharp), - enabled: !_isProfileCompleted, - ), - readOnly: true, - onTap: _pickDate, - validator: _validateDob, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.region, - style: TextStyle( - fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - DropdownButtonFormField( - value: _selectedCountry, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - labelText: 'Select', - ), - items: _countries - .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) - .toList(), - onChanged: (String? newValue) { - setState(() { - _selectedCountry = newValue; - }); - }, - validator: _validateDropdown, - ), - SizedBox(height: 20), - if (!_isProfileCompleted) // Conditional rendering - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + SizedBox(height: 20), Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: - showError ? Colors.red : Colors.grey, - width: 1.5, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox(height: 10), - Text.rich( - TextSpan( - text: AppLocalizations.of(context)! - .agree, - style: - TextStyle(color: Colors.black), - children: [ - TextSpan( - text: AppLocalizations.of( - context)! - .terms_conditions, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Terms & Conditions tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context)! - .t_and, - style: TextStyle( - color: Colors.black), - ), - TextSpan( - text: AppLocalizations.of( - context)! - .privacy_policy, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Privacy Policy tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context)! - .conditions, - style: TextStyle( - color: Colors.black), - ), - ], - ), - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.visible, - softWrap: true, - ), - ], + Text( + AppLocalizations.of(context)!.register_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, ), ), ], ), - if (showError) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: - const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, - ), - ), + SizedBox(height: 10), + TextFormField( + controller: _usernameController, + focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Mohammad Hassan' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.email_id, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _emailController, + focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.full_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + enabled: false, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + + //RegExp(r"^[a-zA-Z\s]+$"); + final nameRegex = RegExp( + r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$", + ); + if (!nameRegex.hasMatch(value)) { + return 'Invalid Characters'; + } + return null; + }, + controller: _fullNameController, + focusNode: _focusNodes[2], + decoration: InputDecoration( + // hintText: _showHints[2] ? 'Mohammad' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // counterText: '', + enabled: !_isProfileCompleted, + ), + maxLength: + 40, // Set the maximum length to 20 characters + maxLengthEnforcement: + MaxLengthEnforcement.enforced, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Date of Birth*', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _dateController, + focusNode: _focusNodes[3], + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // hintText: _showHints[3] ? 'Select your Date of Birth' : null, + hintStyle: TextStyle(color: Colors.grey), + suffixIcon: + const Icon(Icons.arrow_drop_down_sharp), + enabled: !_isProfileCompleted, + ), + readOnly: true, + onTap: _pickDate, + validator: _validateDob, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.region, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 10), + DropdownButtonFormField( + value: _selectedCountry, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + labelText: 'Select', + ), + items: _countries + .map( + (item) => DropdownMenuItem( + value: item, + child: Text(item), + ), + ) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCountry = newValue; + }); + }, + validator: _validateDropdown, + ), + SizedBox(height: 20), + if (!_isProfileCompleted) // Conditional rendering + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: showError + ? Colors.red + : Colors.grey, + width: 1.5, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Text.rich( + TextSpan( + text: AppLocalizations.of( + context)! + .agree, + style: TextStyle( + color: Colors.black), + children: [ + TextSpan( + text: AppLocalizations.of( + context, + )! + .terms_conditions, + style: TextStyle( + color: Colors.blue, + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Terms & Conditions tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .t_and, + style: TextStyle( + color: Colors.black, + ), + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .privacy_policy, + style: TextStyle( + color: Colors.blue, + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Privacy Policy tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .conditions, + style: TextStyle( + color: Colors.black, + ), + ), + ], + ), + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.visible, + softWrap: true, + ), + ], + ), + ), + ], + ), + if (showError) + Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 10.0), + child: Text( + 'Please agree to terms and conditions', + style: TextStyle( + color: Colors.red[700], + fontSize: 12, + ), + ), + ), + ], + ), ], ), + SizedBox(height: 20), + Center( + child: GestureDetector( + onTap: () { + final email = _emailController.text; + context.go('/createNewPw/$userId/$email'); + }, + child: Text( + AppLocalizations.of(context)! + .change_password, + style: TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ), + ), + SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () { + showConfirmationDialog(context); + }, + icon: Icon( + Icons.save, + color: Colors.white, + ), + label: Text( + AppLocalizations.of(context)!.save, + style: TextStyle(color: Colors.white), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), ], ), - SizedBox(height: 20), - Center( - child: GestureDetector( - onTap: () { - final email = _emailController.text; - context.go('/createNewPw/$userId/$email'); - }, - child: Text( - AppLocalizations.of(context)!.change_password, - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline), - ), - ), - ), - SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () { - showConfirmationDialog(context); - }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - AppLocalizations.of(context)!.save, - style: TextStyle(color: Colors.white), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ], - ), ), ), ), @@ -841,7 +940,7 @@ class _EditProfileState extends State { } class ConfirmationDialog extends StatelessWidget { - const ConfirmationDialog({Key? key}) : super(key: key); + const ConfirmationDialog({super.key}); @override Widget build(BuildContext context) { return AlertDialog( @@ -849,7 +948,7 @@ class ConfirmationDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - "Are you sure you want to save this page?", + 'Are you sure you want to save this page?', style: const TextStyle( fontSize: 14, color: Color(0xFF898C81), @@ -861,7 +960,7 @@ class ConfirmationDialog extends StatelessWidget { SizedBox(height: 8), Text.rich( TextSpan( - text: "Once saved, you will not be able to change your ", + text: 'Once saved, you will not be able to change your ', style: TextStyle( fontSize: 14, color: Color(0xFF898C81), @@ -870,25 +969,26 @@ class ConfirmationDialog extends StatelessWidget { ), children: [ TextSpan( - text: "Country", + text: 'Country', style: TextStyle(fontWeight: FontWeight.w700), // Bold for "name" ), TextSpan( - text: " or ", + text: ' or ', ), TextSpan( - text: "Profile Image", + text: 'Profile Image', style: TextStyle( - fontWeight: FontWeight.w700), // Bold for "date of birth" + fontWeight: FontWeight.w700, + ), // Bold for "date of birth" ), TextSpan( - text: ".", + text: '.', ), ], ), textAlign: TextAlign.center, - ) + ), ], ), shape: RoundedRectangleBorder( @@ -913,7 +1013,7 @@ class ConfirmationDialog extends StatelessWidget { ), // Set the border color here ), child: Text( - "Cancel", + 'Cancel', style: TextStyle(color: Color(0xFF92722A)), ), ), @@ -938,7 +1038,7 @@ class ConfirmationDialog extends StatelessWidget { ), SizedBox( height: 5, - ) + ), ], ); } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart index 363a66d4..6d97715a 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart @@ -213,7 +213,7 @@ class _FeedbackFormState extends State title: Text(AppLocalizations.of(context)!.feedback_title), // appBar: AppBar( // backgroundColor: const Color(0xFFf8f9ff), - // title: Text(context.translate('Feedback Form', 'نموذج الملاحظات')), + // title: Text(AppLocalizations.of(context)!.feedback_title), // actions: [ // Align( // alignment: AlignmentDirectional.topEnd, diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index bafc175f..d0f37f7c 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -7,19 +9,12 @@ import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../../components/indicators/locale_provider.dart'; -import '../../components/my_toggle.dart'; - -class BaseScaffold extends ConsumerStatefulWidget { - final Widget body; - final Widget title; - final bool? showBackButton; - final bool? mycenterTitle; - final List? actions; - final Color? appbarColor, mytitleColor; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; +class BaseScaffold extends ConsumerStatefulWidget { const BaseScaffold({ - Key? key, + super.key, required this.body, required this.title, this.actions, @@ -27,7 +22,13 @@ class BaseScaffold extends ConsumerStatefulWidget { this.mycenterTitle = false, this.appbarColor, this.mytitleColor, - }) : super(key: key); + }); + final Widget body; + final Widget title; + final bool? showBackButton; + final bool? mycenterTitle; + final List? actions; + final Color? appbarColor, mytitleColor; @override _BaseScaffoldState createState() => _BaseScaffoldState(); @@ -48,6 +49,7 @@ class _BaseScaffoldState extends ConsumerState { String? collectionIds; String? collectionId; + bool isLoggedOut = false; @override void initState() { @@ -55,7 +57,6 @@ class _BaseScaffoldState extends ConsumerState { _checkUserId(); } - // Method to retrieve userId from SharedPreferences Future getUserId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('userId'); // Retrieve the userId @@ -69,125 +70,39 @@ class _BaseScaffoldState extends ConsumerState { userId = fetchedUserId; }); //print('NAVUser ID: $userId'); - _initializeUserData(); + _fetchUserData(); } else { print('No userId found'); // Handle the case where userId is not available } } - // Future _fetchUserData() async { - // try { - // final adminAuth = await _pb.admins - // .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); - // final adminToken = adminAuth.token; - // //print('adminToken- ${adminToken}'); - // final userDetailsResponse = await _pb.collection('users').getOne( - // userId!, - // headers: { - // 'Authorization': 'Bearer $adminToken', - // }, - // ); - // print('NAVuserDetails: $userDetailsResponse'); - // - // setState(() { - // userName = userDetailsResponse.data['uname'] ?? ''; - // userEmail = userDetailsResponse.data['email'] ?? ''; - // userAvatar = userDetailsResponse.data['avatar'] ?? ''; - // role = userDetailsResponse.data['role'] ?? ''; - // String recordId = userId; - // String collectionId = - // userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; - // - // if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { - // _avatarUrl = - // 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; - // } else { - // _avatarUrl = ''; // Reset to default or empty - // } - // }); - // } catch (e) { - // print('Error fetching user details: $e'); - // } - // } - - Future _initializeUserData() async { - print("Laoding 1"); - final prefs = await SharedPreferences.getInstance(); - // final userDataString = prefs.getString('userData'); - - if (prefs.containsKey('userData')) { - // print("Loading user data from local storage: $userData"); - final String? userDataString = prefs.getString('userData'); - - if (userDataString != null) { - // Decode the JSON string into a Map - final Map userData = jsonDecode(userDataString); - - // Access the userName field - setState(() { - userName = userData['userNames']; - userEmail = userData['userEmails']; - userAvatar = userData['userAvatars']; - role = userData['roles']; - String recordId = userId; - String collectionId = userData['collectionIds']; - - if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { - _avatarUrl = - 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; - } else { - _avatarUrl = ''; // Reset to default or empty - } - }); - } else { - print("No user data found in local storage."); - } - } else { - // Fetch data from server and save it to local storage - - await _fetchUserData(); - } - } - Future _fetchUserData() async { try { - // print("Fetching _fetchUserData"); final adminAuth = await _pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - + //print('adminToken- ${adminToken}'); final userDetailsResponse = await _pb.collection('users').getOne( userId!, headers: { 'Authorization': 'Bearer $adminToken', }, ); - // print('User details fetched from server: $userDetailsResponse'); - - final userData = { - 'userNames': userDetailsResponse.data['uname'] ?? '', - 'userEmails': userDetailsResponse.data['email'] ?? '', - 'userAvatars': userDetailsResponse.data['avatar'] ?? '', - 'roles': userDetailsResponse.data['role'] ?? '', - 'collectionIds': - userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_', - }; - - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('userData', jsonEncode(userData)); + print('NAVuserDetails: $userDetailsResponse'); setState(() { - userNames = userData['userNames']; - userEmails = userData['userEmails']; - userAvatars = userData['userAvatars']; - roles = userData['roles']; + userName = userDetailsResponse.data['uname'] ?? ''; + userEmail = userDetailsResponse.data['email'] ?? ''; + userAvatar = userDetailsResponse.data['avatar'] ?? ''; + role = userDetailsResponse.data['role'] ?? ''; String recordId = userId; - collectionIds = userData['collectionIds']; + String collectionId = + userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; - if (userAvatars!.isNotEmpty && recordId.isNotEmpty) { + if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = - 'https://pb.venbait.in/api/files/$collectionIds/$recordId/$userAvatars'; + 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; } else { _avatarUrl = ''; // Reset to default or empty } @@ -199,6 +114,9 @@ class _BaseScaffoldState extends ConsumerState { Future logout() async { final prefs = await SharedPreferences.getInstance(); + // setState(() { + // isLoggedOut = true; + // }); prefs.clear(); context.go('/'); } @@ -221,14 +139,16 @@ class _BaseScaffoldState extends ConsumerState { // onPressed: () {}, // icon: const Icon(Icons.toggle_off_outlined), // ), - MyToggle(isOn: locale?.languageCode == 'en', + MyToggle( + isOn: locale?.languageCode == 'en', knobTextWhenOn: 'ع', knobTextWhenOff: 'EN', pathColorWhenOn: Colors.grey.shade300, pathColorWhenOff: Colors.grey.shade300, - onTap: (){ + onTap: () { ref.read(localeProvider.notifier).toggleLocale(); - },), + }, + ), ], leading: Stack( children: [ @@ -272,9 +192,11 @@ class _BaseScaffoldState extends ConsumerState { Row( children: [ SizedBox( - width: mywidth / 8, - child: Image( - image: AssetImage('assets/logos/fcsc.png'))), + width: mywidth / 8, + child: Image( + image: AssetImage('assets/logos/fcsc.png'), + ), + ), ], ), Divider(), @@ -295,7 +217,8 @@ class _BaseScaffoldState extends ConsumerState { ) : Image( image: AssetImage( - 'assets/edit_profile/profile.png'), + 'assets/edit_profile/profile.png', + ), fit: BoxFit.cover, width: double.infinity, height: double.infinity, @@ -317,10 +240,10 @@ class _BaseScaffoldState extends ConsumerState { style: TextStyle(fontSize: 12), ), ], - ) + ), ], ), - ) + ), ], ), ), @@ -332,8 +255,10 @@ class _BaseScaffoldState extends ConsumerState { if (role == 'admin') ListTile( leading: Icon(Icons.manage_accounts), - // title: Text('Manage User'), - title: Text(AppLocalizations.of(context)!.manage_user,), + title: Text('Manage User'), + // title: Text( + // AppLocalizations.of(context)!.manage_user, + // ), onTap: () => context.go('/manageuser'), ), ListTile( From f1a72c9df942482a8d2ca8c2db772de7c1f15221 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 22 Jan 2025 18:39:23 +0530 Subject: [PATCH 04/18] merge --- pubspec.lock | 142 +++++++++++++++++++++++++++++++++++++++++---------- pubspec.yaml | 3 +- 2 files changed, 117 insertions(+), 28 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index df3242b9..d9e812c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" analyzer_plugin: dependency: transitive description: @@ -134,6 +134,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.3" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -186,10 +210,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -266,10 +290,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" + sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 url: "https://pub.dev" source: hosted - version: "1.0.0+6.7.0" + version: "1.0.0+6.11.0" dart_style: dependency: transitive description: @@ -385,15 +409,23 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" + sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" url: "https://pub.dev" source: hosted - version: "0.69.2" + version: "0.70.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_carousel_widget: dependency: "direct main" description: @@ -781,18 +813,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -821,10 +853,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" mailer: dependency: "direct main" description: @@ -889,6 +921,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -1180,7 +1220,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" smart_api_client: dependency: "direct main" description: @@ -1220,14 +1260,54 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -1256,10 +1336,18 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted version: "1.3.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: @@ -1272,10 +1360,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" the_validator: dependency: "direct main" description: @@ -1432,10 +1520,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: @@ -1533,5 +1621,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.2 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4ba75fd8..2b27550c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.69.2 + fl_chart: ^0.70.2 marquee: ^2.2.3 url_launcher: ^6.3.1 @@ -60,6 +60,7 @@ dependencies: material_charts: ^0.0.23 flutter_staggered_grid_view: ^0.7.0 flutter_plugin_android_lifecycle: ^2.0.24 + cached_network_image: ^3.4.1 dependency_overrides: fading_edge_scrollview: ^4.1.1 From 49b1efae4a3caa1f77b2071cfe715622dad31da9 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 22 Jan 2025 18:42:43 +0530 Subject: [PATCH 05/18] merge --- .run/main.dart.run.xml | 2 +- android/app/build.gradle | 8 ++++++-- android/gradle.properties | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4767aff8..4a58a7c9 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index ec52a38b..49fcb1cd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -29,8 +29,12 @@ android { // ndkVersion = flutter.ndkVersion ndkVersion = "25.1.8937393" compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" } defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 303728bd..476c2b1c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -org.gradle.java.home=C:/Program Files/Eclipse Adoptium/jdk-21.0.5.11-hotspot +org.gradleorg.gradle.java.home=/usr/lib/jvm/openlogic-openjdk-17.0.13+11-linux-x64 diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 436f106b..6e2bc65d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import flutter_secure_storage_macos import path_provider_foundation import share_plus import shared_preferences_foundation +import sqflite_darwin import url_launcher_macos import video_player_avfoundation import webview_flutter_wkwebview @@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) From cb3085076063373e4986402b4605a0be135163e4 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Wed, 29 Jan 2025 19:20:37 +0530 Subject: [PATCH 06/18] app code add --- assets/app_tour/down_right.png | Bin 0 -> 4331 bytes assets/app_tour/leftDown.png | Bin 0 -> 4133 bytes assets/app_tour/leftUp.png | Bin 0 -> 4312 bytes lib/l10n/intl_ar.arb | 10 +- lib/l10n/intl_en.arb | 12 +- .../UAE_Numbers/Social/marriages.dart | 621 +++++++++++++++++- .../Screens/app_tour/Target_content.dart | 50 ++ .../tab_routes/home_route.dart | 563 +++++++++++++++- .../drawer_routes/custom_drawer_routes.dart | 448 ++++++++++++- pubspec.lock | 156 ++--- pubspec.yaml | 3 +- 11 files changed, 1775 insertions(+), 88 deletions(-) create mode 100644 assets/app_tour/down_right.png create mode 100644 assets/app_tour/leftDown.png create mode 100644 assets/app_tour/leftUp.png create mode 100644 lib/presentation/Screens/app_tour/Target_content.dart diff --git a/assets/app_tour/down_right.png b/assets/app_tour/down_right.png new file mode 100644 index 0000000000000000000000000000000000000000..be10a1bbf76a76da1d6f446d87d44c75f65e6dc7 GIT binary patch literal 4331 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 DP>NlX literal 0 HcmV?d00001 diff --git a/assets/app_tour/leftDown.png b/assets/app_tour/leftDown.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba2314ee8950414be1926d656586c5b9a656a8d GIT binary patch literal 4133 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 literal 0 HcmV?d00001 diff --git a/assets/app_tour/leftUp.png b/assets/app_tour/leftUp.png new file mode 100644 index 0000000000000000000000000000000000000000..56df6adcc628f617e2e5b0cc3cba43b6dc4ef4a0 GIT binary patch literal 4312 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 literal 0 HcmV?d00001 diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index a191b63e..1f5eb600 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -82,5 +82,13 @@ "password_between_8_to_40": "يجب أن تتراوح كلمة المرور بين 8 و64 حرفًا", "new_password_required": "كلمة المرور الجديدة مطلوبة", "old_password_is_required": "كلمة المرور القديمة مطلوبة", - "confirm_password_is_required": "تأكيد كلمة المرور مطلوب" + "confirm_password_is_required": "تأكيد كلمة المرور مطلوب", + + "kpiCards": "يمكنك عرض بطاقات مؤشرات الأداء الرئيسية التفاعلية والرسوم البيانية لاستكشاف اتجاهات البيانات ومقارنة المقاييس الرئيسية بصريًا", + "home_topic": "اختر أي فئة (على سيبل المثال : الاقتصاد، الاجتماعية، البيئة) لاستكشاف مؤشرات الأداء الرئيسية التفصيلية ، التصورات، ورؤى البيانات .", + "economy": "يمكنك الوصول إلى الإحصائيات التفصيلية وصفحات التحليل لمؤشرات الأداء الرئيسية في فئة الاقتصاد لاكتشاف الاتجاهات والمقاييس الرئيسية .", + "bookMark": "يمكنك وضع إشارة مرجعية على مؤشرات الأداء الرئيسية أو مجموعات البيانات المهمة للوصول السريع والسهل في أي وقت من خلال قسم الإشارات المرجعية. ", + "toggle": "يمكنك التبديل بين اللغتين الإنجليزية والعربية باستخدام مفتاح تبديل اللغة في الزاوية اليمنى العليا للحصول على تجربة ثنائية اللغة.", + "tour_navBar": "يمكنك استخدام شريط التنقل للوصول السريع إلى الملف الشخصي ,التعليقات , دليل المستخدم , الإشعارات والميزات الأساسية الأخرى للتطبيق.", + "mainMenu": "يمكنك الوصول إلى الصفحات المخصصة والميزات الإضافية عن طريق تحديد الخيارات من القائمة الرئيسية." } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b034d44b..dac0f961 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -13,7 +13,7 @@ "enter_your_password": "Enter your password", "register_Confirm_password": "Confirm password", "agree": "I agree to ", - "t_and": "and", + "t_and": " and ", "conditions": " of Fcsc", "terms_conditions": "terms & conditions", "privacy_policy":"privacy policy", @@ -83,5 +83,13 @@ "password_between_8_to_40": "Password must be between 8 and 64 characters", "new_password_required": "New password is required", "old_password_is_required": "Old password is required", - "confirm_password_is_required": "Confirm password is required" + "confirm_password_is_required": "Confirm password is required", + + "kpiCards": "View interactive KPI cards and graphs to explore data trends and compare key metrics visually.", + "home_topic": "Tap on any category (e.g., Economy, Social, Environment) to explore detailed KPIs, visualizations, and data insights.", + "economy": "Access detailed statistics and drill-down pages for KPIs in the Economy category to uncover key trends and metrics.", + "bookMark": "Bookmark important KPIs or datasets for quick and easy access anytime through the Bookmarks section.", + "toggle": "Switch between English and Arabic using the language toggle at the top-right corner for a bilingual experience.", + "tour_navBar": "Use the navigation bar to quickly access Profile, Feedback, User Guide, Notifications, and other key app features.", + "mainMenu": "Access dedicated pages and additional features by selecting options from the Main Menu." } diff --git a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart index 0a9a7e50..c4541382 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/Social/marriages.dart @@ -1,12 +1,626 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import 'package:uae_stat/presentation/components/constant/constant.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; - -class Marriages extends StatelessWidget { +class Marriages extends ConsumerStatefulWidget { const Marriages({super.key}); + @override + ConsumerState createState() => _MarriagesState(); +} + +class _MarriagesState extends ConsumerState { + late List marriageTargets; + late List previousMarriageTargets; + late TutorialCoachMark tutorialCoachMark; + final GlobalKey chartKey = GlobalKey(); + final GlobalKey bookMarkKey = GlobalKey(); + late double cardHeight= 0; + + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _calculateMarkPosition(); + _startTutorialAfterRender(); + }); + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + context.go('/myhomepage'); + } + + void _kpiTutorialCoachMark() { + + + _initmarriageTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: marriageTargets, + onClickTarget: (target) { + if (target.identify == 'chartKey') { + // Scroll to the long widget + Scrollable.ensureVisible( + chartKey.currentContext!, + duration: Duration(milliseconds: 500), + ); + } + }, + onFinish: () { + context.push('/myhomepage'); + debugPrint('Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + void _previousKpiTutorial() { + + _initpreviousTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousMarriageTargets, + onFinish: () { + context.push('/myhomepage'); + debugPrint('Previous Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + + + void _startTutorialAfterRender() { + final chartTour = ref.watch(chartsTourProvider); + final previousChartTour = ref.watch(previousChartsTourProvider); + if (chartKey.currentContext != null && !chartTour) { + _kpiTutorialCoachMark(); + } else if(!previousChartTour) { + _previousKpiTutorial(); + } + else if (!chartTour || !previousChartTour){ + Future.delayed(Duration(milliseconds: 100), _startTutorialAfterRender); + }else{ + return; + } + + } + + void _calculateMarkPosition() { + final RenderBox cardRenderBox = bookMarkKey.currentContext!.findRenderObject() as RenderBox; + final Size cardSize = cardRenderBox.size; + cardHeight = cardSize.height; + debugPrint('height : $cardHeight'); + } + + void _initmarriageTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + marriageTargets=[ + TargetFocus( + identify: 'chartsKey', + keyTarget: chartKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + alignment: ContentAlign.top, + gap:50, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + alignment: ContentAlign.bottom, + gap: 23, + space: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: cardHeight+20), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + ]; + } + + void _initpreviousTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + previousMarriageTargets=[ + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + space: 50, + alignment: ContentAlign.bottom, + gap: 23, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: cardHeight+20), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'chartsKey', + keyTarget: chartKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + space: 0, + alignment: ContentAlign.top, + gap:50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 30, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(previousChartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + + + ]; + } @override Widget build(BuildContext context) { double myheight = MediaQuery.of(context).size.height; @@ -33,7 +647,7 @@ class Marriages extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - IconButton(onPressed: (){}, icon: Icon(Icons.bookmark_add_outlined,color: Colors.white,size: 40,)), + IconButton(key :bookMarkKey,onPressed: (){}, icon: Icon(Icons.bookmark_add_outlined,color: Colors.white,size: 40,)), IconButton(onPressed: (){}, icon: Icon(Icons.share,color: Colors.white,size: 30,)), ],), ], @@ -47,6 +661,7 @@ class Marriages extends StatelessWidget { child: Column( children: [ Row( + key:chartKey, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container(height: myheight/3.5,width: mywidth/2.5, diff --git a/lib/presentation/Screens/app_tour/Target_content.dart b/lib/presentation/Screens/app_tour/Target_content.dart new file mode 100644 index 00000000..51e52b06 --- /dev/null +++ b/lib/presentation/Screens/app_tour/Target_content.dart @@ -0,0 +1,50 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:flutter/material.dart'; + +final allTargetsCompletedProvider = StateProvider((ref) => true); +final chartsTourProvider = StateProvider((ref) => true); +final previousChartsTourProvider = StateProvider((ref) => true); +final homeTourProvider = StateProvider((ref) => false); +final previousHomeTourProvider = StateProvider((ref) => true); +final scaffoldTourProvider = StateProvider((ref) => true); +final previousScaffoldTourProvider = StateProvider((ref) => true); + +TargetContent createTargetContent({ + required String text, + required ContentAlign alignment, + required double gap, + required double space , +}) { + return TargetContent( + align: alignment, + builder: (context, controller) { + return Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10, right: 10, bottom: space , top: space ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: gap,), + ], + ), + ], + ); + }, + ); +} \ No newline at end of file diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index 260f7692..779033df 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -163,8 +163,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import '../../drawer_routes/custom_drawer_routes.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:http/http.dart' as http; @@ -215,26 +219,571 @@ class _MyHomePageState extends State { } } -class EconomyStatsWidget extends StatefulWidget { +class EconomyStatsWidget extends ConsumerStatefulWidget { const EconomyStatsWidget({super.key}); @override EconomyStatsState createState() => EconomyStatsState(); } -class EconomyStatsState extends State { +class EconomyStatsState extends ConsumerState { List data = []; bool isLoading = true; + final GlobalKey cardTopicKey = GlobalKey(); + final GlobalKey cardsKey = GlobalKey(); + late TutorialCoachMark tutorialCoachMark; + late List homeTargets; + late List previousHomeTargets; + Offset _imageCardPosition =Offset.zero; + Offset _imageTopicPosition =Offset.zero; + + void _calculateCardPosition() { + final RenderBox cardRenderBox = cardsKey.currentContext!.findRenderObject() as RenderBox; + final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); + final Size cardSize = cardRenderBox.size; + + // Calculate the position for the image + final double imageX = cardPosition.dx + cardSize.width / 2; + final double imageY = cardPosition.dy + cardSize.height; + + setState(() { + _imageCardPosition = Offset(imageX, imageY); + }); + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + } + + //Method waits app tour to render Targets + void _starTourRender() { + if (cardTopicKey.currentContext != null) { + _showHomeTour(); + } else { + Future.delayed(Duration(milliseconds: 100), _starTourRender); + } + } + + //Method to Start App Tour + void _showHomeTour() { + final homeTour = ref.watch(homeTourProvider); + final previousHomeTour = ref.watch(previousHomeTourProvider); + +// Check and show tutorials + if (!homeTour) { + // Show Home Tour + _initTarget(); + _calculateCardPosition(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: homeTargets, + onFinish: () { + ref.read(homeTourProvider.notifier).state = true; + ref.read(chartsTourProvider.notifier).state = false; + debugPrint('Home Tutorial Finished'); + context.push('/marriages'); + }, + )..show(context: context); + } else if (!previousHomeTour) { + _initPreviousTarget(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousHomeTargets, + onFinish: () { + ref.read(previousHomeTourProvider.notifier).state = true; + debugPrint('Previous Home Tutorial Finished'); + context.push('/marriages'); + }, + )..show(context: context); + } + + } + + void _initTarget(){ + homeTargets=[ + TargetFocus( + identify: 'cardTopicKey', + keyTarget: cardTopicKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.home_topic, + alignment: ContentAlign.bottom, + gap: 45, + space: 40, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.69, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.4, + top: _imageCardPosition.dy-29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + TargetFocus( + identify: 'cardsKey', + keyTarget: cardsKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text:AppLocalizations.of(context)!.economy, + alignment: ContentAlign.bottom, + gap: 0, + space: 40,), + + TargetContent( + align: ContentAlign.bottom, + child: + SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () =>handleSkip, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: MediaQuery.of(context).size.width , + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + top: _imageTopicPosition.dy-29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + ]; + } + + void _initPreviousTarget(){ + previousHomeTargets=[ + TargetFocus( + identify: 'cardsKey', + keyTarget: cardsKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text:AppLocalizations.of(context)!.economy, + alignment: ContentAlign.bottom, + gap: 0, + space: 40,), + TargetContent( + align: ContentAlign.bottom, + child: + SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () =>handleSkip, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(previousHomeTourProvider.notifier).state=true; + ref.read(chartsTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + top: _imageTopicPosition.dy-29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + TargetFocus( + identify: 'cardTopicKey', + keyTarget: cardTopicKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text:AppLocalizations.of(context)!.home_topic, + alignment: ContentAlign.bottom, + gap: 45, + space: 40, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.69, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.4, + top: _imageCardPosition.dy-29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + ]; + } + + @override void initState() { super.initState(); fetchData(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _starTourRender(); + }); } Future fetchData() async { - const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; + const baseUrl = 'https://pb.venbait.in/api/getHomePageData?language=en'; try { final response = await http.get(Uri.parse(baseUrl)); if (response.statusCode == 200) { @@ -336,6 +885,9 @@ class EconomyStatsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ RoundedCornerContainer( + key: mainTopic['main_topic'] =='ECONOMY' + ? cardTopicKey + : null, text: mainTopic['main_topic'], backgroundColor: backgroundColor, textStyle: const TextStyle( @@ -347,6 +899,9 @@ class EconomyStatsState extends State { SizedBox( height: myheight / 4.8, // Set dynamic height child: Container( + key:mainTopic['main_topic'] == 'ECONOMY' + ? cardsKey + : null, // color: Colors.grey[200], // Set a background color for the scrollable container child: ConstrainedBox( constraints: BoxConstraints( diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 0d3e5450..1d13c05d 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -6,9 +6,11 @@ import 'package:go_router/go_router.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; -import '../../components/indicators/locale_provider.dart'; -import '../../components/my_toggle.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; class BaseScaffold extends ConsumerStatefulWidget { final Widget body; @@ -48,11 +50,447 @@ class _BaseScaffoldState extends ConsumerState { String? collectionIds; String? collectionId; + late TutorialCoachMark tutorialCoachMark; + late List appBarTargets; + final GlobalKey bottomNavKey = GlobalKey(); + final GlobalKey toggleKey = GlobalKey(); + final GlobalKey drawerKey = GlobalKey(); + late double toggleHeight=0; @override void initState() { super.initState(); _checkUserId(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _startAppbarTour(); + }); + } + //Method to Start App Tour + void _startAppbarTour() { + final scaffoldProvider = ref.watch(scaffoldTourProvider); + _calculatetooglePosition(); + if (!scaffoldProvider) { + // Show Home Tour + _initTarget(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: appBarTargets, // Ensure `homeTargets` is defined for Home Tour + onFinish: () { + ref.read(homeTourProvider.notifier).state = true; + debugPrint('Home Tutorial Finished'); + }, + )..show(context: context); + } + + } + + void _calculatetooglePosition() { + final RenderBox cardRenderBox = toggleKey.currentContext!.findRenderObject() as RenderBox; + final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); + final Size cardSize = cardRenderBox.size; + toggleHeight = cardSize.height; + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + } + + void _initTarget(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + appBarTargets=[ + TargetFocus( + identify: 'bottomNav', + keyTarget: bottomNavKey, + shape: ShapeLightFocus.RRect, // Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.tour_navBar, + alignment: ContentAlign.top, + space: 0, + gap: 100, + + ), + TargetContent( + align: ContentAlign.top, + child: + SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height* 0.30, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '5/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.finish(); + ref.read(scaffoldTourProvider.notifier).state=true; + ref.read(previousChartsTourProvider.notifier).state=false; + context.push('/marriages'); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:screenWidth*0.4, bottom: 0 ), + align: ContentAlign.top, + child: Container( + width: 200, + height: 100, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftUp.png', + fit: BoxFit.contain, + ), + + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'drawerKey', + keyTarget: drawerKey, + shape: ShapeLightFocus.RRect, + radius: 0, // No rounded corners + paddingFocus: 1, // Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.mainMenu, + alignment: ContentAlign.bottom, + gap: 55, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: + SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height* 0.80, // Set an appropriate height for the Stack + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '6/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(right: screenWidth*0.7, top: toggleHeight-5), + align: ContentAlign.right, + child: Container( + width: 200, + height: screenHeight/4, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'toggleKey', + keyTarget: toggleKey, + shape: ShapeLightFocus.RRect, + radius: 0, + paddingFocus: 0,// Rounded rectangle highlight + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.toggle, + space: 0, + alignment: ContentAlign.bottom, + gap: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: + SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height* 0.80, + child: Stack( + children: [ + Positioned( + bottom: 25, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '7/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + + ElevatedButton( + onPressed: () { + debugPrint('Got it clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + tutorialCoachMark.finish(); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.white, + elevation: 0, + ), + child: const Text( + 'Got it', + style: TextStyle( + color: Color(0xFF7DAFBC), + fontSize: 14, + ), + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:screenWidth*0.7, top: toggleHeight), + align: ContentAlign.left, + child: Container( + width: 200, + height: screenHeight/4, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/down_right.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + ]; } // Method to retrieve userId from SharedPreferences @@ -221,7 +659,9 @@ class _BaseScaffoldState extends ConsumerState { // onPressed: () {}, // icon: const Icon(Icons.toggle_off_outlined), // ), - MyToggle(isOn: locale?.languageCode == 'en', + MyToggle( + key: toggleKey, + isOn: locale?.languageCode == 'en', knobTextWhenOn: 'ع', knobTextWhenOff: 'EN', pathColorWhenOn: Colors.grey.shade300, @@ -248,6 +688,7 @@ class _BaseScaffoldState extends ConsumerState { child: Builder( builder: (BuildContext context) { return IconButton( + key:drawerKey, color: (widget.showBackButton == true) ? Colors.white : Colors.black, @@ -351,6 +792,7 @@ class _BaseScaffoldState extends ConsumerState { ), ), bottomNavigationBar: BottomNavigationBar( + key: bottomNavKey, currentIndex: _getSelectedIndex(currentRoute), onTap: (index) => _onItemTapped(context, index), type: BottomNavigationBarType.fixed, diff --git a/pubspec.lock b/pubspec.lock index f310f720..be76549b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "88399e291da5f7e889359681a8f64b18c5123e03576b01f32a6a276611e511c3" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "78.0.0" + version: "72.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "62899ef43d0b962b056ed2ebac6b47ec76ffd003d5f7c4e4dc870afe63188e33" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "6.7.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "1d460d14e3c2ae36dc2b32cef847c4479198cf87704f63c3c3c8150ee50c3916" + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" url: "https://pub.dev" source: hosted - version: "0.12.0" + version: "0.11.3" ansicolor: dependency: transitive description: @@ -74,50 +74,50 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.1" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.14" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" connectivity_plus: dependency: "direct main" description: @@ -258,42 +258,42 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "6d509673c4dd0baa90e60dc8366bc2acc6690f16a7d44bfae31294d82c5d2a62" + sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.0" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "8cc525c7b160eb47bb1ded8b2633c0f8b907930eb986ac577aded87cdd2835fe" + sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.0" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "6dcee8a017181941c51a110da7e267c1d104dc74bec8862eeb8c85b5c8759a9e" + sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.0" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: "14df0760dfa81b7b0c398c876045f4e4a343eb2c9d200c66163671dd3e337c1b" + sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" url: "https://pub.dev" source: hosted - version: "1.0.0+7.1.0" + version: "1.0.0+6.7.0" dart_style: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.3.7" dbus: dependency: transitive description: @@ -565,10 +565,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" url: "https://pub.dev" source: hosted - version: "2.5.8" + version: "2.5.7" freezed_annotation: dependency: "direct main" description: @@ -661,10 +661,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.1.2" + version: "4.0.2" image: dependency: transitive description: @@ -749,10 +749,10 @@ packages: dependency: "direct dev" description: name: injectable_generator - sha256: b04673a4c88b3a848c0c77bf58b8309f9b9e064d9fe1df5450c8ee1675eaea1a + sha256: af403d76c7b18b4217335e0075e950cd0579fd7f8d7bd47ee7c85ada31680ba1 url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.6.2" intl: dependency: "direct main" description: @@ -789,10 +789,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: b0a98230538fe5d0b60a22fb6bf1b6cb03471b53e3324ff6069c591679dd59c9 + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.9.3" + version: "6.9.0" jwt_decoder: dependency: "direct main" description: @@ -805,18 +805,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -829,10 +829,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.0.0" logging: dependency: transitive description: @@ -845,10 +845,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" mailer: dependency: "direct main" description: @@ -1045,10 +1045,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.4.0" recase: dependency: transitive description: @@ -1077,10 +1077,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" + sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 url: "https://pub.dev" source: hosted - version: "0.5.9" + version: "0.5.8" riverpod_annotation: dependency: "direct main" description: @@ -1093,18 +1093,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" + sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" url: "https://pub.dev" source: hosted - version: "2.6.4" + version: "2.6.3" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693 + sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99" url: "https://pub.dev" source: hosted - version: "2.6.4" + version: "2.6.3" rxdart: dependency: transitive description: @@ -1196,10 +1196,10 @@ packages: dependency: transitive description: name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.4.1" shelf_web_socket: dependency: transitive description: @@ -1212,7 +1212,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" smart_api_client: dependency: "direct main" description: @@ -1224,10 +1224,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1256,10 +1256,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1288,10 +1288,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1320,10 +1320,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" the_validator: dependency: "direct main" description: @@ -1340,6 +1340,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tutorial_coach_mark: + dependency: "direct main" + description: + name: tutorial_coach_mark + sha256: df450c88d4c812bc221afd3ff948da3dc0f44c0b4fa5dbc046d6d86f2cfc9e71 + url: "https://pub.dev" + source: hosted + version: "1.2.12" typed_data: dependency: transitive description: @@ -1408,10 +1416,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: @@ -1448,10 +1456,10 @@ packages: dependency: transitive description: name: video_player_android - sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" + sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" url: "https://pub.dev" source: hosted - version: "2.7.17" + version: "2.7.16" video_player_avfoundation: dependency: transitive description: @@ -1480,10 +1488,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" watcher: dependency: transitive description: @@ -1581,5 +1589,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.5.2 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0145a0ed..6caf3e5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: cupertino_icons: ^1.0.6 fl_chart: ^0.69.2 - + tutorial_coach_mark: ^1.2.12 marquee: ^2.2.3 url_launcher: ^6.3.1 # dynamic_layouts: @@ -110,6 +110,7 @@ flutter: - assets/cached_responses/sharepoint/country_leader_imgs/ - assets/edit_profile/ - assets/splash_screen/ + - assets/app_tour/ fonts: - family: Segoe From aab1520305f7176b69eb4d5b8be01b832c8c0c71 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Wed, 29 Jan 2025 19:52:02 +0530 Subject: [PATCH 07/18] app code added --- .run/main.dart.run.xml | 2 +- .../tab_routes/home_route.dart | 2 +- .../drawer_routes/custom_drawer_routes.dart | 1 + pubspec.lock | 22 ++++++++++++++++--- pubspec.yaml | 1 + 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4a58a7c9..4767aff8 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ 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 779033df..6329c251 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 @@ -570,7 +570,7 @@ class EconomyStatsState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip, + onPressed: () =>handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 92e0ad82..5c027e11 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -741,6 +741,7 @@ class _BaseScaffoldState extends ConsumerState { label: AppLocalizations.of(context)!.country_profile, ), ], + key: bottomNavKey, selectedItemColor: Colors.black, unselectedItemColor: Colors.grey, showUnselectedLabels: true, diff --git a/pubspec.lock b/pubspec.lock index c1d2fc52..1bb21a9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,10 +433,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" + sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" url: "https://pub.dev" source: hosted - version: "0.70.2" + version: "0.69.2" flutter: dependency: "direct main" description: flutter @@ -496,7 +496,7 @@ packages: source: hosted version: "2.4.4" flutter_plugin_android_lifecycle: - dependency: transitive + dependency: "direct main" description: name: flutter_plugin_android_lifecycle sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" @@ -953,6 +953,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -1380,6 +1388,14 @@ packages: url: "https://pub.dev" source: hosted version: "28.1.39" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 90e0f094..cbabc649 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -112,6 +112,7 @@ flutter: - assets/cached_responses/sharepoint/country_leader_imgs/ - assets/edit_profile/ - assets/splash_screen/ + - assets/app_tour/ fonts: - family: Segoe From 5c5243d02ebc449000e3eb6f90a715b5f7a28da5 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 30 Jan 2025 16:58:50 +0530 Subject: [PATCH 08/18] Charts Added --- lib/config/my_router.dart | 2 + .../UAE_Numbers/uae_numbers.dart | 53 +- .../auth_verification/create_new_pw.dart | 24 + .../Screens/charts/screens/chart_screen.dart | 535 ++++++-- .../Screens/charts/services/api_service.dart | 7 +- .../Screens/charts/widgets/chart_widget.dart | 1166 ++++++++++++++--- .../routes/auth_routes/login_route.dart | 12 +- .../tab_routes/home_route.dart | 81 +- .../drawer_routes/custom_drawer_routes.dart | 200 +-- .../drawer_routes/user_guide_route.dart | 199 +-- 10 files changed, 1737 insertions(+), 542 deletions(-) diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 6449fff3..1678f6e3 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -374,6 +374,7 @@ final GoRouter router = GoRouter( '0xFFFFFFFF'; // Default white final mainTopic = state.uri.queryParameters['mainTopic'] ?? ''; final title = state.uri.queryParameters['title'] ?? ''; + final key = state.uri.queryParameters['key'] ?? ''; print('Router dataSets: $dataSets'); print('Router bgColor: $bgColor'); @@ -386,6 +387,7 @@ final GoRouter router = GoRouter( bgColor: bgColor, mainTopic: mainTopic, title: title, + keyParam: key, ); }, ), diff --git a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart index 190d010f..6917a159 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart @@ -1,7 +1,9 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/presentation/components/constant/constant.dart'; @@ -23,28 +25,31 @@ class UaeNumbers extends StatelessWidget { } } -class uaenumberWidget extends StatefulWidget { +class uaenumberWidget extends ConsumerStatefulWidget { const uaenumberWidget({super.key}); @override - _UaenumberWidgetState createState() => _UaenumberWidgetState(); + ConsumerState createState() => _UaenumberWidgetState(); } -class _UaenumberWidgetState extends State { +class _UaenumberWidgetState extends ConsumerState { @override List homePageData = []; bool isLoading = true; + int? expandedIndex = 0; @override void initState() { - fetchData(); + super.initState(); + final locale = ref.read(localeProvider); + fetchData(locale?.languageCode ?? 'en'); } - Future fetchData() async { + Future fetchData(locale) async { // const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; const baseUrl = 'https://pb.venbait.in/api/getUAENumbersData'; try { - final response = await http.get(Uri.parse(baseUrl)); + final response = await http.get(Uri.parse(baseUrl + '?language=$locale')); if (response.statusCode == 200) { setState(() { homePageData = json.decode(response.body); @@ -63,6 +68,10 @@ class _UaenumberWidgetState extends State { } Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchData(localeCode); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; @@ -99,7 +108,12 @@ class _UaenumberWidgetState extends State { return CustomExpandableTile( index: index, - isExpanded: isFirstTile, +isExpanded: expandedIndex == index, // Compare with expandedIndex + onTap: (index) { + setState(() { + expandedIndex = (expandedIndex == index) ? null : index; + }); + }, title: mainTopic['main_topic'], titleBackgroundColor: backgroundColor, children: _buildSubTopics(mainTopic['sub_topics'] ?? [], mywidth, @@ -280,6 +294,8 @@ class CustomExpandableTile extends StatefulWidget { final List children; final int index; final bool isExpanded; + final ValueChanged onTap; + const CustomExpandableTile({ required this.title, @@ -287,6 +303,8 @@ class CustomExpandableTile extends StatefulWidget { required this.children, required this.index, required this.isExpanded, + required this.onTap, + }); @override @@ -294,14 +312,12 @@ class CustomExpandableTile extends StatefulWidget { } class _CustomExpandableTileState extends State { - // bool isExpanded = false; - late bool isExpanded; + bool isExpanded = false; + // late bool isExpanded; @override void initState() { - super.initState(); - isExpanded = - widget.isExpanded; // Initialize isExpanded based on widget's property + super.initState(); // Initialize isExpanded based on widget's property } @override @@ -312,11 +328,8 @@ class _CustomExpandableTileState extends State { child: Column( children: [ GestureDetector( - onTap: () { - setState(() { - isExpanded = !isExpanded; - }); - }, + onTap: () => widget.onTap(widget.index), + child: Container( decoration: BoxDecoration( color: widget.titleBackgroundColor, @@ -335,7 +348,7 @@ class _CustomExpandableTileState extends State { ), ), Icon( - isExpanded + widget.isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, color: Colors.white, @@ -350,8 +363,8 @@ class _CustomExpandableTileState extends State { duration: Duration(milliseconds: 300), curve: Curves.easeInOut, width: double.infinity, - height: isExpanded ? myheight * 0.52 : 0, - child: isExpanded + height: widget.isExpanded ? myheight * 0.52 : 0, + child: widget.isExpanded ? SingleChildScrollView( child: Container( decoration: BoxDecoration( diff --git a/lib/presentation/Screens/auth_verification/create_new_pw.dart b/lib/presentation/Screens/auth_verification/create_new_pw.dart index 1fa3a802..2e63faa1 100644 --- a/lib/presentation/Screens/auth_verification/create_new_pw.dart +++ b/lib/presentation/Screens/auth_verification/create_new_pw.dart @@ -205,7 +205,31 @@ class _CreateNewPwState extends State { padding: const EdgeInsets.all(24.0), child: Column( children: [ + // SizedBox(height: screenHeight / 7), + Align( + alignment: Alignment.topRight, + child: GestureDetector( + onTap: () { + context.go('/editProfile'); + }, + child: Container( + margin: EdgeInsets.only(top: 16,left: 16,bottom: 16,right: 1), // Add margin for positioning + width: 30, // Circle diameter + height: 30, + decoration: BoxDecoration( + color: Colors.grey[300], // Circle color + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + size: 20, // Icon size + color: Colors.white, // Icon color + ), + ), + ), + ), SizedBox(height: screenHeight / 6), + Text( // "Create New Password", AppLocalizations.of(context)!.create_new_password, diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 3682dcb4..291e7e1d 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -1,15 +1,20 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart'; import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import '../filters/search_filter_helper.dart'; -class ChartScreen1 extends StatefulWidget { +class ChartScreen1 extends ConsumerStatefulWidget { final String dataSets; final String bgColor; final String mainTopic; final String title; + final String? keyParam; // Nullable String const ChartScreen1({ Key? key, @@ -17,18 +22,20 @@ class ChartScreen1 extends StatefulWidget { required String this.bgColor, required this.mainTopic, required this.title, + required this.keyParam, }); @override - _ChartScreen1State createState() => _ChartScreen1State(); + ConsumerState createState() => _ChartScreen1State(); } -class _ChartScreen1State extends State { +class _ChartScreen1State extends ConsumerState { List> selectedFiltersStorage = []; final GlobalKey _scaffoldKey = GlobalKey(); final ApiService apiService = ApiService(); bool isLoading = true; List isChartData = []; List nonChartData = []; + List filterDataSet = []; List filterData = []; List chartsData = []; List tabFilteredChartData = []; @@ -50,7 +57,8 @@ class _ChartScreen1State extends State { final String bgColor = widget.bgColor; print(' bgColor $bgColor'); // fetchChartData(widget.dataSets); - fetchChartData(widget.dataSets).then((_) { + final locale = ref.read(localeProvider); + fetchChartData(widget.dataSets, locale?.languageCode ?? 'en').then((_) { if (_tabsData.isNotEmpty) { // Call onTabSelected for the first tab onTabSelected(_tabsData[0]['id']); @@ -74,6 +82,7 @@ class _ChartScreen1State extends State { }); _scrollToIndex(_activeTabIndex); onTabSelected(_tabsData[_activeTabIndex]['id']!); // Pass the tab's id + print("TABFiltered Data: $filterData"); } } @@ -87,9 +96,39 @@ class _ChartScreen1State extends State { } } + // void processChartData(chartsData) { + // // Group data by 'kpi' + // Map>> groupedData = {}; + // for (var chart in chartsData) { + // String kpi = chart['kpi'] ?? ''; + // if (!groupedData.containsKey(kpi)) { + // groupedData[kpi] = []; + // } + // groupedData[kpi]!.add(chart); + // } + // + // // Format 'kpi' values for _tabs + // List> _tabs = groupedData.keys.map((kpi) { + // String name = kpi + // .split('_') // Split by underscore + // .map( + // (word) => word[0].toUpperCase() + word.substring(1)) // Capitalize + // .join(' '); // Join words with space + // + // return {'id': kpi, 'name': name}; + // }).toList(); + // + // print('Grouped Data: $groupedData'); + // print('Tabs: $_tabs'); + // setState(() { + // _tabsData = _tabs; + // }); + // } + void processChartData(chartsData) { // Group data by 'kpi' Map>> groupedData = {}; + for (var chart in chartsData) { String kpi = chart['kpi'] ?? ''; if (!groupedData.containsKey(kpi)) { @@ -98,30 +137,52 @@ class _ChartScreen1State extends State { groupedData[kpi]!.add(chart); } - // Format 'kpi' values for _tabs - List> _tabs = groupedData.keys.map((kpi) { - String name = kpi + // Format 'kpi' values for _tabs with tab_heading + List> _tabs = groupedData.entries.map((entry) { + String kpi = entry.key; + + // Extract tab_heading from the first chart in the grouped list + String tabHeading = entry.value.isNotEmpty + ? entry.value.first['tab_heading'] ?? 'Unknown' + : 'Unknown'; + + String formattedKpi = kpi .split('_') // Split by underscore - .map( - (word) => word[0].toUpperCase() + word.substring(1)) // Capitalize + .map((word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1) + : '') // Capitalize .join(' '); // Join words with space - return {'id': kpi, 'name': name}; + return {'id': kpi, 'name': tabHeading}; }).toList(); print('Grouped Data: $groupedData'); print('Tabs: $_tabs'); + setState(() { _tabsData = _tabs; }); } - Future fetchChartData(String dataSets) async { - var data = await apiService.fetchChartData(dataSets); + Future fetchChartData(String dataSets, locale) async { + var data = await apiService.fetchChartData(dataSets, locale); + + if (data.containsKey('filterData')) { + var filterData = data['filterData']; + filterDataSet = filterData.entries + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList(); + print("filterDataf1:- $filterData"); + print("filterDataf11:- $filterDataSet"); + } else { + filterDataSet = data['filterData'] ?? []; + print('filterData not found!'); + } + setState(() { isChartData = data['isChartData'] ?? []; nonChartData = data['nonChartData'] ?? []; - filterData = data['filterData'] ?? []; + // filterData = data['filterData'] ?? []; originalChartsData = List.from(isChartData); // Store original data originalCardData = List.from(nonChartData); @@ -133,17 +194,19 @@ class _ChartScreen1State extends State { print('chartsData :- $chartsData'); print('cardData :- $cardData'); - print('filterData :- $filterData'); + // print('filterData :- $filterData'); isLoading = false; }); - print(chartsData); - print(cardData); + print('chartsData -$chartsData'); + print('cardData -$cardData'); } void applyFilters(BuildContext context, List filters, List data, List dataCard, List selectedFilters) { - print("Selected Filters before applying: $selectedFilters"); - // Check if all filter_data is empty + print("applyFilters called"); + print("Selected ApplyFilters: $selectedFilters"); + print("Selected data: $data"); + if (selectedFilters.every((filter) => filter['filter_data'].isEmpty)) { setState(() { chartsData = @@ -157,8 +220,9 @@ class _ChartScreen1State extends State { // Loop through each chart data in the `data` list List filteredData = []; + Set addedChartIds = {}; // Track unique chart identifiers for (var chart in data) { - // Extract response data for filtering + final groupBy = chart['group_by']; List response = chart['response'] ?? []; // Filter the response based on selected filters @@ -171,20 +235,31 @@ class _ChartScreen1State extends State { final filterValues = filter['filter_data']; // Ensure the filter_key exists in the `ObsKey` and check if it matches any of the `filter_data` - if (obsKey.containsKey(filterKey)) { + if (groupBy == filterKey && obsKey.containsKey(filterKey)) { final obsKeyValue = obsKey[filterKey]?.toString(); return filterValues.isEmpty || filterValues.contains(obsKeyValue); } - return false; + + if (filterKey == 'TIME_PERIOD' && obsKey.containsKey('TIME_PERIOD')) { + final timePeriodValue = obsKey['TIME_PERIOD']?.toString(); + return filterValues.isEmpty || + filterValues.contains(timePeriodValue); + } + + return true; }); }).toList(); // If any data matches the filter, add the whole chart data object - if (chartFilteredData.isNotEmpty) { + // Add filtered chart only once + if (chartFilteredData.isNotEmpty && + !addedChartIds.contains(chart['chart_heading'])) { filteredData.add({ - ...chart, // Include all other properties of the chart object - 'response': chartFilteredData, // Only include filtered response data + ...chart, + 'response': chartFilteredData, }); + addedChartIds + .add(chart['chart_heading']); // Track by a unique identifier } } @@ -224,7 +299,7 @@ class _ChartScreen1State extends State { // Update the chartsData with the filtered data setState(() { chartsData = filteredData; - cardData = filteredCardData; + cardData = filteredCardData; // Adjust this part as needed }); print("Filtered Data: $filteredData"); @@ -271,7 +346,10 @@ class _ChartScreen1State extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Filters', + context.translate( + 'Filters', + 'المرشحات', + ), style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -357,7 +435,12 @@ class _ChartScreen1State extends State { setState(() {}); Navigator.pop(context); }, - child: Text('OK'), + child: Text( + context.translate( + 'OK', + 'نعم', + ), + ), ), ], ); @@ -428,7 +511,10 @@ class _ChartScreen1State extends State { selectedFiltersStorage.clear(); Navigator.pop(context); }, - child: Text('Clear'), + child: Text(context.translate( + 'Clear', + 'واضح', + )), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, ), @@ -447,7 +533,10 @@ class _ChartScreen1State extends State { // Save the selected filters to storage after applying selectedFiltersStorage = List.from(selectedFilters); }, - child: Text('Apply Filter'), + child: Text(context.translate( + 'Apply Filter', + 'تطبيق الفلتر', + )), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, ), @@ -466,6 +555,28 @@ class _ChartScreen1State extends State { void onTabSelected(String tabId) { print('Selected Tab: $tabId'); selectedFiltersStorage.clear(); + // Filter the filterDataSet + filterData = filterDataSet + .where((item) => item['key'] == tabId) + .map((item) => item['value']) + .where((item) => item != null && item is Iterable) + .expand((item) => item) + .toList(); + + for (var item in filterData) { + if (item['filter_key'] == 'TIME_PERIOD') { + // Convert the values to integers, sort them, and convert back to strings + List timePeriodData = item['filter_data'] + .map((e) => int.parse(e.toString())) // Convert to int + .toList(); + timePeriodData.sort((a, b) => a.compareTo(b)); // Sort numerically + + // Optionally, convert sorted integers back to strings if necessary + item['filter_data'] = timePeriodData.map((e) => e.toString()).toList(); + } + } + + print("TABFiltered Data: $filterData"); setState(() { chartsData = originalChartsData; cardData = originalCardData; @@ -507,22 +618,38 @@ class _ChartScreen1State extends State { // For example, you can update the chart data or display the results } - double calculateAspectRatio(int itemCount) { - // Modify the logic based on your layout requirements - if (itemCount <= 2) { - return 190.5 / 180; - } else if (itemCount == 3) { - return 180.0 / 300; - } else if (itemCount == 4) { - return 190.5 / 180; + // double calculateAspectRatio(int itemCount) { + // // Modify the logic based on your layout requirements + // if (itemCount <= 2) { + // return 190.5 / 180; + // } else if (itemCount == 3) { + // return 180.0 / 250; + // } else if (itemCount == 4) { + // return 190.5 / 180; + // } else { + // return 180.0 / 180; // Default for more items + // // return 190.5 / 180; + // } + // } + + double calculateAspectRatio(int crossAxisCount, List cardData) { + // Determine a default aspect ratio based on the most common chart type in the list + if (cardData.any((item) => + item['chart_type'] == 'total' || item['chart_type'] == 'average')) { + return crossAxisCount == 2 ? 1.0 : 0.7; // Larger Content + // return crossAxisCount == 2 ? 1.5 : 0.9; } else { - return 180.0 / 260; // Default for more items - // return 190.5 / 180; + return crossAxisCount == 2 ? 1.5 : 0.9; + // Shorter Content } } @override Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchChartData(widget.dataSets, localeCode); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; final color = @@ -536,11 +663,18 @@ class _ChartScreen1State extends State { leading: IconButton( icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () { - context.go('/uaenumbers'); + if (widget.keyParam == 'home') { + context.go('/myhomepage'); + } else { + context.go('/uaenumbers'); + } }, ), title: Text( - 'UAE Numbers', + context.translate( + 'UAE Numbers', + 'أرقام الإمارات', + ), style: TextStyle(color: Colors.white), ), ), @@ -609,7 +743,10 @@ class _ChartScreen1State extends State { Row( children: [ Text( - 'Bookmark', + context.translate( + 'Bookmark', + 'إشارة مرجعية', + ), style: const TextStyle( fontSize: 16, color: Colors.white, @@ -624,7 +761,10 @@ class _ChartScreen1State extends State { Row( children: [ Text( - 'Share', + context.translate( + 'Share', + 'يشارك', + ), style: const TextStyle( fontSize: 16, color: Colors.white, @@ -653,33 +793,37 @@ class _ChartScreen1State extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ // Left arrow button - _buildArrowButton( - onPressed: _activeTabIndex > 0 ? _scrollLeft : null, - icon: Icons.arrow_back_ios_new, - ), + if (_tabsData.length > 1) + _buildArrowButton( + onPressed: + _activeTabIndex > 0 ? _scrollLeft : null, + icon: Icons.arrow_back_ios_new, + ), // Tabs with horizontal scroll - Expanded( - child: SingleChildScrollView( - controller: _scrollController, - scrollDirection: Axis.horizontal, - child: Row( - children: - List.generate(_tabsData.length, (index) { - return _buildTab( - _tabsData[index], - isActive: index == _activeTabIndex, - ); - }), + if (_tabsData.length > 1) + Expanded( + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + child: Row( + children: + List.generate(_tabsData.length, (index) { + return _buildTab( + _tabsData[index], + isActive: index == _activeTabIndex, + ); + }), + ), ), ), - ), // Right arrow button - _buildArrowButton( - onPressed: _activeTabIndex < _tabsData.length - 1 - ? _scrollRight - : null, - icon: Icons.arrow_forward_ios, - ), + if (_tabsData.length > 1) + _buildArrowButton( + onPressed: _activeTabIndex < _tabsData.length - 1 + ? _scrollRight + : null, + icon: Icons.arrow_forward_ios, + ), ], ), // Expanded( @@ -699,25 +843,32 @@ class _ChartScreen1State extends State { physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: cardData.length == 2 - ? 2 - : cardData.length == 3 - ? 3 - : cardData.length == 4 - ? 2 - : 3, // Default to 3 if more than 4 items // 2 cards per row - crossAxisSpacing: 2, - mainAxisSpacing: 5, - childAspectRatio: - calculateAspectRatio(cardData.length)), + crossAxisCount: cardData.length == 2 + ? 2 + : cardData.length == 3 + ? 3 + : cardData.length == 4 + ? 2 + : 3, // Default to 3 if more than 4 items // 2 cards per row + crossAxisSpacing: 2, + mainAxisSpacing: 5, + childAspectRatio: + calculateAspectRatio(cardData.length, cardData), + ), itemBuilder: (context, index) { final item = cardData[index]; print('item item item $item'); final chart_type = item['chart_type']; final chart_heading = item['chart_heading']; + final card_logo = item['card_logo']; final data = apiService.processNonChartData(item); print('processNonChartData'); + double cardHeight = (chart_type == 'totals' || + chart_type == 'averages') + ? 180.0 + : 130.0; + if (chart_type == 'total') { return Card( margin: const EdgeInsets.all(10), @@ -725,21 +876,41 @@ class _ChartScreen1State extends State { borderRadius: BorderRadius.circular(12), ), elevation: 4, - child: Padding( + child: Container( + height: cardHeight, padding: const EdgeInsets.all(10.0), child: Column( + mainAxisSize: MainAxisSize + .min, // Adjust card height based on content mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.public, - color: Color(0xFF90B0D5), size: 30), + // const Icon(Icons.public, + // color: Color(0xFF90B0D5), size: 30), + Image.network( + card_logo ?? '', + width: 30, + height: 30, + errorBuilder: + (context, error, stackTrace) { + return Icon(Icons.public, + color: Color(0xFF90B0D5), + size: 30); // Fallback icon + }, + ), const SizedBox(height: 3), - Text( - '${chart_heading ?? 'NA'}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Colors.black87, + Flexible( + fit: FlexFit.loose, + child: FittedBox( + // fit: BoxFit.contain, + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), ), ), Text( @@ -747,13 +918,19 @@ class _ChartScreen1State extends State { style: const TextStyle( fontSize: 11, color: Colors.grey), ), - Text( - apiService.formatAmount( - data['lastYearValue']), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['lastYearValue']), + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), ), ), SizedBox( @@ -764,19 +941,27 @@ class _ChartScreen1State extends State { 1, // Divider line thickness ), ), - Text( - apiService.formatAmount( - data['secondLastYearValue']), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFFD83731), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['secondLastYearValue']), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFFD83731), + ), + ), ), ), Text( '(${data['secondLastYear'] ?? 'NA'})', style: const TextStyle( - fontSize: 11, color: Colors.grey), + fontSize: 8, + fontWeight: FontWeight.w500, + color: Colors.grey), ), ], ), @@ -789,13 +974,24 @@ class _ChartScreen1State extends State { borderRadius: BorderRadius.circular(12), ), elevation: 4, - child: Padding( + child: Container( + height: cardHeight, padding: const EdgeInsets.all(16.0), child: Column( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.analytics, - color: Color(0xFF90B0D5), size: 30), + Image.network( + card_logo ?? '', + width: 30, + height: 30, + errorBuilder: + (context, error, stackTrace) { + return Icon(Icons.public, + color: Color(0xFF90B0D5), + size: 30); // Fallback icon + }, + ), const SizedBox(height: 5), Text( '${chart_heading ?? 'NA'}', @@ -811,12 +1007,133 @@ class _ChartScreen1State extends State { style: const TextStyle( fontSize: 11, color: Colors.grey), ), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + '${data['roundedAverage'] ?? 'NA'}', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), + ), + ), + ], + ), + ), + ); + } else if (chart_type == 'totals') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Container( + height: cardHeight, + // height: maxHeight, + // padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 10), + child: Column( + // mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.public, + color: Color(0xFF90B0D5), size: 30), + const SizedBox(height: 3), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + // fit: BoxFit.contain, + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + ), + ), Text( - '${data['roundedAverage'] ?? 'NA'}', + '(${data['lastYear'] ?? 'NA'})', style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), + fontSize: 11, color: Colors.grey), + ), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['lastYearValue']), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), + ), + ), + ], + ), + ), + ); + } else if (chart_type == 'averages') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Container( + // height: maxHeight, + // height: 200, + height: cardHeight, + padding: const EdgeInsets.all(5.0), + // padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + child: Column( + // mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.public, + color: Color(0xFF90B0D5), size: 30), + const SizedBox(height: 5), + Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + Text( + '(${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 11, color: Colors.grey), + ), + const SizedBox(height: 5), + Flexible( + fit: FlexFit.loose, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + apiService.formatAmount( + data['lastYearValue']), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), + ), ), ), ], diff --git a/lib/presentation/Screens/charts/services/api_service.dart b/lib/presentation/Screens/charts/services/api_service.dart index 31494a16..a99ecb50 100644 --- a/lib/presentation/Screens/charts/services/api_service.dart +++ b/lib/presentation/Screens/charts/services/api_service.dart @@ -13,13 +13,13 @@ class ChartData { class ApiService { static const String baseUrl = 'https://pb.venbait.in/api/getDataSet'; - Future> fetchChartData(String dataSets) async { + Future> fetchChartData(String dataSets, locale) async { List isChartData = []; List nonChartData = []; List originalChartsData = []; List originalCardData = []; - final url = Uri.parse('$baseUrl?dataset=$dataSets'); + final url = Uri.parse('$baseUrl?dataset=$dataSets&language=$locale'); try { final response = await http.get(url); @@ -43,7 +43,8 @@ class ApiService { return { 'isChartData': isChartData, 'nonChartData': nonChartData, - 'filterData': jsonData['filter_data'] + 'filterData': jsonData['new_filter_data'] + // 'filterData': jsonData['filter_data'] // 'originalChartsData': originalChartsData, // 'originalCardData': originalCardData, }; diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 5e24182b..7451e2a9 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -294,7 +294,7 @@ class ChartWidget extends StatelessWidget { aspectRatio: 1.5, child: BarChart( BarChartData( - alignment: BarChartAlignment.start, + alignment: BarChartAlignment.spaceEvenly, barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.8), @@ -324,6 +324,17 @@ class ChartWidget extends StatelessWidget { double value = toY - fromY; String groupName = groupNames[i]; + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } // Get the color for the current group Color groupColor = _getColorForGroup( i); // Replace with your color logic @@ -337,8 +348,7 @@ class ChartWidget extends StatelessWidget { fontSize: 14), // Circle color ), TextSpan( - text: - '$groupName - ${value.toStringAsFixed(0)}\n', + text: '$groupName - $formattedValue\n', style: TextStyle(color: Colors.white, fontSize: 12), ), @@ -433,147 +443,618 @@ class ChartWidget extends StatelessWidget { ], ); case 'line_trend': - case 'line_trend': - return LineChart(LineChartData( - lineTouchData: lineTouchData1(), - gridData: gridData(), - titlesData: titlesData1(), - borderData: borderData(), - lineBarsData: lineBarsData(chartData), - minX: 1970, // Adjust based on your data range - maxX: 2025, // Adjust based on your data range - )); - case 'fl_multi_bar': - return BarChart( - BarChartData( - alignment: BarChartAlignment.spaceAround, - maxY: 100, // Maximum value for the bar - barGroups: _buildBarGroups(), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - return Text( - '${value.toInt()}%', - style: TextStyle(fontSize: 12), - ); - }, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - return Text( - "Bar ${value.toInt()}", - style: TextStyle(fontSize: 12), - ); - }, - ), - ), - topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - rightTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], ), - borderData: FlBorderData( - show: false, + ); + } + + // Find the maximum year and calculate the range for the last 5 years + int maxYear = years.reduce((a, b) => a > b ? a : b); + int minYear = maxYear - 5; + + // Filter chart data to only include entries within the last 5 years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return year >= minYear && year <= maxYear; + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = + lineBarsData(filteredData, groupByValues, groupByKey); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, ), - barTouchData: BarTouchData(enabled: true), - gridData: FlGridData(show: false), + textAlign: TextAlign.center, ), - ); + SizedBox(height: 10), + // Chart + Expanded( + child: LineChart(LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ))), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 12, + runSpacing: 8, + children: groupByValues.map((group) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + color: groupColorMap[group], + ), + SizedBox(width: 6), + Text( + group, + style: TextStyle(fontSize: 14), + ), + ], + ); + }).toList(), + ), + ), + ]); + case 'line_trend_2': + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], + ), + ); + } + + // Find the maximum year and calculate the range for the last 5 years + int maxYear = years.reduce((a, b) => a > b ? a : b); + int minYear = maxYear - 5; + + // Filter chart data to only include entries within the last 5 years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return year >= minYear && year <= maxYear; + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = + lineBarsData2(filteredData, groupByValues, groupByKey); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: LineChart(LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData2(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ))), + ]); + case 'bar_chart': + // Extract groupBy values and their corresponding y-axis values + List xAxisData = []; + List yAxisData = []; + + for (var entry in chartData['response']) { + var xValue = entry['ObsKey'][groupByKey]; + var yValue = entry['ObsValue']['Value']; + if (xValue != null && yValue != null) { + xAxisData.add(xValue.toString()); + yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); + } + } + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + : 10, + barTouchData: BarTouchData(enabled: true), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 + : 1), + getTitlesWidget: (value, meta) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(xAxisData[value.toInt()]), + ); + } + return Container(); + }, + reservedSize: 40, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide right titles + ), + ), + gridData: FlGridData(show: false), + borderData: FlBorderData(show: false), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: Colors.blueAccent, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + ), + ), + ), + )) + ]); + case 'fl_multi_bar': + double _calculateChartWidth(dynamic chartData) { + int totalBars = chartData['response']?.length ?? 0; + const double barWidth = 30; // Width for each bar, including spacing + return totalBars * barWidth; // Calculate total chart width + } + // Group crops by CROP_TYPE + Map> groupedCrops = {}; + int touchedGroupIndex = -1; + // Iterate over the chartData to group crops by CROP_TYPE + for (var item in chartData['response']) { + String crop, cropType; + if (chartData['dataset'] == 'health_services') { + crop = item['ObsKey']['SECTOR']; + cropType = item['ObsKey'][groupByKey]; + } else { + crop = item['ObsKey']['CROP']; + cropType = item['ObsKey'][groupByKey]; + } + + if (groupedCrops.containsKey(cropType)) { + groupedCrops[cropType]!.add(crop); + } else { + groupedCrops[cropType] = [crop]; + } + } + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, // Enable horizontal scrolling + child: SizedBox( + width: _calculateChartWidth( + chartData), // Dynamically calculate the chart width + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: + _calculateMaxY(chartData), // Dynamically calculate max Y + barGroups: _buildHorizontalRotateBarGroups( + chartData, groupByValues), // Build bar groups + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 20, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = + groupByValues.elementAt(value.toInt()); + return Transform.rotate( + angle: + -0.5, // Rotation in radians (~ -30 degrees) + child: Text( + title, + style: const TextStyle(fontSize: 12), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipHorizontalAlignment: FLHorizontalAlignment.center, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0 || touchedGroupIndex == -1) { + return null; // Don't show the tooltip if the value is 0 or there's no touch + } + if (groupIndex == touchedGroupIndex) { + // Get the group label dynamically + String groupLabel = + groupByValues.elementAt(groupIndex); + + // Fetch the crop for the current group from groupedCrops + String cropType = groupByValues.elementAt(groupIndex); + print('GrpcropType: $cropType'); + // String crop = groupedCrops[cropType]![rodIndex]; + + // Safely fetch crop with null check + List? crops = groupedCrops[cropType]; + String crop; + if (crops != null && rodIndex < crops.length) { + crop = crops[rodIndex]; + } else { + print( + 'Crop is null or index out of bounds for cropType: $cropType and rodIndex: $rodIndex'); + crop = cropType; // Fallback to cropType + } + + // print('groupedCrops1: $groupedCrops - $rodIndex'); + // print( + // 'Groupcrop: $crop'); + double value = rod.toY; + + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } + // print('Cropvaluevalue: $groupLabel\n$crop $value $formattedValue'); + + return BarTooltipItem( + '$groupLabel\n$crop', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + // text: ' Value: ${rod.toY}', + text: ' Value: $formattedValue', + style: const TextStyle( + color: Colors.yellow, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + return null; + }, + ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + touchedGroupIndex = -1; // Reset if no interaction + // }); + } + }, + ), + gridData: FlGridData(show: false), + ), + ), + ), + )) + ]); + + case 'horizontal_rotate': + // Group crops by CROP_TYPE + Map> groupedCrops = {}; + + // Iterate over the chartData to group crops by CROP_TYPE + for (var item in chartData['response']) { + String crop = item['ObsKey']['CROP']; + String cropType = item['ObsKey']['CROP_TYPE']; + + if (groupedCrops.containsKey(cropType)) { + groupedCrops[cropType]!.add(crop); + } else { + groupedCrops[cropType] = [crop]; + } + } + int touchedGroupIndex = -1; + + int rotationTurns = 1; + print('Grouped Crops: $groupedCrops'); + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + maxY: 400000, + rotationQuarterTurns: + rotationTurns, // Adjust maxY as needed based on your data + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipHorizontalAlignment: FLHorizontalAlignment.center, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0 || touchedGroupIndex == -1) { + return null; // Don't show the tooltip if the value is 0 or there's no touch + } + + if (groupIndex == touchedGroupIndex) { + // print('Group Index: $groupIndex, Group : $group'); + + // Get the group label dynamically + String groupLabel = groupByValues.elementAt(groupIndex); + + // Fetch the crop for the current group from groupedCrops + String cropType = groupByValues.elementAt(groupIndex); + String crop = groupedCrops[cropType]![rodIndex]; + double value = rod.toY; + + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value + .toStringAsFixed(0); // for values smaller than 1000 + } + + return BarTooltipItem( + '$groupLabel\n$crop', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + text: ' Value: $formattedValue', + style: const TextStyle( + color: Colors.yellow, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + return null; + }, + ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + touchedGroupIndex = -1; // Reset if no interaction + // }); + } + }, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 20, + interval: 100000, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: const TextStyle(fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 20, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = groupByValues.elementAt(value.toInt()); + return Transform.rotate( + angle: -0.5, // Rotation in radians (~ -30 degrees) + child: Text( + title, + style: const TextStyle(fontSize: 12), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.grey), + ), + ), + gridData: FlGridData( + show: false, + drawVerticalLine: true, + verticalInterval: 1, + horizontalInterval: 100000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + ), + barGroups: + _buildHorizontalRotateBarGroups(chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, + ), + )) + ]); default: return Center(child: Text('Unknown chart type')); } } -// Function to create LineChartBarData for Males and Females - List lineBarsData(dynamic chartData) { - List lineBars = []; - - // Filter data for males and females - List> maleData = []; - List> femaleData = []; - - // Separate the data based on gender - for (var entry in chartData['response']) { - if (entry['ObsKey']['GENDER'] == 'M') { - maleData.add(entry); - } else if (entry['ObsKey']['GENDER'] == 'F') { - femaleData.add(entry); - } - } - - // Prepare data for Male - List maleSpots = maleData.map((entry) { - double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); - double yValue = double.parse(entry['ObsValue']['Value']); - return FlSpot(xValue, yValue); - }).toList(); - - // Prepare data for Female - List femaleSpots = femaleData.map((entry) { - double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); - double yValue = double.parse(entry['ObsValue']['Value']); - return FlSpot(xValue, yValue); - }).toList(); - - // Add the Male line (Blue color) - lineBars.add( - LineChartBarData( - spots: maleSpots, - isCurved: true, - color: Colors.blue, - barWidth: 3, - belowBarData: BarAreaData(show: false), - ), - ); - - // Add the Female line (Pink color) - lineBars.add( - LineChartBarData( - spots: femaleSpots, - isCurved: true, - color: Colors.pink, - barWidth: 3, - belowBarData: BarAreaData(show: false), - ), - ); - - return lineBars; - } - -// Sample implementation for other chart details like titles, grid, etc. - LineTouchData lineTouchData1() { - return LineTouchData( - touchTooltipData: LineTouchTooltipData( - // tooltipBgColor: Colors.blueAccent, - ), - // touchCallback: (LineTouchResponse touchResponse) {}, - handleBuiltInTouches: true, - ); - } - - FlGridData gridData() { - return FlGridData( - show: true, - drawVerticalLine: true, - getDrawingHorizontalLine: (value) => - FlLine(color: Colors.grey, strokeWidth: 1), - getDrawingVerticalLine: (value) => - FlLine(color: Colors.grey, strokeWidth: 1), - ); - } - - FlTitlesData titlesData1() { +// Start Of line trend chart + FlTitlesData titlesData2(Set xValues) { return FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, + reservedSize: 40, getTitlesWidget: (value, meta) { + // Format values as millions (M) + // String formattedValue = (value / 1000000).toStringAsFixed(1) + 'M'; return Text( - value.toInt().toString(), + value.toStringAsFixed(1), style: TextStyle(color: Colors.black, fontSize: 12), ); }, @@ -582,22 +1063,244 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, + interval: 1, // Ensure each year is shown only once getTitlesWidget: (value, meta) { - return Text( - value.toInt().toString(), - style: TextStyle(color: Colors.black, fontSize: 12), - ); + if (xValues.contains(value)) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } }, ), ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide right titles + ), + ); + } + + List lineBarsData2(List filteredData, + Set groupByValues, String groupByKey) { + List lineBars = []; + print('filteredData $filteredData'); + print('filteredData222 $groupByValues'); + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + + // Create a map to assign colors to each group in order + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + } + + print('Group-Color Map: $groupColorMap'); + + // Create a map to store the values by year and gender (M and F) + Map> yearGenderMap = {}; + + // Populate the yearGenderMap with male and female values + for (var entry in filteredData) { + int year = int.parse(entry['ObsKey']['TIME_PERIOD']); + String gender = entry['ObsKey']['GENDER']; + double value = double.parse(entry['ObsValue']['Value'].toString()); + + if (!yearGenderMap.containsKey(year)) { + yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; + } + + // Assign value based on gender + if (gender == 'M') { + yearGenderMap[year]!['M'] = value; + } else if (gender == 'F') { + yearGenderMap[year]!['F'] = value; + } + } + + // Calculate the ratio (M/F) * 100 for each year + List spots = []; + yearGenderMap.forEach((year, genderMap) { + if (genderMap['M'] != 0.0 && genderMap['F'] != 0.0) { + double ratio = (genderMap['M']! / genderMap['F']!) * 100; + spots.add(FlSpot(year.toDouble(), ratio)); + } + }); + + // Add a line for the ratio data + lineBars.add( + LineChartBarData( + spots: spots, + isCurved: true, + color: Colors.blue, // Set color for the ratio line + barWidth: 3, + isStrokeCapRound: true, + belowBarData: BarAreaData(show: true), + ), + ); + + return lineBars; + } + + List lineBarsData(List filteredData, + Set groupByValues, String groupByKey) { + List lineBars = []; + print('filteredData $filteredData'); + print('filteredData222 $groupByValues'); + final List uniqueColors = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + + // Create a map to assign colors to each group in order + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + } + + print('Group-Color Map: $groupColorMap'); + // Iterate through each group and generate line data + for (String group in groupByValues) { + List spots = filteredData + .where((entry) => entry['ObsKey'][groupByKey] == group) + .map((entry) { + double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); + double yValue = double.parse(entry['ObsValue']['Value']); + return FlSpot(xValue, yValue); + }).toList(); + + // Add a line for this group + lineBars.add( + LineChartBarData( + spots: spots, + isCurved: true, + color: groupColorMap[group], + barWidth: 3, + isStrokeCapRound: true, + belowBarData: BarAreaData(show: false), + ), + ); + } + + return lineBars; + } + +// Helper functions for chart styles + LineTouchData lineTouchData1() { + return LineTouchData( + touchTooltipData: LineTouchTooltipData(), + handleBuiltInTouches: true, + ); + } + + FlGridData gridData() { + return FlGridData( + show: false, + drawVerticalLine: true, + getDrawingHorizontalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + getDrawingVerticalLine: (value) => + FlLine(color: Colors.grey, strokeWidth: 1), + ); + } + + FlTitlesData titlesData1(Set xValues) { + return FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) { + // Format values as millions (M) + String formattedValue; + + if (value % 10 == 0) { +// Check if the value is in the millions or thousands range + if (value >= 1000000) { + formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; + } else if (value >= 1000) { + formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; + } else { + formattedValue = value.toStringAsFixed( + 0); // No decimals for values less than 1000 + } + + // Skip rendering if the formatted value is the same as the last one + if (_lastFormattedValue == formattedValue) { + return const SizedBox.shrink(); // Empty widget for duplicates + } + // Update the last formatted value for the next comparison + _lastFormattedValue = formattedValue; + + print('formattedValueLine - $formattedValue'); + return Text(formattedValue, style: TextStyle(fontSize: 10)); + } + return const SizedBox.shrink(); + + // return Text( + // formattedValue, + // style: TextStyle(color: Colors.black, fontSize: 12), + // ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, // Ensure each year is shown only once + getTitlesWidget: (value, meta) { + if (xValues.contains(value)) { + return Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } + }, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide right titles + ), ); } FlBorderData borderData() { return FlBorderData( - show: true, border: Border.all(color: Colors.black, width: 1)); + show: true, + border: Border( + bottom: BorderSide(color: Colors.black, width: 1), // Show bottom border + left: BorderSide(color: Colors.black, width: 1), // Show left border + top: BorderSide.none, // Hide top border + right: BorderSide.none, // Hide right border + ), + ); } + // End Of line trend chart + // Define the list of unique colors final List uniqueColors = [ Color(0xFF648CBA), @@ -642,7 +1345,7 @@ class ChartWidget extends StatelessWidget { List timePeriods = groupedData.values.first.keys.toList(); print('timePeriods $timePeriods'); - double barWidth = calculateBarWidth(context, timePeriods.length); + // double barWidth = calculateBarWidth(context, timePeriods.length); // For each time period, generate a BarChartGroupData for (int i = 0; i < timePeriods.length; i++) { @@ -678,8 +1381,8 @@ class ChartWidget extends StatelessWidget { BarChartRodData( toY: currentToY, // Use the accumulated `currentToY` value rodStackItems: rodStackItems, // Add the stacked items - // width: 20, - width: barWidth, + width: 20, + // width: barWidth, borderRadius: BorderRadius.zero, color: Colors .transparent, // This will act as a container for stacked items @@ -708,64 +1411,149 @@ class ChartWidget extends StatelessWidget { } // Function to dynamically set left axis titles (values) + String? _lastFormattedValue; Widget _generateLeftTitles(double value, TitleMeta meta) { + String formattedValue; + if (value % 1000 == 0) { - return Text(value.toStringAsFixed(0), style: TextStyle(fontSize: 10)); +// Check if the value is in the millions or thousands range + if (value >= 1000000) { + formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; + } else if (value >= 1000) { + formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; + } else { + formattedValue = + value.toStringAsFixed(0); // No decimals for values less than 1000 + } + + // Skip rendering if the formatted value is the same as the last one + if (_lastFormattedValue == formattedValue) { + return const SizedBox.shrink(); // Empty widget for duplicates + } + // Update the last formatted value for the next comparison + _lastFormattedValue = formattedValue; + + print('formattedValue - $formattedValue'); + return Text(formattedValue, style: TextStyle(fontSize: 10)); } - return Container(); + return const SizedBox.shrink(); + // return Container(); } - /// Build the data for the bar groups - List _buildBarGroups() { - return [ - BarChartGroupData( - x: 1, // Identifier for the group - barRods: [ - BarChartRodData( - toY: 40, // Value for the first part of the stack - color: Colors.blue, - width: 10, - ), - BarChartRodData( - toY: 60, // Value for the second part of the stack - color: Colors.green, - width: 10, - ), - ], - ), - BarChartGroupData( - x: 2, - barRods: [ - BarChartRodData( - toY: 30, - color: Colors.orange, - width: 10, - ), - BarChartRodData( - toY: 70, - color: Colors.red, - width: 10, - ), - ], - ), - BarChartGroupData( - x: 3, - barRods: [ - BarChartRodData( - toY: 50, - color: Colors.purple, - width: 10, - ), - BarChartRodData( - toY: 50, - color: Colors.cyan, - width: 10, - ), - ], - ), - ]; + /// Start horizontal rotate and fl_multi_bar bar + // List _buildBarGroups(dynamic chartData) { + // String groupByKey = chartData['group_by']; + // if (groupByKey == null || chartData['response'] == null) { + // return []; + // } + // + // // Extract unique group_by values + // Set groupByValues = extractGroupByValues(chartData, groupByKey); + // + // // Create a map to hold grouped data + // Map>> groupedData = {}; + // for (var entry in chartData['response']) { + // String groupValue = entry['ObsKey'][groupByKey] ?? ''; + // if (groupValue.isNotEmpty) { + // groupedData.putIfAbsent(groupValue, () => []).add(entry); + // } + // } + // + // // Convert grouped data into BarChartGroupData + // List barGroups = []; + // int groupIndex = 0; + // + // groupedData.forEach((key, values) { + // int colorIndex = 0; // Track the color for each bar within the group + // + // List rods = values.map((entry) { + // double yValue = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; + // + // // Cycle through colors for each bar + // final barColor = uniqueColors[colorIndex % uniqueColors.length]; + // colorIndex++; + // + // return BarChartRodData( + // fromY: 0, + // toY: yValue, + // color: barColor, + // width: 20, // Adjust bar width for better visibility + // borderRadius: BorderRadius.circular(4), + // ); + // }).toList(); + // + // barGroups.add(BarChartGroupData( + // x: groupIndex, + // barRods: rods, + // showingTooltipIndicators: [0], + // )); + // groupIndex++; + // }); + // + // return barGroups; + // } + + double _calculateMaxY(dynamic chartData) { + // Dynamically calculate the maximum Y value + List response = chartData['response'] ?? []; + double maxY = 0; + + for (var entry in response) { + double value = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; + if (value > maxY) { + maxY = value; + } + } + + return maxY * 1.1; // Add 10% buffer for better visualization } + List _buildHorizontalRotateBarGroups( + dynamic chartData, Set groupByValues) { + List responseData = chartData['response']; + List barGroups = []; + int colorIndex = 0; + // Iterate through groupByValues and populate BarChartGroupData + for (int i = 0; i < groupByValues.length; i++) { + String groupValue = groupByValues.elementAt(i); + + // Filter data for the current group (grouping by CROP_TYPE) + List groupData = responseData.where((entry) { + return entry['ObsKey'][chartData['group_by']] == groupValue; + }).toList(); + + // Create BarChartRodData for each bar in the group + List barRods = groupData.map((data) { + // Ensure to retrieve and parse 'ObsValue' value (which should be a double) + double value = + double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; + + final barColor = uniqueColors[colorIndex % uniqueColors.length]; + colorIndex++; + + return BarChartRodData( + toY: value, // Use the parsed value + color: barColor, // Dynamic color + width: 20, + ); + }).toList(); + + // Add BarChartGroupData for the group + barGroups.add( + BarChartGroupData( + x: i, + barRods: barRods, + showingTooltipIndicators: + List.generate(barRods.length, (index) => index), + ), + ); + } + + return barGroups; + } + + /// End horizontal rotate and fl_multi_bar bar + @override Widget build(BuildContext context) { return buildChart(chartData, context); diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 29b2a978..20fb5f46 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -461,8 +461,16 @@ class LoginRoute extends HookConsumerWidget { final continueAsGuestBtn = SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: () => - context.go('/myhomepage'), + onPressed: () async { + final prefs = await SharedPreferences.getInstance(); + prefs.clear(); + final userId = 'guest'; + if (userId.isNotEmpty) { + await saveUserId(userId); + } + context.go('/myhomepage'); + //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + }, //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), style: ButtonStyle( shape: WidgetStatePropertyAll( diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index 260f7692..4c41728f 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -163,43 +163,21 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import '../../drawer_routes/custom_drawer_routes.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:http/http.dart' as http; -class MyHomePage extends StatefulWidget { +class MyHomePage extends ConsumerStatefulWidget { const MyHomePage({super.key}); @override - State createState() => _MyHomePageState(); + ConsumerState createState() => _MyHomePageState(); } -void handleInfoCardClick(BuildContext context, String data, Color color) { - // Handle navigation and pass dynamic data - print(data); - final dataSets = data; - if (dataSets != null) { - // Perform navigation - context.go('/chartScreen/$dataSets'); - } else { - // Show error if dataSet is null - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('No dataset available')), - ); - } - // final dataSets = data; - // print(dataSets); - // if (dataSets == 'hotels') { - // context.go('/chartScreen/$dataSets'); - // } else if (dataSets == 'divorces') { - // context.go('/chartScreen/$dataSets'); - // } else if (dataSets == 'marriages') { - // context.go('/Chart/$dataSets'); - // } -} - -class _MyHomePageState extends State { +class _MyHomePageState extends ConsumerState { @override Widget build(BuildContext context) { double myheight = MediaQuery.of(context).size.height; @@ -215,14 +193,14 @@ class _MyHomePageState extends State { } } -class EconomyStatsWidget extends StatefulWidget { +class EconomyStatsWidget extends ConsumerStatefulWidget { const EconomyStatsWidget({super.key}); @override - EconomyStatsState createState() => EconomyStatsState(); + ConsumerState createState() => EconomyStatsState(); } -class EconomyStatsState extends State { +class EconomyStatsState extends ConsumerState { List data = []; bool isLoading = true; @@ -230,13 +208,22 @@ class EconomyStatsState extends State { @override void initState() { super.initState(); - fetchData(); + final locale = ref.read(localeProvider); + fetchData(locale?.languageCode ?? 'en'); } - Future fetchData() async { + // @override + // void didChangeDependencies() { + // super.didChangeDependencies(); + // // Access the provider here + // final locale = ref.watch(localeProvider); + // fetchData(locale); // Pass the locale to the fetchData method + // } + + Future fetchData(locale) async { const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; try { - final response = await http.get(Uri.parse(baseUrl)); + final response = await http.get(Uri.parse(baseUrl + '?language=$locale')); if (response.statusCode == 200) { setState(() { data = json.decode(response.body); @@ -283,6 +270,10 @@ class EconomyStatsState extends State { } Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchData(localeCode); + }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; @@ -386,7 +377,8 @@ class EconomyStatsState extends State { children: _buildRows( [tileData], borderColor, - mainTopic['color_pattern']), + mainTopic['color_pattern'], + mainTopic['main_topic']), ), ), ], @@ -452,8 +444,8 @@ class EconomyStatsState extends State { // return rows; // } -List _buildRows( - List> tileData, Color borderColor, color_pattern) { +List _buildRows(List> tileData, Color borderColor, + String colorPattern, String mainTopic) { // tileData.sort((a, b) => // (a['data_set_list_order'] ?? 0).compareTo(b['data_set_list_order'] ?? 0),); @@ -479,8 +471,10 @@ List _buildRows( dataset: tile['data_set']!, bordercolor: borderColor, textcolor: borderColor, + colorPattern: colorPattern, backgroundColor: Colors.white, // Set background color for InfoCard + mainTopic: mainTopic, onTap: () {}, ), ); @@ -529,10 +523,12 @@ class RoundedCornerContainer extends StatelessWidget { class InfoCard extends StatelessWidget { final String title; + final String mainTopic; final String subtitle; final String value; final String dataset; final Color bordercolor; + final String colorPattern; final Color? textcolor; final VoidCallback onTap; final Color backgroundColor; @@ -544,9 +540,11 @@ class InfoCard extends StatelessWidget { required this.value, required this.dataset, required this.bordercolor, + required this.colorPattern, this.textcolor, required this.onTap, required this.backgroundColor, + required this.mainTopic, }) : super(key: key); @override @@ -554,11 +552,16 @@ class InfoCard extends StatelessWidget { double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; + print("colorPatternInfo -$colorPattern $mainTopic"); + final encodedMainTopic = Uri.encodeComponent(mainTopic); + final encodedTitle = Uri.encodeComponent(title); + final encodedKey = Uri.encodeQueryComponent('home'); + + return GestureDetector( onTap: () { - // Call handleInfoCardClick and pass the title and color - handleInfoCardClick( - context, dataset, bordercolor); // Pass 'title' to the function + context.go( + '/chartScreen/$dataset?bgColor=$colorPattern&mainTopic=$encodedMainTopic&title=$encodedTitle&key=$encodedKey'); }, child: Container( height: myheight / 12, diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 2ea5e160..6f5472c0 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -57,6 +58,11 @@ class _BaseScaffoldState extends ConsumerState { _checkUserId(); } + Future _getAppVersion() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return packageInfo.version; // Returns the app version + } + Future getUserId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('userId'); // Retrieve the userId @@ -183,98 +189,130 @@ class _BaseScaffoldState extends ConsumerState { ), ), drawer: Drawer( - child: ListView( - children: [ - DrawerHeader( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - SizedBox( - width: mywidth / 8, - child: Image( - image: AssetImage('assets/logos/fcsc.png'), - ), - ), - ], - ), - Divider(), - InkWell( - onTap: () => context.go('/editProfile'), - child: Row( + child: Column(children: [ + Expanded( + child: ListView( + children: [ + DrawerHeader( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( children: [ SizedBox( width: mywidth / 8, - height: mywidth / 8, - child: ClipOval( - child: _avatarUrl.isNotEmpty - ? Image( - image: NetworkImage(_avatarUrl), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ) - : Image( - image: AssetImage( - 'assets/edit_profile/profile.png', - ), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ), + child: Image( + image: AssetImage('assets/logos/fcsc.png'), ), - //child: Image(image: AssetImage('assets/edit_profile/profile.png')) - ), - SizedBox( - width: mywidth / 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Text(userId ?? 'Loading user...'), // Display userId here - // Text('Mohammad@fcsc.com') - Text(userName ?? 'Loading...'), - Text( - userEmail ?? 'Loading...', - style: TextStyle(fontSize: 12), - ), - ], ), ], ), - ), - ], + Divider(), + InkWell( + onTap: () { if(userId != 'guest')context.go('/editProfile');}, + child: Row( + children: [ + SizedBox( + width: mywidth / 8, + height: mywidth / 8, + child: ClipOval( + child: _avatarUrl.isNotEmpty + ? Image( + image: NetworkImage(_avatarUrl), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ) + : Image( + image: AssetImage( + 'assets/edit_profile/profile.png', + ), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + //child: Image(image: AssetImage('assets/edit_profile/profile.png')) + ), + SizedBox( + width: mywidth / 20, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text(userId ?? 'Loading user...'), // Display userId here + // Text('Mohammad@fcsc.com') + if(userId == "guest")...[ Text(userName ?? 'Guest User'),] + else...[Text(userName ?? 'Loading...'), + Text( + userEmail ?? 'Loading...', + style: TextStyle(fontSize: 12), + ),] + ], + ), + ], + ), + ), + ], + ), ), - ), - ListTile( - leading: Icon(Icons.feedback), - // title: const Text('Feedback'), - title: Text(AppLocalizations.of(context)!.feedback_title), - onTap: () => context.go('/feedback'), - ), - if (role == 'admin') + if(userId != 'guest') ListTile( - leading: Icon(Icons.manage_accounts), - title: Text('Manage User'), - // title: Text( - // AppLocalizations.of(context)!.manage_user, - // ), - onTap: () => context.go('/manageuser'), + leading: Icon(Icons.feedback), + // title: const Text('Feedback'), + title: Text(AppLocalizations.of(context)!.feedback_title), + onTap: () => context.go('/feedback'), ), - ListTile( - leading: Icon(Icons.book), - title: const Text('User Guide'), - onTap: () => context.go('/user-guide'), - ), - ListTile( - leading: Icon(Icons.logout), - title: const Text('Logout'), - onTap: () => logout(), - ), - ], + if (role == 'admin') + ListTile( + leading: Icon(Icons.manage_accounts), + // title: Text('Manage User'), + title: Text( + AppLocalizations.of(context)!.manage_user, + ), + onTap: () => context.go('/manageuser'), + ), + ListTile( + leading: Icon(Icons.book), + title: const Text('User Guide'), + onTap: () => context.go('/user-guide'), + ), + ListTile( + leading: Icon(Icons.logout), + title: const Text('Logout'), + onTap: () => logout(), + ), + ], + ), ), - ), + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: FutureBuilder( + future: _getAppVersion(), // Function to get the app version + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Text( + 'Loading version...', + style: TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.center, + ); + } else if (snapshot.hasError) { + return Text( + 'Error fetching version', + style: TextStyle(fontSize: 12, color: Colors.red), + textAlign: TextAlign.center, + ); + } else { + return Text( + 'Version ${snapshot.data}', + style: TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.center, + ); + } + }, + ), + ), + ])), bottomNavigationBar: BottomNavigationBar( currentIndex: _getSelectedIndex(currentRoute), onTap: (index) => _onItemTapped(context, index), diff --git a/lib/presentation/routes/drawer_routes/user_guide_route.dart b/lib/presentation/routes/drawer_routes/user_guide_route.dart index 341037ec..3c250988 100644 --- a/lib/presentation/routes/drawer_routes/user_guide_route.dart +++ b/lib/presentation/routes/drawer_routes/user_guide_route.dart @@ -1,99 +1,100 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:video_player/video_player.dart'; - -import 'package:uae_stat/domain/use_cases/language.dart'; -import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart'; -import 'package:uae_stat/presentation/components/space.dart'; -import 'package:uae_stat/presentation/components/themed_app_bar.dart'; - -typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc}); - -class UserGuideRoute extends HookConsumerWidget { - const UserGuideRoute({super.key}); - - static const List<_UserGuideType> _data = [ - ( - videoPath: UserGuideAssetPath.toggleLanguage, - enDesc: - 'You can toggle between Arabic and English by click the toggle button.', - arDesc: - 'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.', - ), - ( - videoPath: UserGuideAssetPath.searchIndicators, - enDesc: - 'You can browse indicators by category or use the search functionality', - arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث', - ), - ( - videoPath: UserGuideAssetPath.bookmarkIndicators, - enDesc: - 'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.', - arDesc: - 'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.', - ), - ]; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final e = useState<_UserGuideType>(_data.first); - final carouselOptions = CarouselOptions( - autoPlay: true, - clipBehavior: Clip.none, - onPageChanged: (index, reason) => e.value = _data[index], - aspectRatio: 9 / 17, - disableCenter: true, - enlargeCenterPage: true, - floatingIndicator: true, - autoPlayInterval: const Duration(seconds: 10), - ); - final bodyContent = Column( - children: [ - Expanded( - flex: 3, - child: FlutterCarousel( - options: carouselOptions, - items: _data - .map( - (e) => VideoPlayer( - VideoPlayerController.asset( - e.videoPath, - )..initialize(), - ), - ) - .toList(), - ), - ), - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(36, 24, 36, 36), - child: Text( - context.translate(e.value.enDesc, e.value.arDesc), - ), - ), - ), - 98.verticalSpace, - ], - ); - final body = Column( - children: [ - ThemedAppBar( - titleText: context.translate( - 'User Guide', - 'دليل المستخدم', - ), - ), - Expanded( - child: bodyContent, - ), - ], - ); - return ColoredBox( - color: Colors.white, - child: SafeArea(child: body), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:hooks_riverpod/hooks_riverpod.dart'; +// import 'package:video_player/video_player.dart'; +// +// import 'package:uae_stat/domain/use_cases/language.dart'; +// import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart'; +// import 'package:uae_stat/presentation/components/space.dart'; +// import 'package:uae_stat/presentation/components/themed_app_bar.dart'; +// +// typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc}); +// +// class UserGuideRoute extends HookConsumerWidget { +// const UserGuideRoute({super.key}); +// +// static const List<_UserGuideType> _data = [ +// ( +// videoPath: UserGuideAssetPath.toggleLanguage, +// enDesc: +// 'You can toggle between Arabic and English by click the toggle button.', +// arDesc: +// 'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.', +// ), +// ( +// videoPath: UserGuideAssetPath.searchIndicators, +// enDesc: +// 'You can browse indicators by category or use the search functionality', +// arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث', +// ), +// ( +// videoPath: UserGuideAssetPath.bookmarkIndicators, +// enDesc: +// 'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.', +// arDesc: +// 'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.', +// ), +// ]; +// +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// final e = useState<_UserGuideType>(_data.first); +// final carouselOptions +// // final carouselOptions = CarouselOptions( +// // autoPlay: true, +// // clipBehavior: Clip.none, +// // onPageChanged: (index, reason) => e.value = _data[index], +// // aspectRatio: 9 / 17, +// // disableCenter: true, +// // enlargeCenterPage: true, +// // floatingIndicator: true, +// // autoPlayInterval: const Duration(seconds: 10), +// // ); +// final bodyContent = Column( +// children: [ +// Expanded( +// flex: 3, +// child: FlutterCarousel( +// options: carouselOptions, +// items: _data +// .map( +// (e) => VideoPlayer( +// VideoPlayerController.asset( +// e.videoPath, +// )..initialize(), +// ), +// ) +// .toList(), +// ), +// ), +// Expanded( +// child: SingleChildScrollView( +// padding: const EdgeInsets.fromLTRB(36, 24, 36, 36), +// child: Text( +// context.translate(e.value.enDesc, e.value.arDesc), +// ), +// ), +// ), +// 98.verticalSpace, +// ], +// ); +// final body = Column( +// children: [ +// ThemedAppBar( +// titleText: context.translate( +// 'User Guide', +// 'دليل المستخدم', +// ), +// ), +// Expanded( +// child: bodyContent, +// ), +// ], +// ); +// return ColoredBox( +// color: Colors.white, +// child: SafeArea(child: body), +// ); +// } +// } From 8efc267da417ee752900608eb7a3025c00e94a6c Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 30 Jan 2025 17:02:56 +0530 Subject: [PATCH 09/18] chart added --- FCSC_UAE_Stats.jks | Bin 0 -> 2708 bytes android/app/build.gradle | 21 +++++++++-- macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ pubspec.lock | 34 +++++++++++++++++- pubspec.yaml | 4 ++- 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 FCSC_UAE_Stats.jks diff --git a/FCSC_UAE_Stats.jks b/FCSC_UAE_Stats.jks new file mode 100644 index 0000000000000000000000000000000000000000..2519f50d438315bc674743bc78fd2eea37730956 GIT binary patch literal 2708 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 literal 0 HcmV?d00001 diff --git a/android/app/build.gradle b/android/app/build.gradle index 49fcb1cd..0b8467e3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,14 +15,18 @@ if (localPropertiesFile.exists()) { def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "1" + flutterVersionCode = "3" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0" + flutterVersionName = "1.0.2" } +def keystorePropertiesFile = rootProject.file("key.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + android { namespace = "ae.gov.fcsc.frontend" compileSdk = flutter.compileSdkVersion @@ -48,11 +52,22 @@ android { versionName = flutterVersionName } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug + signingConfig signingConfigs.release + minifyEnabled true // Enable code shrinking for smaller APKs + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8a156355..2feedeb3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import connectivity_plus import file_selector_macos import flutter_secure_storage_macos +import package_info_plus import path_provider_foundation import share_plus import shared_preferences_foundation @@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index c0267d53..3458fc7c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -496,7 +496,7 @@ packages: source: hosted version: "2.4.4" flutter_plugin_android_lifecycle: - dependency: transitive + dependency: "direct main" description: name: flutter_plugin_android_lifecycle sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" @@ -953,6 +953,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -961,6 +969,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 + url: "https://pub.dev" + source: hosted + version: "8.1.4" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + url: "https://pub.dev" + source: hosted + version: "3.0.2" path: dependency: transitive description: @@ -1380,6 +1404,14 @@ packages: url: "https://pub.dev" source: hosted version: "28.1.39" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0eb9f5d6..b382e3aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,8 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" -version: 0.5.10 +#version: 0.5.10 +version: 1.0.2+3 environment: sdk: ">=3.2.3 <4.0.0" @@ -63,6 +64,7 @@ dependencies: cached_network_image: ^3.4.1 syncfusion_flutter_charts: ^28.1.39 connectivity_plus: ^6.1.2 + package_info_plus: ^8.1.4 dependency_overrides: fading_edge_scrollview: ^4.1.1 From 7761c6b19afb0df1e21ff845d5e887cd8de0a9aa Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 30 Jan 2025 17:53:36 +0530 Subject: [PATCH 10/18] merge issue fix --- .run/main.dart.run.xml | 2 +- .../Screens/charts/widgets/chart_widget.dart | 3 +- .../drawer_routes/custom_drawer_routes.dart | 167 ++++++++++-------- pubspec.lock | 4 +- pubspec.yaml | 2 +- 5 files changed, 101 insertions(+), 77 deletions(-) diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4767aff8..4a58a7c9 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ 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 7451e2a9..05755d86 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -901,8 +901,7 @@ class ChartWidget extends StatelessWidget { child: BarChart( BarChartData( maxY: 400000, - rotationQuarterTurns: - rotationTurns, // Adjust maxY as needed based on your data + rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( tooltipHorizontalAlignment: FLHorizontalAlignment.center, diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 82073e8d..6394c770 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -59,7 +59,7 @@ class _BaseScaffoldState extends ConsumerState { final GlobalKey bottomNavKey = GlobalKey(); final GlobalKey toggleKey = GlobalKey(); final GlobalKey drawerKey = GlobalKey(); - late double toggleHeight=0; + late double toggleHeight = 0; bool isLoggedOut = false; @override @@ -70,6 +70,7 @@ class _BaseScaffoldState extends ConsumerState { _startAppbarTour(); }); } + //Method to Start App Tour void _startAppbarTour() { final scaffoldProvider = ref.watch(scaffoldTourProvider); @@ -89,11 +90,11 @@ class _BaseScaffoldState extends ConsumerState { }, )..show(context: context); } - } void _calculatetooglePosition() { - final RenderBox cardRenderBox = toggleKey.currentContext!.findRenderObject() as RenderBox; + final RenderBox cardRenderBox = + toggleKey.currentContext!.findRenderObject() as RenderBox; final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); final Size cardSize = cardRenderBox.size; toggleHeight = cardSize.height; @@ -102,18 +103,18 @@ class _BaseScaffoldState extends ConsumerState { 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; } - void _initTarget(){ - final double screenWidth= MediaQuery.of(context).size.width; - final double screenHeight= MediaQuery.of(context).size.height; - appBarTargets=[ + void _initTarget() { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + appBarTargets = [ TargetFocus( identify: 'bottomNav', keyTarget: bottomNavKey, @@ -124,14 +125,13 @@ class _BaseScaffoldState extends ConsumerState { alignment: ContentAlign.top, space: 0, gap: 100, - ), TargetContent( align: ContentAlign.top, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height* 0.30, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.30, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -142,7 +142,7 @@ class _BaseScaffoldState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip(), + onPressed: () => handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -174,14 +174,21 @@ class _BaseScaffoldState 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( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.finish(); - ref.read(scaffoldTourProvider.notifier).state=true; - ref.read(previousChartsTourProvider.notifier).state=false; + ref + .read(scaffoldTourProvider.notifier) + .state = true; + ref + .read(previousChartsTourProvider + .notifier) + .state = false; context.push('/marriages'); }, ), @@ -191,10 +198,12 @@ class _BaseScaffoldState 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( - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -212,7 +221,7 @@ class _BaseScaffoldState extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(left:screenWidth*0.4, bottom: 0 ), + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.top, child: Container( width: 200, @@ -223,7 +232,6 @@ class _BaseScaffoldState extends ConsumerState { 'assets/app_tour/leftUp.png', fit: BoxFit.contain, ), - ], ), ), @@ -245,10 +253,10 @@ class _BaseScaffoldState extends ConsumerState { ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height* 0.80, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.80, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -259,7 +267,7 @@ class _BaseScaffoldState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip(), + onPressed: () => handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -291,10 +299,12 @@ class _BaseScaffoldState 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( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.previous(); }, @@ -305,10 +315,12 @@ class _BaseScaffoldState 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( - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -326,11 +338,12 @@ class _BaseScaffoldState extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(right: screenWidth*0.7, top: toggleHeight-5), + padding: EdgeInsets.only( + right: screenWidth * 0.7, top: toggleHeight - 5), align: ContentAlign.right, child: Container( width: 200, - height: screenHeight/4, + height: screenHeight / 4, child: Stack( children: [ Image.asset( @@ -360,7 +373,7 @@ class _BaseScaffoldState extends ConsumerState { keyTarget: toggleKey, shape: ShapeLightFocus.RRect, radius: 0, - paddingFocus: 0,// Rounded rectangle highlight + paddingFocus: 0, // Rounded rectangle highlight contents: [ createTargetContent( text: AppLocalizations.of(context)!.toggle, @@ -370,10 +383,9 @@ class _BaseScaffoldState extends ConsumerState { ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height* 0.80, + height: MediaQuery.of(context).size.height * 0.80, child: Stack( children: [ Positioned( @@ -416,26 +428,40 @@ class _BaseScaffoldState 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( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.previous(); }, ), ), const SizedBox(width: 10), - ElevatedButton( onPressed: () { debugPrint('Got it clicked'); - ref.read(chartsTourProvider.notifier).state = true; - ref.read(previousChartsTourProvider.notifier).state = true; - ref.read(homeTourProvider.notifier).state = true; - ref.read( previousHomeTourProvider.notifier).state = true; - ref.read(scaffoldTourProvider.notifier).state = true; - ref.read(previousScaffoldTourProvider.notifier).state = true; + ref + .read(chartsTourProvider.notifier) + .state = true; + ref + .read( + previousChartsTourProvider.notifier) + .state = true; + ref.read(homeTourProvider.notifier).state = + true; + ref + .read(previousHomeTourProvider.notifier) + .state = true; + ref + .read(scaffoldTourProvider.notifier) + .state = true; + ref + .read(previousScaffoldTourProvider + .notifier) + .state = true; tutorialCoachMark.finish(); }, style: ElevatedButton.styleFrom( @@ -466,11 +492,12 @@ class _BaseScaffoldState extends ConsumerState { ), ), TargetContent( - padding: EdgeInsets.only(left:screenWidth*0.7, top: toggleHeight), + padding: + EdgeInsets.only(left: screenWidth * 0.7, top: toggleHeight), align: ContentAlign.left, child: Container( width: 200, - height: screenHeight/4, + height: screenHeight / 4, child: Stack( children: [ Image.asset( @@ -615,7 +642,7 @@ class _BaseScaffoldState extends ConsumerState { child: Builder( builder: (BuildContext context) { return IconButton( - key:drawerKey, + key: drawerKey, color: (widget.showBackButton == true) ? Colors.white : Colors.black, @@ -651,7 +678,9 @@ class _BaseScaffoldState extends ConsumerState { ), Divider(), InkWell( - onTap: () { if(userId != 'guest')context.go('/editProfile');}, + onTap: () { + if (userId != 'guest') context.go('/editProfile'); + }, child: Row( children: [ SizedBox( @@ -684,12 +713,15 @@ class _BaseScaffoldState extends ConsumerState { children: [ // Text(userId ?? 'Loading user...'), // Display userId here // Text('Mohammad@fcsc.com') - if(userId == "guest")...[ Text(userName ?? 'Guest User'),] - else...[Text(userName ?? 'Loading...'), - Text( - userEmail ?? 'Loading...', - style: TextStyle(fontSize: 12), - ),] + if (userId == "guest") ...[ + Text(userName ?? 'Guest User'), + ] else ...[ + Text(userName ?? 'Loading...'), + Text( + userEmail ?? 'Loading...', + style: TextStyle(fontSize: 12), + ), + ] ], ), ], @@ -698,20 +730,13 @@ class _BaseScaffoldState extends ConsumerState { ], ), ), - if(userId != 'guest') - ListTile( -<<<<<<< HEAD - leading: Icon(Icons.feedback), - // title: const Text('Feedback'), - title: Text(AppLocalizations.of(context)!.feedback_title), - onTap: () => context.go('/feedback'), -======= - leading: Icon(Icons.manage_accounts), - // title: Text('Manage User'), - title: Text(AppLocalizations.of(context)!.manage_user,), - onTap: () => context.go('/manageuser'), ->>>>>>> aab1520305f7176b69eb4d5b8be01b832c8c0c71 - ), + if (userId != 'guest') + ListTile( + leading: Icon(Icons.feedback), + // title: const Text('Feedback'), + title: Text(AppLocalizations.of(context)!.feedback_title), + onTap: () => context.go('/feedback'), + ), if (role == 'admin') ListTile( leading: Icon(Icons.manage_accounts), diff --git a/pubspec.lock b/pubspec.lock index 84ac165f..99484f5b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,10 +433,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" + sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" url: "https://pub.dev" source: hosted - version: "0.69.2" + version: "0.70.2" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 68b42804..0506e766 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.69.2 + fl_chart: ^0.70.2 tutorial_coach_mark: ^1.2.12 marquee: ^2.2.3 url_launcher: ^6.3.1 From b5d4df493ec2b0fb8dddd38a32ee350b447f9fdf Mon Sep 17 00:00:00 2001 From: venbaittech Date: Sat, 1 Feb 2025 17:55:37 +0530 Subject: [PATCH 11/18] charts added and bug fix --- android/app/build.gradle | 4 +- assets/banners/fcsc.png | Bin 11792 -> 19097 bytes assets/icons/uae_numbers/bookmarks.png | Bin 0 -> 325 bytes assets/icons/uae_numbers/share.png | Bin 0 -> 375 bytes assets/logos/fcsc.png | Bin 558 -> 2302 bytes assets/splash_screen/logo.png | Bin 25412 -> 19097 bytes lib/config/my_router.dart | 4 +- .../icons/uae_numbers_asset_path.dart | 7 + .../auth_verification/create_new_pw.dart | 17 +- .../auth_verification/registration.dart | 86 +- .../Screens/charts/screens/chart_screen.dart | 264 +++-- .../Screens/charts/widgets/chart_widget.dart | 358 ++++++- lib/presentation/Screens/profilepage.dart | 6 +- .../routes/auth_routes/login_route.dart | 46 +- .../Drawer Items/edit_profile.dart | 960 +++++++++--------- .../drawer_routes/custom_drawer_routes.dart | 29 +- pubspec.yaml | 4 +- 17 files changed, 1151 insertions(+), 634 deletions(-) create mode 100644 assets/icons/uae_numbers/bookmarks.png create mode 100644 assets/icons/uae_numbers/share.png create mode 100644 lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 0b8467e3..02d9a058 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 = "3" + flutterVersionCode = "6" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.2" + flutterVersionName = "1.0.5" } def keystorePropertiesFile = rootProject.file("key.properties") diff --git a/assets/banners/fcsc.png b/assets/banners/fcsc.png index 4de43a5c4c6e3de85809f061638c20602be4da86..03eff4b63c56e0be969e17bd971b4e64c9141b1a 100644 GIT binary patch literal 19097 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 diff --git a/assets/icons/uae_numbers/bookmarks.png b/assets/icons/uae_numbers/bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..82d02a7b230fcdda741da71375bb05052de77e60 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoM!3HGxw}u@9Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFK>jUH7srqa#^ zqrreNhe7%PTPpL7hP(i?2Hr!FKNfaz7_!PW)LqgHt*hx*o9~dt(^QKF(b* T#YRaK=t~AqS3j3^P6P000;W1^@s654Bdt00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPvQpm!8nj^1gkpDUJx(E`wI&1dA~A{v3TDVF((t>MZkf7jooA7<8VtgsM>y;A#nl#GqA?5ar^>{9O=j8J|<`S12_| zxqFPe|F}M|Ko8F_Q3hUM=2X==qO`7&G<@!y$P75xfNfW*sN%$zodx#pGO-5JS(Gpj zTn!6t3-bjjGvFlc{5hHAf%sNxuy@uOKkvw6k|aYQ_l6uWPAe(Ewxx#1!Q1egcmM_? V)wMo8P!j+E002ovPDHLkV1k(0kc|KU literal 0 HcmV?d00001 diff --git a/assets/logos/fcsc.png b/assets/logos/fcsc.png index 044f949079b72bf5e84bd17a90047a53bd1fb545..dfbf3987e14c3ad96ec8112efd31522e82b60ea0 100644 GIT binary patch literal 2302 zcmd6peK^y5AIE>AAq#CgeswhS6g$|22(vs+L!&|-es!{REx*!Hw^W`dG8-p!$C5i| zY*NxBe$`Rb^vIk}tuj+0c-tJ*KtaPM1}5Y~ltRLbmRMqBKHD}<0t8afkZ zPGa}?BWF9WVYU!xyPa|U|AbvG$DJ=NY&KOU?76YH|1j)QA6MDZe++i9iv+!iw7agj zNHB9L?QVi1VU^_0^3Y}wgG&Bofql-svQ7aQ=oqOAk^jalwi-0LGUQbTyVQyGNx{hS zBDHrl+SnYMU7}03gqFObV9fC}?VH%(Gt6S23Nc95PYp&!d#t}8Wn=-EZNJJVc2O!D z84VoRvEWjpFA3@kJzt`mVGX73!rNRoDP1D~e~!DY^-)>Zup~h-iVcR#Fl&jls~flN z;(2fY$(>r%O2p{mX~vuK4v?HJl?m~AcYb0PXB&W0Jnf)u?609ne*wqk>I%#=`#!Us z7#kw3DRSG~=H#o_3WH2ZCT)s@8O?SB&=gNwbMhWNwm7#)0G>#9RIvi~=GD_BcY9Bq&AQ&E3-yiZe%5xf+48~7 z=_T_FORMA2-ii%+>(8@e>UKWb9q469F3gN!=fYvn7^>pgAUV^M9R8T0@_5?usJPo@ zS6B~2^`N~rkR2p!|2UlUoHy%M!}6;Z0ZB!`zz#3)W)-}-iG)iOO@44!o_qeR^czv`!E8)WhpVbvOLaf<;mzkrxWVqc zy+m1P$04nJ+T6(sGEJf2)(~Ze3)^oC|A#Se7uL*8jd(5XpH-avZ60zpI!;o z-dn>EhfVe8hr~X=|0u6uHWEq-n$4Bx{Nk?jK7Zn2wr)TiIEuV?LRFWdoU1W@MwH>( z{sOL{$ZmeR<75lRL*h;_naf`gsDeqj?Y0KQOrmjaa z(0H;ix|ALFaIJFum_6<)1zL-Se3^|DG*k%BprHl4jEshSCA}A?+o_S@Cl*3Sb2-B3 z!uQp_abZxY&S)uoV6@|LA?DM1g#MS2xAPL^xZr1SNgzT;XM81y)_;F0nO zoJmT0*@X#tGxM!qJ$1PVHm}Lzj=a#k2KY9R{%6hsw?%wgC^O|g=B^c#NuVw-gx_^5 zmwp%fP|pU+bmn%ZF_Tza%;d>9XR`9?u`^!s-%$g-?F9Tzu0y^{fxMe@3#WuC5%p!Q zvE-7i$A7>IJev>y(z!)iOO{;1MuJZQk)|m)Jz0!3#mkbOd(E%R%@C;ENk+9!k^|U7 ztu;On}*)mVFfI;S4Yzcp(hce@^)apqX;;LUl%=xR8+}W={E?dl5nW8=CV}j_%LhvC40O8B z>M6&zxmnQWsa>bX;I?r<<7Q5aF}D=gOVzN!cM)I4&#F2qIz#shzrU_Whn%u|4a93b zQdrJLw@lc&#);Of1NteuU7>nvS7SFkkm$e7j%Hc$M#2?i+uWDYmW5hfc_UHJ^UMAf zf(t&~09TA83c+Dj6r%X}^6>MjB2lApS$w22rQwI^q=__Gj2&<%%32;c6sK8U*(p(j z40hmZWy_JmU9cVWZQmfquC}&XWXoXt6TiD-ur(z5;A|eo>>-frr9wFF=B-q>mdEH( zLuD4C(0PH{JEoP>o@SLIJqbEgq^kx_CmFGOhNbEfkX`)Rs!UNQ9q6BI+hC4h~L!1<}FJAVP8S z3+Uz~I0z0xi<65Vpo0jCZVnC=L2Q>Uf)o^WDT4p1EwxS3=6uiErA>25o7`Q&2VN5H zNO;J5+`V@(05E7w?C&~GQG;HLP!wB&HRvb!%Mdgt=F+x7F^m34P=91xdv+Fmk9_2x!fXp=dr6C$H7eZ_cNotC5jYpAws__!E3vm%+$-IQdW1G8z;^$UWZ dXlnf%;s?#wg=dHF=H>tZ002ovPDHLkV1mOk>aYL+ diff --git a/assets/splash_screen/logo.png b/assets/splash_screen/logo.png index 8c3218e8f97d9884e8e460bf8ab12d5bb9648906..03eff4b63c56e0be969e17bd971b4e64c9141b1a 100644 GIT binary patch literal 19097 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 Ny;V?uQz>r|{6C#shF<^x diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 1678f6e3..c02c0097 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -413,7 +413,9 @@ final GoRouter router = GoRouter( builder: (context, state) { final userId = state.pathParameters['userId']!; final email = state.pathParameters['email']!; - return CreateNewPw(userId: userId, email: email); + final key = state.uri.queryParameters['key'] ?? ''; + + return CreateNewPw(userId: userId, email: email, keyParam: key); }, ), GoRoute( diff --git a/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart b/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart new file mode 100644 index 00000000..903aca60 --- /dev/null +++ b/lib/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +abstract class UaeNumbersAssetPath { + static const _basePath = 'assets/icons/uae_numbers'; + static const bookmarksUae = '$_basePath/bookmarks.png'; + static const shareUae = '$_basePath/share.png'; +} \ No newline at end of file diff --git a/lib/presentation/Screens/auth_verification/create_new_pw.dart b/lib/presentation/Screens/auth_verification/create_new_pw.dart index 2e63faa1..83692570 100644 --- a/lib/presentation/Screens/auth_verification/create_new_pw.dart +++ b/lib/presentation/Screens/auth_verification/create_new_pw.dart @@ -13,7 +13,9 @@ import '../../../config/my_theme.dart'; class CreateNewPw extends StatefulWidget { final String userId; final String email; - const CreateNewPw({Key? key, required this.userId, required this.email}); + final String? keyParam; + + const CreateNewPw({Key? key, required this.userId, required this.email, required this.keyParam}); @override State createState() => _CreateNewPwState(); @@ -210,7 +212,14 @@ class _CreateNewPwState extends State { alignment: Alignment.topRight, child: GestureDetector( onTap: () { - context.go('/editProfile'); + + if (widget.keyParam == 'editProfile') { + context.go('/editProfile'); + }else + { + context.go('/profile/${widget.userId}'); + } + }, child: Container( margin: EdgeInsets.only(top: 16,left: 16,bottom: 16,right: 1), // Add margin for positioning @@ -351,8 +360,8 @@ class _CreateNewPwState extends State { SizedBox(height: screenHeight / 5), Center( child: Container( - height: screenHeight / 8, - width: screenWidth / 2, + height: screenHeight / 12, + width: screenWidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/splash_screen/logo.png"), diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index 616c128e..a4650aad 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -31,6 +31,7 @@ class _RegisterScreenState extends State { String? _password; bool registrationSuccess = false; bool registrationFailed = false; + bool isRegistering = false; dynamic userID; final pb = PocketBase('https://pb.venbait.in'); @@ -144,6 +145,12 @@ class _RegisterScreenState extends State { final regex = RegExp( r'^[A-Za-z0-9~!@#$%^&*()_+=[\]{}|;:,.<>?/-àèìòùáéíóúÀÈÌÒÙÁÉÍÓÚ]*$'); + // Define regular expressions for password complexity requirements + final hasUppercase = RegExp(r'[A-Z]'); + final hasLowercase = RegExp(r'[a-z]'); + final hasDigit = RegExp(r'\d'); + final hasSpecialCharacter = RegExp(r'[~!@#$%^&*()_+=[\]{}|;:,.<>?/-]'); + if (value == null || value.isEmpty) { return 'Required'; } else if (value.length < 8) { @@ -151,7 +158,7 @@ class _RegisterScreenState extends State { } // Check the length constraint - if (value.length < 8 || value.length > 40) { + if (value.length < 8 || value.length > 64) { return 'Password must be between 8 and 64 characters'; } @@ -160,6 +167,27 @@ class _RegisterScreenState extends State { return 'Password contains invalid characters'; } + // Track missing constraints + List missingConstraints = []; + + if (!hasUppercase.hasMatch(value)) { + missingConstraints.add('uppercase letter'); + } + if (!hasLowercase.hasMatch(value)) { + missingConstraints.add('lowercase letter'); + } + if (!hasDigit.hasMatch(value)) { + missingConstraints.add('numeric digit'); + } + if (!hasSpecialCharacter.hasMatch(value)) { + missingConstraints.add('special character'); + } + + // If there are missing constraints, return a consolidated message + if (missingConstraints.isNotEmpty) { + return 'At least one ${missingConstraints.join(', ')}'; + } + _password = value; // Store the password for confirm password validation return null; } @@ -185,12 +213,15 @@ class _RegisterScreenState extends State { Future _registerUser() async { if (_formKey.currentState?.validate() ?? false) { if (isChecked) { + setState(() { + isRegistering = true; // Disable the button + }); try { final adminAuth = await pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - print('adminToken- ${adminToken}'); + print('adminTokenREgistration- ${adminToken}'); // Create user in PocketBase final response = await pb.collection('users').create(body: { 'uname': _usernameController.text, @@ -211,6 +242,7 @@ class _RegisterScreenState extends State { userID = response.id; registrationSuccess = true; registrationFailed = false; // Show success message on success + isRegistering = false; }); // Navigate to ProfileScreen after successful registration @@ -249,6 +281,9 @@ class _RegisterScreenState extends State { registrationFailed = true; registrationSuccess = false; }); + setState(() { + isRegistering = false; // Re-enable the button after error + }); } } } else { @@ -330,7 +365,9 @@ class _RegisterScreenState extends State { buildIconContainer(Icons.report, Color(0xFF7DAFBC)), SizedBox(height: 20), Text( - "Your registration is pending for verification.", + context.translate( + 'Your registration is pending for verification.', + 'تسجيلك في انتظار التحقق.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 16, @@ -339,7 +376,9 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), Text( - "Kindly verify your mail to proceed further.", + context.translate( + 'Kindly verify your mail to proceed further.', + 'يرجى التحقق من البريد الخاص بك للمضي قدما.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -350,14 +389,14 @@ class _RegisterScreenState extends State { ElevatedButton( onPressed: () => {context.go('/')}, child: Text( - 'Go to Login', + context.translate('Go to Login', 'اذهب إلى تسجيل الدخول'), ), ), SizedBox(height: screenheight / 5), Center( child: Container( - height: screenheight / 8, - width: screenwidth / 2, + height: screenheight / 16, + width: screenwidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( @@ -379,7 +418,8 @@ class _RegisterScreenState extends State { buildIconContainer(Icons.report, Colors.red), SizedBox(height: 20), Text( - "Sorry ${_usernameController.text}!", + context.translate('Sorry ${_usernameController.text}!', + 'آسف ${_usernameController.text}!'), textAlign: TextAlign.center, style: TextStyle( fontSize: 16, @@ -388,7 +428,9 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), Text( - "Your registration process failed. For further assistance, please contact support.", + context.translate( + 'Your registration process failed. For further assistance, please contact support.', + 'فشلت عملية التسجيل الخاصة بك. لمزيد من المساعدة، يرجى الاتصال بالدعم.'), textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -409,14 +451,14 @@ class _RegisterScreenState extends State { }) }, child: Text( - 'Retry', + context.translate('Retry', 'أعد المحاولة'), ), ), SizedBox(height: screenheight / 5), Center( child: Container( - height: screenheight / 8, - width: screenwidth / 2, + height: screenheight / 16, + width: screenwidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( @@ -475,7 +517,8 @@ class _RegisterScreenState extends State { decoration: InputDecoration( // hintText: 'Enter your email', hintText: _showHints[1] - ? AppLocalizations.of(context)!.enter_your_email + ? AppLocalizations.of(context)! + .enter_your_email : null, // _showHints[1] ? 'Enter your email' : null, prefixIcon: Icon( @@ -500,7 +543,8 @@ class _RegisterScreenState extends State { obscureText: _obscurePassword, decoration: InputDecoration( hintText: _showHints[2] - ? AppLocalizations.of(context)!.enter_your_password + ? AppLocalizations.of(context)! + .enter_your_password : null, // _showHints[2] ? 'Enter your password' : null, prefixIcon: Icon( @@ -510,8 +554,8 @@ class _RegisterScreenState extends State { suffixIcon: IconButton( icon: Icon( _obscurePassword - ? Icons.visibility - : Icons.visibility_off, + ? Icons.visibility_off + : Icons.visibility, color: Colors.blue, ), onPressed: () { @@ -545,8 +589,8 @@ class _RegisterScreenState extends State { suffixIcon: IconButton( icon: Icon( _obscureConfirmPassword - ? Icons.visibility - : Icons.visibility_off, + ? Icons.visibility_off + : Icons.visibility, color: Colors.blue, ), onPressed: () { @@ -632,7 +676,7 @@ class _RegisterScreenState extends State { SizedBox( width: screenwidth / 1.3, child: ElevatedButton( - onPressed: _registerUser, + onPressed: isRegistering ? null : _registerUser, style: ElevatedButton.styleFrom( backgroundColor: Color( 0xFFA7887A), // Brownish color for Register @@ -719,8 +763,8 @@ class _RegisterScreenState extends State { ), Center( child: Container( - height: screenheight / 8, - width: screenwidth / 2, + height: screenheight / 16, + width: screenwidth / 2.5, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 291e7e1d..c962a673 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -3,9 +3,11 @@ 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/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart'; import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart'; import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:uae_stat/presentation/components/my_toggle.dart'; import '../filters/search_filter_helper.dart'; @@ -492,53 +494,139 @@ class _ChartScreen1State extends ConsumerState { ), ), // Button Section + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ElevatedButton( + // onPressed: () { + // // Clear all selected filters + // selectedFilters.forEach((filter) { + // filter["filter_data"].clear(); + // }); + // setState(() { + // chartsData = List.from(originalTabChartsData); + // cardData = List.from(originalTabCardData); + // }); + // // Reset the selectedFiltersStorage to empty when clearing + // selectedFiltersStorage.clear(); + // Navigator.pop(context); + // }, + // child: Text(context.translate( + // 'Clear', + // 'واضح', + // )), + // style: ElevatedButton.styleFrom( + // backgroundColor: Colors.grey, + // ), + // ), + // ElevatedButton( + // onPressed: () { + // print( + // "Selected Filters before applying: $selectedFilters"); + // setState(() { + // chartsData = originalTabChartsData; + // cardData = originalTabCardData; + // }); + // + // applyFilters(context, filters, chartsData, cardData, + // selectedFilters); + // // Save the selected filters to storage after applying + // selectedFiltersStorage = List.from(selectedFilters); + // }, + // child: Text(context.translate( + // 'Apply Filter', + // 'تطبيق الفلتر', + // )), + // style: ElevatedButton.styleFrom( + // backgroundColor: Colors.blue, + // ), + // ), + // ], + // ), + // ), Padding( padding: const EdgeInsets.all(16.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ElevatedButton( - onPressed: () { - // Clear all selected filters - selectedFilters.forEach((filter) { - filter["filter_data"].clear(); - }); - setState(() { - chartsData = List.from(originalTabChartsData); - cardData = List.from(originalTabCardData); - }); - // Reset the selectedFiltersStorage to empty when clearing - selectedFiltersStorage.clear(); - Navigator.pop(context); - }, - child: Text(context.translate( - 'Clear', - 'واضح', - )), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey, + Expanded( + child: ElevatedButton( + onPressed: () { + selectedFilters.forEach((filter) { + filter["filter_data"].clear(); + }); + setState(() { + chartsData = List.from(originalTabChartsData); + cardData = List.from(originalTabCardData); + }); + selectedFiltersStorage.clear(); + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12), // Added padding + child: Text( + context.translate('Clear', 'واضح'), + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + overflow: + TextOverflow.ellipsis, // Prevents wrapping + maxLines: 1, // Ensures single line + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), ), ), - ElevatedButton( - onPressed: () { - print( - "Selected Filters before applying: $selectedFilters"); - setState(() { - chartsData = originalTabChartsData; - cardData = originalTabCardData; - }); + SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + print( + "Selected Filters before applying: $selectedFilters"); + setState(() { + chartsData = originalTabChartsData; + cardData = originalTabCardData; + }); - applyFilters(context, filters, chartsData, cardData, - selectedFilters); - // Save the selected filters to storage after applying - selectedFiltersStorage = List.from(selectedFilters); - }, - child: Text(context.translate( - 'Apply Filter', - 'تطبيق الفلتر', - )), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, + applyFilters(context, filters, chartsData, cardData, + selectedFilters); + selectedFiltersStorage = List.from(selectedFilters); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12), // Added padding + child: FittedBox( + fit: BoxFit + .scaleDown, // Ensures text resizes if necessary + child: Text( + context.translate( + 'Apply Filter', 'تطبيق الفلتر'), + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + overflow: + TextOverflow.ellipsis, // Prevents wrapping + maxLines: 1, // Ensures single line + ), + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), ), ), ], @@ -646,9 +734,18 @@ class _ChartScreen1State extends ConsumerState { @override Widget build(BuildContext context) { + final locale = ref.watch(localeProvider); + final localeNotifier = ref.read(localeProvider.notifier); ref.listen(localeProvider, (previous, next) { final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null - fetchChartData(widget.dataSets, localeCode); + // fetchChartData(widget.dataSets, localeCode); + + fetchChartData(widget.dataSets, localeCode).then((_) { + if (_tabsData.isNotEmpty) { + // Call onTabSelected for the first tab + onTabSelected(_tabsData[0]['id']); + } + }); }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; @@ -660,6 +757,23 @@ class _ChartScreen1State extends ConsumerState { appBar: AppBar( backgroundColor: color, elevation: 0, + actions: [ + // IconButton( + // onPressed: () {}, + // icon: const Icon(Icons.toggle_off_outlined), + // ), + MyToggle( + // key: toggleKey, + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ), + ], leading: IconButton( icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () { @@ -753,8 +867,12 @@ class _ChartScreen1State extends ConsumerState { ), ), SizedBox(width: 5), - Icon(Icons.bookmark_add_outlined, - color: Colors.white, size: 18), + Image.asset( + UaeNumbersAssetPath.bookmarksUae, + color: Colors.white, + width: 24, + height: 24, + ), ], ), SizedBox(width: 10), @@ -771,8 +889,12 @@ class _ChartScreen1State extends ConsumerState { ), ), SizedBox(width: 5), - Icon(Icons.share_sharp, - color: Colors.white, size: 18), + Image.asset( + UaeNumbersAssetPath.shareUae, + color: Colors.white, + width: 24, + height: 24, + ), ], ), SizedBox( @@ -901,10 +1023,13 @@ class _ChartScreen1State extends ConsumerState { Flexible( fit: FlexFit.loose, child: FittedBox( - // fit: BoxFit.contain, child: Text( '${chart_heading ?? 'NA'}', textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully style: TextStyle( fontSize: 11, fontWeight: FontWeight.w400, @@ -993,19 +1118,28 @@ class _ChartScreen1State extends ConsumerState { }, ), const SizedBox(height: 5), - Text( - '${chart_heading ?? 'NA'}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Colors.black87, + Flexible( + fit: FlexFit.loose, + child: FittedBox( + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), ), ), Text( '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', style: const TextStyle( - fontSize: 11, color: Colors.grey), + fontSize: 10, color: Colors.grey), ), Flexible( fit: FlexFit.loose, @@ -1049,10 +1183,13 @@ class _ChartScreen1State extends ConsumerState { Flexible( fit: FlexFit.loose, child: FittedBox( - // fit: BoxFit.contain, child: Text( '${chart_heading ?? 'NA'}', textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully style: TextStyle( fontSize: 11, fontWeight: FontWeight.w400, @@ -1124,14 +1261,17 @@ class _ChartScreen1State extends ConsumerState { Flexible( fit: FlexFit.loose, child: FittedBox( - fit: BoxFit.contain, child: Text( - apiService.formatAmount( - data['lastYearValue']), - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Colors.black87, ), ), ), diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 05755d86..ca303309 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -72,27 +72,34 @@ class ChartWidget extends StatelessWidget { }).toList(); } - List generateIndicators(dynamic chartData, groupByValue) { + List generateIndicators(dynamic chartData, String groupByValue) { return chartData['response'].asMap().entries.map((entry) { int index = entry.key; var data = entry.value; Color color = Colors.primaries[index % Colors.primaries.length]; String title = data['ObsKey'][groupByValue] ?? ''; + String shortTitle = + title.length > 10 ? '${title.substring(0, 10)}…' : title; + return Row( mainAxisSize: MainAxisSize.min, children: [ Container( - width: 16, - height: 16, + width: 10, + height: 10, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), ), SizedBox(width: 8), - Text( - title, - style: TextStyle(fontSize: 14), + Tooltip( + message: title, // Full text on hover + child: Text( + shortTitle, + style: TextStyle(fontSize: 12), + overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow + ), ), ], ); @@ -299,7 +306,7 @@ class ChartWidget extends StatelessWidget { touchTooltipData: BarTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.8), fitInsideHorizontally: true, - fitInsideVertically: false, + fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(8), tooltipMargin: 16, getTooltipItem: @@ -541,11 +548,21 @@ class ChartWidget extends StatelessWidget { ), ), ]); - case 'line_trend_2': + case 'line_trend_population': + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; Map groupColorMap = {}; int colorIndex = 0; for (String group in groupByValues) { - groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; colorIndex++; } @@ -565,15 +582,17 @@ class ChartWidget extends StatelessWidget { ); } - // Find the maximum year and calculate the range for the last 5 years - int maxYear = years.reduce((a, b) => a > b ? a : b); - int minYear = maxYear - 5; + if (years.isNotEmpty) { + // Find the latest available year + int latestYear = years.reduce((a, b) => a > b ? a : b); + selectedYears.add(latestYear); // Include the latest year in selection + } - // Filter chart data to only include entries within the last 5 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; - return year >= minYear && year <= maxYear; + return selectedYears.contains(year); }).toList(); Set uniqueXValues = filteredData @@ -583,7 +602,7 @@ class ChartWidget extends StatelessWidget { // Generate line bars for the chart List lineBars = - lineBarsData2(filteredData, groupByValues, groupByKey); + lineBarsData(filteredData, groupByValues, groupByKey); return Column(children: [ Text( @@ -600,12 +619,124 @@ class ChartWidget extends StatelessWidget { child: LineChart(LineChartData( lineTouchData: lineTouchData1(), gridData: gridData(), - titlesData: titlesData2(uniqueXValues), + titlesData: titlesData1(uniqueXValues), borderData: borderData(), lineBarsData: lineBars, minX: uniqueXValues.reduce((a, b) => a < b ? a : b), maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), ))), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 12, + runSpacing: 8, + children: groupByValues.map((group) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + color: groupColorMap[group], + ), + SizedBox(width: 6), + Text( + group, + style: TextStyle(fontSize: 14), + ), + ], + ); + }).toList(), + ), + ), + ]); + case 'line_trend_2': + final List uniqueColorsLine_trend_2 = [ + Color(0xFF6097CD), + Color(0xFFD086A7), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; + Map groupColorMap = {}; + int colorIndex = 0; + for (String group in groupByValues) { + groupColorMap[group] = uniqueColorsLine_trend_2[ + colorIndex % uniqueColorsLine_trend_2.length]; + colorIndex++; + } + + // Extract all years from the chart data + List years = (chartData['response'] as List) + .map((entry) => + int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0) + .toList(); + + if (years.isEmpty) { + // Return an empty chart if no data + return LineChart( + LineChartData( + titlesData: FlTitlesData(show: false), + lineBarsData: [], + ), + ); + } + + if (years.isNotEmpty) { + // Find the latest available year + int latestYear = years.reduce((a, b) => a > b ? a : b); + selectedYears.add(latestYear); // Include the latest year in selection + } + +// Filter chart data to include only the selected years + List filteredData = + (chartData['response'] as List).where((entry) { + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; + return selectedYears.contains(year); + }).toList(); + + Set uniqueXValues = filteredData + .map( + (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) + .toSet(); + + // Generate line bars for the chart + List lineBars = lineBarsData2(filteredData); + + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, // Enable horizontal scrolling + padding: const EdgeInsets.only(right: 40), + child: SizedBox( + width: (uniqueXValues.length * 80) + + 80, // Adjust width dynamically + child: LineChart( + LineChartData( + lineTouchData: lineTouchData1(), + gridData: gridData(), + titlesData: titlesData1(uniqueXValues), + borderData: borderData(), + lineBarsData: lineBars, + minX: uniqueXValues.reduce((a, b) => a < b ? a : b), + maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), + ), + ), + ), + ), + ), ]); case 'bar_chart': // Extract groupBy values and their corresponding y-axis values @@ -697,6 +828,106 @@ class ChartWidget extends StatelessWidget { ), )) ]); + case 'bar_chart_horizontal': + // Extract groupBy values and their corresponding y-axis values + List xAxisData = []; + List yAxisData = []; + + for (var entry in chartData['response']) { + var xValue = entry['ObsKey'][groupByKey]; + var yValue = entry['ObsValue']['Value']; + if (xValue != null && yValue != null) { + xAxisData.add(xValue.toString()); + yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); + } + } + int rotationTurns = 1; + return Column(children: [ + Text( + chartData['chart_heading'] ?? '', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + // Chart + Expanded( + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + : 10, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData(enabled: true), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 + : 1), + getTitlesWidget: (value, meta) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Transform.rotate( + angle: -45 * + (3.1415927 / 180), // Rotating by -45 degrees + alignment: Alignment.center, + child: Text( + xAxisData[value.toInt()], + style: TextStyle(fontSize: 12), + ), + ), + ); + } + return Container(); + }, + reservedSize: 60, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide right titles + ), + ), + gridData: FlGridData(show: false), + borderData: FlBorderData(show: false), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: Colors.blueAccent, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + ), + ), + ), + )) + ]); case 'fl_multi_bar': double _calculateChartWidth(dynamic chartData) { int totalBars = chartData['response']?.length ?? 0; @@ -712,6 +943,15 @@ class ChartWidget extends StatelessWidget { if (chartData['dataset'] == 'health_services') { crop = item['ObsKey']['SECTOR']; cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'general_education') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'higher_education') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; + } else if (chartData['dataset'] == 'labour_force') { + crop = item['ObsKey']['GENDER']; + cropType = item['ObsKey'][groupByKey]; } else { crop = item['ObsKey']['CROP']; cropType = item['ObsKey'][groupByKey]; @@ -781,6 +1021,12 @@ class ChartWidget extends StatelessWidget { barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( tooltipHorizontalAlignment: FLHorizontalAlignment.center, + tooltipRoundedRadius: 8, + fitInsideHorizontally: + true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, // Only show tooltip when touched getTooltipItem: (group, groupIndex, rod, rodIndex) { if (rod.toY == 0 || touchedGroupIndex == -1) { @@ -873,8 +1119,14 @@ class ChartWidget extends StatelessWidget { // Iterate over the chartData to group crops by CROP_TYPE for (var item in chartData['response']) { - String crop = item['ObsKey']['CROP']; - String cropType = item['ObsKey']['CROP_TYPE']; + String crop, cropType; + if (chartData['dataset'] == 'natural_reserves') { + crop = item['ObsKey']['NR_TYPE']; + cropType = item['ObsKey'][groupByKey]; + } else { + crop = item['ObsKey']['CROP']; + cropType = item['ObsKey'][groupByKey]; + } if (groupedCrops.containsKey(cropType)) { groupedCrops[cropType]!.add(crop); @@ -985,15 +1237,22 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 20, // Added space for rotated titles + reservedSize: 80, // Added space for rotated titles getTitlesWidget: (value, meta) { if (value < groupByValues.length) { String title = groupByValues.elementAt(value.toInt()); return Transform.rotate( - angle: -0.5, // Rotation in radians (~ -30 degrees) - child: Text( - title, - style: const TextStyle(fontSize: 12), + angle: -1.58, // Rotation in radians (~ -30 degrees) + child: Center( + child: SizedBox( + width: 80, + child: Text( + title, + style: const TextStyle(fontSize: 12), + softWrap: true, + maxLines: 2, + ), + ), ), ); } @@ -1010,7 +1269,7 @@ class ChartWidget extends StatelessWidget { show: true, border: const Border( // left: BorderSide(color: Colors.grey), - bottom: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.white), ), ), gridData: FlGridData( @@ -1084,47 +1343,25 @@ class ChartWidget extends StatelessWidget { ); } - List lineBarsData2(List filteredData, - Set groupByValues, String groupByKey) { + List lineBarsData2(List filteredData) { List lineBars = []; - print('filteredData $filteredData'); - print('filteredData222 $groupByValues'); - final List uniqueColors = [ - Color(0xFF648CBA), - Color(0xFF90B0D5), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; - // Create a map to assign colors to each group in order - Map groupColorMap = {}; - int colorIndex = 0; - for (String group in groupByValues) { - groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; - colorIndex++; - } - - print('Group-Color Map: $groupColorMap'); - - // Create a map to store the values by year and gender (M and F) + // Create a map to store population values by year and gender Map> yearGenderMap = {}; - // Populate the yearGenderMap with male and female values for (var entry in filteredData) { - int year = int.parse(entry['ObsKey']['TIME_PERIOD']); + int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'].toString()) ?? 0; String gender = entry['ObsKey']['GENDER']; - double value = double.parse(entry['ObsValue']['Value'].toString()); + double value = + double.tryParse(entry['ObsValue']['Value'].toString()) ?? 0.0; if (!yearGenderMap.containsKey(year)) { yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; } - // Assign value based on gender - if (gender == 'M') { + if (gender == 'Male') { yearGenderMap[year]!['M'] = value; - } else if (gender == 'F') { + } else if (gender == 'Female') { yearGenderMap[year]!['F'] = value; } } @@ -1205,7 +1442,14 @@ class ChartWidget extends StatelessWidget { // Helper functions for chart styles LineTouchData lineTouchData1() { return LineTouchData( - touchTooltipData: LineTouchTooltipData(), + touchTooltipData: LineTouchTooltipData( + // tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background + tooltipRoundedRadius: 8, + fitInsideHorizontally: true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, // Adds margin to prevent clipping + ), handleBuiltInTouches: true, ); } @@ -1534,6 +1778,12 @@ class ChartWidget extends StatelessWidget { toY: value, // Use the parsed value color: barColor, // Dynamic color width: 20, + // backDrawRodData: BackgroundBarChartRodData( + // show: true, + // toY: 400000, + // color: Colors.grey.shade300, + // ), + // ); }).toList(); diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index e77c3187..04eb0a0d 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -451,7 +451,7 @@ class _ProfileScreenState extends State { controller: _usernameController, focusNode: _focusNodes[0], decoration: InputDecoration( - hintText: _showHints[0] ? 'Enter the User Name' : null, + // hintText: _showHints[0] ? 'Enter the User Name' : null, hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -477,8 +477,8 @@ class _ProfileScreenState extends State { controller: _emailController, focusNode: _focusNodes[1], decoration: InputDecoration( - hintText: - _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, + // hintText: + // _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 20fb5f46..b74d62f2 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -354,14 +354,22 @@ class LoginRoute extends HookConsumerWidget { 'Email', 'بريد إلكتروني', ), - validator: FieldValidator.email(), + validator: (value) { + if (value == null || value.isEmpty) { + return context.translate('Required', 'مطلوب'); + } + return FieldValidator.email()(value); + }, imgPath: MiscIconAssetPath.person, controller: emailCtl, ), 15.verticalSpace, ThemedFormField( validator: (text) { - if (text!.length < 8) { + if (text == null || text.isEmpty) { + return context.translate('Required', 'مطلوب'); + } + else if (text.length < 8) { return 'The password must be at least 8 characters'; } return FieldValidator.password(minLength: 8)(text); @@ -462,16 +470,16 @@ class LoginRoute extends HookConsumerWidget { width: double.infinity, child: ElevatedButton( onPressed: () async { - final prefs = await SharedPreferences.getInstance(); - prefs.clear(); - final userId = 'guest'; - if (userId.isNotEmpty) { - await saveUserId(userId); - } - context.go('/myhomepage'); - //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), - }, - //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + final prefs = await SharedPreferences.getInstance(); + prefs.clear(); + final userId = 'guest'; + if (userId.isNotEmpty) { + await saveUserId(userId); + } + context.go('/myhomepage'); + //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + }, + //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), style: ButtonStyle( shape: WidgetStatePropertyAll( RoundedRectangleBorder( @@ -515,7 +523,7 @@ class LoginRoute extends HookConsumerWidget { ); final fcscBanner = Image.asset( BannerAssetPath.fcsc, - height: 56, + height: 40, ); final screenWidth = MediaQuery.of(context).size.width; final listViewHorizontalPadding = @@ -528,14 +536,16 @@ class LoginRoute extends HookConsumerWidget { 36.verticalSpace, Align( alignment: AlignmentDirectional.topEnd, - child: MyToggle(isOn: locale?.languageCode == 'en', + child: MyToggle( + isOn: locale?.languageCode == 'en', knobTextWhenOn: 'ع', knobTextWhenOff: 'EN', pathColorWhenOn: Colors.grey.shade300, pathColorWhenOff: Colors.grey.shade300, - onTap: (){ + onTap: () { ref.read(localeProvider.notifier).toggleLocale(); - },), + }, + ), ), 16.verticalSpace, helloAndPleaseLoginTexts, @@ -543,9 +553,9 @@ class LoginRoute extends HookConsumerWidget { form, 20.verticalSpace, dontHaveAnAccountRegisterBtn, - 36.verticalSpace, + 20.verticalSpace, continueAsGuestBtn, - 72.verticalSpace, + 25.verticalSpace, fcscBanner, ], ); 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 fbaff1ef..7ea353b1 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:io'; - import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -67,7 +66,7 @@ class _EditProfileState extends State { final _picker = ImagePicker(); File? _profileImage; String _avatarUrl = ''; - bool isPageLoad= false; + bool isPageLoad = false; // Regular expression to validate Full Name (no special characters) final RegExp _nameRegExp = RegExp(r'^[a-zA-Z\s]+$'); @@ -169,7 +168,6 @@ class _EditProfileState extends State { }, ); - print('userDetails: $userDetailsResponse'); setState(() { _usernameController.text = userDetailsResponse.data['uname'] ?? ''; @@ -199,7 +197,7 @@ class _EditProfileState extends State { if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = - 'https://pb.venbait.in/api/files/$collectionId/$recordId/$avatarFilename'; + 'https://pb.venbait.in/api/files/$collectionId/$recordId/$avatarFilename'; } else { _avatarUrl = ''; // Reset to default or empty } @@ -232,14 +230,14 @@ class _EditProfileState extends State { // await Future.delayed(Duration(seconds: 8)); // Example delay final XFile? pickedFile = - await _picker.pickImage(source: ImageSource.gallery); + await _picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { setState(() { _isLoading = true; // Start loading }); print(pickedFile); final String fileExtension = - pickedFile.path.split('.').last.toLowerCase(); + pickedFile.path.split('.').last.toLowerCase(); print('fileExtension $fileExtension'); if (fileExtension == 'jpg' || fileExtension == 'jpeg' || @@ -265,9 +263,13 @@ class _EditProfileState extends State { final DateTime today = DateTime.now(); final DateTime initialDate = _selectedDate ?? today.subtract( - const Duration(days: 365 * 18),); // Default to 18 years ago - final DateTime firstDate = today.subtract(const Duration( - days: 365 * 100,),); // Allow picking dates back to 100 years ago + const Duration(days: 365 * 18), + ); // Default to 18 years ago + final DateTime firstDate = today.subtract( + const Duration( + days: 365 * 100, + ), + ); // Allow picking dates back to 100 years ago final DateTime lastDate = today; // Allow picking dates up to today // Updated Date format to DD/MM/YYYY @@ -336,19 +338,21 @@ class _EditProfileState extends State { // 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 fields to the request - request.fields['country_region'] = countryRegion; // Only update country here + request.fields['country_region'] = + countryRegion; // Only update country here // If profile image exists, add it if (_profileImage != null) { - request.files.add(await http.MultipartFile.fromPath( - 'avatar', - _profileImage!.path, - ),); - + request.files.add( + await http.MultipartFile.fromPath( + 'avatar', + _profileImage!.path, + ), + ); } // Add headers (e.g., authorization) @@ -360,7 +364,6 @@ class _EditProfileState extends State { print('ShowConfirmationuser response - $response '); // Handle response if (response.statusCode == 200) { - _resetFormFields(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Profile updated successfully!')), @@ -370,8 +373,8 @@ class _EditProfileState extends State { } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: - Text('Failed to update profile: ${response.statusCode}'),), + content: Text('Failed to update profile: ${response.statusCode}'), + ), ); } } catch (error) { @@ -432,464 +435,498 @@ class _EditProfileState extends State { padding: const EdgeInsets.all(20.0), child: isPageLoad ? Center( - child: CircularProgressIndicator(), - ) + child: CircularProgressIndicator(), + ) : Column( - children: [ - // CircleAvatar( - // radius: 50, - // backgroundImage: _profileImage != null - // ? FileImage( - // _profileImage!) // If a local file is selected - // : _avatarUrl.isNotEmpty - // ? NetworkImage(_avatarUrl) // Load from URL - // : AssetImage( - // "assets/edit_profile/profile.png") - // as ImageProvider, - // - // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, - // - // child: - // Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, // Call `_pickImage` on tap - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ), + children: [ + // CircleAvatar( + // radius: 50, + // backgroundImage: _profileImage != null + // ? FileImage( + // _profileImage!) // If a local file is selected + // : _avatarUrl.isNotEmpty + // ? NetworkImage(_avatarUrl) // Load from URL + // : AssetImage( + // "assets/edit_profile/profile.png") + // as ImageProvider, + // + // //backgroundImage: NetworkImage(_avatarUrl) as ImageProvider, + // + // child: + // Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, // Call `_pickImage` on tap + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), + // Stack( + // alignment: Alignment.center, + // children: [ + // // FutureBuilder to load the image + // FutureBuilder( + // future: _loadProfileImage(), + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.waiting) { + // // While the image is loading, show a progress indicator + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else if (snapshot.hasError || snapshot.data == null) { + // // If there's an error or no image, show an error icon + // return CircleAvatar( + // radius: 50, + // child: CircularProgressIndicator(), + // ); + // } else { + // // Display the loaded image + // return CircleAvatar( + // radius: 50, + // backgroundImage: snapshot.data, + // child: Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ); + // } + // }, + // ), + // ], + // ), - - // Stack( - // alignment: Alignment.center, - // children: [ - // // FutureBuilder to load the image - // FutureBuilder( - // future: _loadProfileImage(), - // builder: (context, snapshot) { - // if (snapshot.connectionState == ConnectionState.waiting) { - // // While the image is loading, show a progress indicator - // return CircleAvatar( - // radius: 50, - // child: CircularProgressIndicator(), - // ); - // } else if (snapshot.hasError || snapshot.data == null) { - // // If there's an error or no image, show an error icon - // return CircleAvatar( - // radius: 50, - // child: CircularProgressIndicator(), - // ); - // } else { - // // Display the loaded image - // return CircleAvatar( - // radius: 50, - // backgroundImage: snapshot.data, - // child: Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ); - // } - // }, - // ), - // ], - // ), - - Stack( - alignment: Alignment.center, - children: [ - CircleAvatar( - radius: 50, - backgroundImage: _profileImage != null - ? FileImage(_profileImage!) // If a local file is selected - : _avatarUrl.isNotEmpty - ? NetworkImage(_avatarUrl) // Load from URL - : AssetImage('assets/edit_profile/profile.png') as ImageProvider, - child: _avatarUrl.isNotEmpty - ? FutureBuilder( - future: _loadImage(_avatarUrl), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - return Center( - child: Icon(Icons.error), - ); - } else { - return Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, + Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: 50, + backgroundImage: _profileImage != null + ? FileImage( + _profileImage!) // If a local file is selected + : _avatarUrl.isNotEmpty + ? NetworkImage( + _avatarUrl) // Load from URL + : AssetImage( + 'assets/edit_profile/profile.png') + as ImageProvider, + child: _avatarUrl.isNotEmpty + ? FutureBuilder( + future: _loadImage(_avatarUrl), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Center( + child: + CircularProgressIndicator(), + ); + } else if (snapshot.hasError) { + return Center( + child: Icon(Icons.error), + ); + } else { + return Align( + alignment: + Alignment.bottomRight, + child: GestureDetector( + onTap: + _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: + Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), + ), + ), + ); + } + }, + ) + : Align( + alignment: Alignment.bottomRight, + child: GestureDetector( + onTap: + _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), + ), + ), ), + ), + if (_isLoading) + Positioned( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation( + Colors.grey, ), ), - ); - } - }, - ) - : Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, ), - ), - ), + ], ), - ), - if (_isLoading) - Positioned( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.grey,), - ), - ), - ], - ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.register_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey,), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - // hintText: _showHints[0] ? 'Mohammad Hassan' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.email_id, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey,), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.full_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey,), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - enabled: false, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - - //RegExp(r"^[a-zA-Z\s]+$"); - final nameRegex = RegExp( - r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$",); - if (!nameRegex.hasMatch(value)) { - return 'Invalid Characters'; - } - return null; - }, - controller: _fullNameController, - focusNode: _focusNodes[2], - decoration: InputDecoration( - // hintText: _showHints[2] ? 'Mohammad' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // counterText: '', - enabled: !_isProfileCompleted, - ), - maxLength: - 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - 'Date of Birth*', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey,), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _dateController, - focusNode: _focusNodes[3], - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - // hintText: _showHints[3] ? 'Select your Date of Birth' : null, - hintStyle: TextStyle(color: Colors.grey), - suffixIcon: const Icon(Icons.arrow_drop_down_sharp), - enabled: !_isProfileCompleted, - ), - readOnly: true, - onTap: _pickDate, - validator: _validateDob, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.region, - style: TextStyle( - fontSize: 16, fontWeight: FontWeight.w500,), - ), - ], - ), - SizedBox(height: 10), - DropdownButtonFormField( - value: _selectedCountry, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - labelText: 'Select', - ), - items: _countries - .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - ),) - .toList(), - onChanged: (String? newValue) { - setState(() { - _selectedCountry = newValue; - }); - }, - validator: _validateDropdown, - ), - SizedBox(height: 20), - if (!_isProfileCompleted) // Conditional rendering - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + SizedBox(height: 20), Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: - showError ? Colors.red : Colors.grey, - width: 1.5, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox(height: 10), - Text.rich( - TextSpan( - text: AppLocalizations.of(context)! - .agree, - style: - TextStyle(color: Colors.black), - children: [ - TextSpan( - text: AppLocalizations.of( - context,)! - .terms_conditions, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Terms & Conditions tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context,)! - .t_and, - style: TextStyle( - color: Colors.black,), - ), - TextSpan( - text: AppLocalizations.of( - context,)! - .privacy_policy, - style: TextStyle( - color: Colors.blue, - decoration: - TextDecoration.underline, - ), - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Add action for Privacy Policy tap - }, - ), - TextSpan( - text: AppLocalizations.of( - context,)! - .conditions, - style: TextStyle( - color: Colors.black,), - ), - ], - ), - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.visible, - softWrap: true, - ), - ], + Text( + AppLocalizations.of(context)!.register_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, ), ), ], ), - if (showError) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: - const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, - ), - ), + SizedBox(height: 10), + TextFormField( + controller: _usernameController, + focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Mohammad Hassan' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.email_id, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _emailController, + focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: _showHints[1]? 'mohammad.hassan@fcsc.gov.ae': null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.full_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + enabled: false, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + + //RegExp(r"^[a-zA-Z\s]+$"); + final nameRegex = RegExp( + r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$", + ); + if (!nameRegex.hasMatch(value)) { + return 'Invalid Characters'; + } + return null; + }, + controller: _fullNameController, + focusNode: _focusNodes[2], + decoration: InputDecoration( + // hintText: _showHints[2] ? 'Mohammad' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // counterText: '', + enabled: !_isProfileCompleted, + ), + maxLength: + 40, // Set the maximum length to 20 characters + maxLengthEnforcement: + MaxLengthEnforcement.enforced, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.dob, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _dateController, + focusNode: _focusNodes[3], + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + // hintText: _showHints[3] ? 'Select your Date of Birth' : null, + hintStyle: TextStyle(color: Colors.grey), + suffixIcon: + const Icon(Icons.arrow_drop_down_sharp), + enabled: !_isProfileCompleted, + ), + readOnly: true, + onTap: _pickDate, + validator: _validateDob, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.region, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 10), + DropdownButtonFormField( + value: _selectedCountry, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + labelText: 'Select', + ), + items: _countries + .map( + (item) => DropdownMenuItem( + value: item, + child: Text(item), + ), + ) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCountry = newValue; + }); + }, + validator: _validateDropdown, + ), + SizedBox(height: 20), + if (!_isProfileCompleted) // Conditional rendering + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: showError + ? Colors.red + : Colors.grey, + width: 1.5, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Text.rich( + TextSpan( + text: AppLocalizations.of( + context)! + .agree, + style: TextStyle( + color: Colors.black), + children: [ + TextSpan( + text: AppLocalizations.of( + context, + )! + .terms_conditions, + style: TextStyle( + color: Colors.blue, + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Terms & Conditions tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .t_and, + style: TextStyle( + color: Colors.black, + ), + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .privacy_policy, + style: TextStyle( + color: Colors.blue, + decoration: + TextDecoration + .underline, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Add action for Privacy Policy tap + }, + ), + TextSpan( + text: AppLocalizations.of( + context, + )! + .conditions, + style: TextStyle( + color: Colors.black, + ), + ), + ], + ), + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.visible, + softWrap: true, + ), + ], + ), + ), + ], + ), + if (showError) + Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 10.0), + child: Text( + 'Please agree to terms and conditions', + style: TextStyle( + color: Colors.red[700], + fontSize: 12, + ), + ), + ), + ], + ), ], ), + SizedBox(height: 20), + Center( + child: GestureDetector( + onTap: () { + final email = _emailController.text; + context.go('/createNewPw/$userId/$email?key=editProfile'); + }, + child: Text( + AppLocalizations.of(context)! + .change_password, + style: TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ), + ), + SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () { + showConfirmationDialog(context); + }, + icon: Icon( + Icons.save, + color: Colors.white, + ), + label: Text( + AppLocalizations.of(context)!.save, + style: TextStyle(color: Colors.white), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), ], ), - SizedBox(height: 20), - Center( - child: GestureDetector( - onTap: () { - final email = _emailController.text; - context.go('/createNewPw/$userId/$email'); - }, - child: Text( - AppLocalizations.of(context)!.change_password, - style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline,), - ), - ), - ), - SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () { - showConfirmationDialog(context); - }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - AppLocalizations.of(context)!.save, - style: TextStyle(color: Colors.white), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ], - ), ), ), ), @@ -902,8 +939,6 @@ class _EditProfileState extends State { } } - - class ConfirmationDialog extends StatelessWidget { const ConfirmationDialog({super.key}); @override @@ -936,7 +971,7 @@ class ConfirmationDialog extends StatelessWidget { TextSpan( text: 'Country', style: - TextStyle(fontWeight: FontWeight.w700), // Bold for "name" + TextStyle(fontWeight: FontWeight.w700), // Bold for "name" ), TextSpan( text: ' or ', @@ -944,7 +979,8 @@ class ConfirmationDialog extends StatelessWidget { TextSpan( text: 'Profile Image', style: TextStyle( - fontWeight: FontWeight.w700,), // Bold for "date of birth" + fontWeight: FontWeight.w700, + ), // Bold for "date of birth" ), TextSpan( text: '.', @@ -1006,4 +1042,4 @@ class ConfirmationDialog extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 6394c770..9a51ce96 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -748,14 +748,31 @@ class _BaseScaffoldState extends ConsumerState { ), ListTile( leading: Icon(Icons.book), - title: const Text('User Guide'), + // title: const Text('User Guide'), + title: Text(AppLocalizations.of(context)!.guide_title), onTap: () => context.go('/user-guide'), ), - ListTile( - leading: Icon(Icons.logout), - title: const Text('Logout'), - onTap: () => logout(), - ), + if (userId != 'guest') + ListTile( + leading: Icon(Icons.logout), + // title: const Text('Logout'), + title: Text(AppLocalizations.of(context)!.logout), + onTap: () => logout(), + ), + if (userId == 'guest') + ListTile( + leading: Icon(Icons.login), + // title: const Text('Login'), + title: Text(AppLocalizations.of(context)!.login_title), + onTap: () => context.go('/login'), + ), + if (userId == 'guest') + ListTile( + leading: Icon(Icons.app_registration), + // title: const Text('Register'), + title: Text(AppLocalizations.of(context)!.register_title), + onTap: () => context.go('/register'), + ), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 0506e766..073fd01c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" #version: 0.5.10 -version: 1.0.2+3 +version: 1.0.5+6 environment: sdk: ">=3.2.3 <4.0.0" @@ -115,6 +115,8 @@ flutter: - assets/edit_profile/ - assets/splash_screen/ - assets/app_tour/ + - assets/icons/uae_numbers/bookmarks.png + - assets/icons/uae_numbers/share.png fonts: - family: Segoe From 9b8e50283f6fb8b94398e76a6c7dd5ec9eaea18b Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Mon, 3 Feb 2025 15:02:14 +0530 Subject: [PATCH 12/18] App Tour Bug and Bookmark Frontend added --- .run/main.dart.run.xml | 2 +- assets/app_tour/right_down.png | Bin 0 -> 4302 bytes lib/config/my_router.dart | 5 + lib/l10n/intl_en.arb | 4 +- .../Screens/charts/screens/chart_screen.dart | 597 +++++++++++++++++- .../tab_routes/home_route.dart | 120 ++-- .../drawer_routes/Drawer Items/bookmark.dart | 194 ++++++ .../Drawer Items/edit_profile.dart | 2 +- .../drawer_routes/custom_drawer_routes.dart | 7 +- pubspec.lock | 52 +- pubspec.yaml | 2 +- 11 files changed, 881 insertions(+), 104 deletions(-) create mode 100644 assets/app_tour/right_down.png create mode 100644 lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4a58a7c9..4767aff8 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/assets/app_tour/right_down.png b/assets/app_tour/right_down.png new file mode 100644 index 0000000000000000000000000000000000000000..324c148cdb056c6a17f43e0574533d9669cbf052 GIT binary patch literal 4302 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 literal 0 HcmV?d00001 diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 1678f6e3..dc0af316 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -316,6 +316,7 @@ import '../presentation/Screens/demo_home.dart'; import '../presentation/Screens/profilepage.dart'; import '../presentation/routes/auth_routes/login_route.dart'; import '../presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/bookmark.dart'; import '../presentation/routes/drawer_routes/Drawer Items/edit_profile.dart'; import '../presentation/routes/drawer_routes/Drawer Items/feedback.dart'; import '../presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart'; @@ -470,6 +471,10 @@ final GoRouter router = GoRouter( path: '/editProfile', builder: (context, state) => EditProfile(), ), + GoRoute( + path: '/bookmark', + builder: (context, state) => BookMark(), + ), GoRoute( path: '/manageuser', builder: (context, state) => ManageUserRouter(), diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index dac0f961..a37e9e39 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -90,6 +90,6 @@ "economy": "Access detailed statistics and drill-down pages for KPIs in the Economy category to uncover key trends and metrics.", "bookMark": "Bookmark important KPIs or datasets for quick and easy access anytime through the Bookmarks section.", "toggle": "Switch between English and Arabic using the language toggle at the top-right corner for a bilingual experience.", - "tour_navBar": "Use the navigation bar to quickly access Profile, Feedback, User Guide, Notifications, and other key app features.", - "mainMenu": "Access dedicated pages and additional features by selecting options from the Main Menu." + "tour_navBar": "Access dedicated pages and additional features by selecting options from the Main Menu.", + "mainMenu": "Use the navigation bar to quickly access Profile, Feedback, User Guide, Notifications, and other key app features." } diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 291e7e1d..566b834f 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -2,11 +2,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart'; import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; import '../filters/search_filter_helper.dart'; class ChartScreen1 extends ConsumerStatefulWidget { @@ -50,6 +53,12 @@ class _ChartScreen1State extends ConsumerState { int _activeTabIndex = 0; dynamic _tabsData = []; + late List marriageTargets; + late List previousMarriageTargets; + late TutorialCoachMark tutorialCoachMark; + final GlobalKey chartKey = GlobalKey(); + final GlobalKey bookMarkKey = GlobalKey(); + final GlobalKey cardKey= GlobalKey(); @override void initState() { @@ -64,6 +73,586 @@ class _ChartScreen1State extends ConsumerState { onTabSelected(_tabsData[0]['id']); } }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _startTutorialAfterRender(); + }); + } + + void handleSkip() { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + ref.read(chartsTourProvider.notifier).state = true; + ref.read(previousChartsTourProvider.notifier).state = true; + ref.read(homeTourProvider.notifier).state = true; + ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(scaffoldTourProvider.notifier).state = true; + ref.read(previousScaffoldTourProvider.notifier).state = true; + context.go('/myhomepage'); + } + + void _kpiTutorialCoachMark() { + debugPrint('Not Initialized and Started'); + // _calculateMarkPosition(); + _initmarriageTargets(); + debugPrint('Initialized and Started'); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + + pulseEnable: false, + hideSkip: true, + targets: marriageTargets, + onFinish: () { + context.go('/myhomepage'); + debugPrint('Marriage Tutorial Finished'); + }, + + )..show(context: context); + } + + + void _startTutorialAfterRender() { + final chartTour = ref.watch(chartsTourProvider); + final previousChartTour = ref.watch(previousChartsTourProvider); + debugPrint('render problem'); + if (cardKey.currentContext != null && !chartTour) { + _kpiTutorialCoachMark(); + } else if(cardKey.currentContext != null && !previousChartTour) { + _previousKpiTutorial(); + } + else if (!chartTour || !previousChartTour){ + Future.delayed(Duration(milliseconds: 100), _startTutorialAfterRender); + }else{ + return; + } + + } + + void _previousKpiTutorial() { + _initpreviousTargets(); + tutorialCoachMark = TutorialCoachMark( + paddingFocus: 0, + useSafeArea: true, + pulseEnable: false, + hideSkip: true, + targets: previousMarriageTargets, + onFinish: () { + context.go('/myhomepage'); + debugPrint('Previous Marriage Tutorial Finished'); + }, + + )..show(context: context); + + } + void _initmarriageTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + marriageTargets=[ + TargetFocus( + identify: 'cardKey', + keyTarget: cardKey, + shape: ShapeLightFocus.RRect, + paddingFocus: 0, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + alignment: ContentAlign.top, + gap:55, + space: 0, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.3, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + paddingFocus: 1, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + alignment: ContentAlign.bottom, + gap: 53, + space: 50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top:0), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + + ]; + } + void _initpreviousTargets(){ + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; + previousMarriageTargets=[ + TargetFocus( + identify: 'BookMarkKey', + keyTarget: bookMarkKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.bookMark, + space: 50, + alignment: ContentAlign.bottom, + gap: 23, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.60, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '4/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(chartsTourProvider.notifier).state=true; + ref.read(scaffoldTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(left:0, top: 10), + align: ContentAlign.right, + child: Container( + width: screenWidth/4, + height: screenHeight, + child: Stack( + children: [ + Image.asset( + 'assets/app_tour/leftDown.png', + fit: BoxFit.contain, + ), + // Positioned( + // top: 0, + // left: MediaQuery.of(context).size.width* 0.5, + // child: SizedBox( + // width: 50, + // height: 100, + // child: Image.asset( + // 'assets/app_tour/bookmark2.png', + // fit: BoxFit.contain, + // ), + // ), + // ), + ], + ), + ), + ), + ], + ), + TargetFocus( + identify: 'cardKey', + keyTarget: cardKey, + shape: ShapeLightFocus.RRect, + contents: [ + createTargetContent( + text: AppLocalizations.of(context)!.kpiCards, + space: 0, + alignment: ContentAlign.top, + gap:50, + ), + TargetContent( + align: ContentAlign.bottom, + child: SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height*0.30, + child: Stack( + children: [ + Positioned( + bottom: 35, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + Column( + children: [ + Text( + '3/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + ref.read(previousChartsTourProvider.notifier).state=true; + ref.read(previousHomeTourProvider.notifier).state=false; + tutorialCoachMark.finish(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color:Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.previous(); + }, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + TargetContent( + padding: EdgeInsets.only(bottom: 0,top : 0,), + align: ContentAlign.top, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height:MediaQuery.of(context).size.height*0.69 , + child: Stack( + children: [ + Positioned( + left: MediaQuery.of(context).size.width*0.40, + bottom: 0, + child: Image.asset( + 'assets/app_tour/leftUp.png', + width: 100, + height: 90, + ), + ), + ] + ), + + ), + ), + ], + ), + + + ]; } void _scrollToIndex(int index) { @@ -172,8 +761,8 @@ class _ChartScreen1State extends ConsumerState { filterDataSet = filterData.entries .map((entry) => {'key': entry.key, 'value': entry.value}) .toList(); - print("filterDataf1:- $filterData"); - print("filterDataf11:- $filterDataSet"); + // print("filterDataf1:- $filterData"); + // print("filterDataf11:- $filterDataSet"); } else { filterDataSet = data['filterData'] ?? []; print('filterData not found!'); @@ -741,6 +1330,7 @@ class _ChartScreen1State extends ConsumerState { mainAxisAlignment: MainAxisAlignment.end, children: [ Row( + key: bookMarkKey, children: [ Text( context.translate( @@ -753,7 +1343,7 @@ class _ChartScreen1State extends ConsumerState { ), ), SizedBox(width: 5), - Icon(Icons.bookmark_add_outlined, + Icon(Icons.bookmarks_outlined, color: Colors.white, size: 18), ], ), @@ -838,6 +1428,7 @@ class _ChartScreen1State extends ConsumerState { Padding( padding: const EdgeInsets.all(3.34), child: GridView.builder( + key:cardKey, itemCount: cardData.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), 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 ab7cc0f0..6d6d6927 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 @@ -270,7 +270,7 @@ class EconomyStatsState extends ConsumerState { ref.read(homeTourProvider.notifier).state = true; ref.read(chartsTourProvider.notifier).state = false; debugPrint('Home Tutorial Finished'); - context.push('/marriages'); + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } else if (!previousHomeTour) { @@ -284,7 +284,7 @@ class EconomyStatsState extends ConsumerState { onFinish: () { ref.read(previousHomeTourProvider.notifier).state = true; debugPrint('Previous Home Tutorial Finished'); - context.push('/marriages'); + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } @@ -292,6 +292,7 @@ class EconomyStatsState extends ConsumerState { } void _initTarget(){ + final double screenWidth = MediaQuery.of(context).size.width; homeTargets=[ TargetFocus( identify: 'cardTopicKey', @@ -301,8 +302,8 @@ class EconomyStatsState extends ConsumerState { createTargetContent( text: AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, - gap: 45, - space: 40, + gap:0, + space: 44, ), TargetContent( align: ContentAlign.bottom, @@ -376,24 +377,19 @@ class EconomyStatsState extends ConsumerState { ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], @@ -407,7 +403,7 @@ class EconomyStatsState extends ConsumerState { text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), + space: 44,), TargetContent( align: ContentAlign.bottom, @@ -425,7 +421,7 @@ class EconomyStatsState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip, + onPressed: () =>handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -494,24 +490,19 @@ class EconomyStatsState extends ConsumerState { ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width , - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], @@ -520,6 +511,7 @@ class EconomyStatsState extends ConsumerState { } void _initPreviousTarget(){ + final double screenWidth = MediaQuery.of(context).size.width; previousHomeTargets=[ TargetFocus( identify: 'cardsKey', @@ -530,7 +522,7 @@ class EconomyStatsState extends ConsumerState { text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), + space: 44,), TargetContent( align: ContentAlign.bottom, child: @@ -617,24 +609,19 @@ class EconomyStatsState extends ConsumerState { ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], @@ -647,8 +634,8 @@ class EconomyStatsState extends ConsumerState { createTargetContent( text:AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, - gap: 45, - space: 40, + gap: 0, + space: 44, ), TargetContent( align: ContentAlign.bottom, @@ -724,24 +711,19 @@ class EconomyStatsState extends ConsumerState { ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart new file mode 100644 index 00000000..75e2ccbd --- /dev/null +++ b/lib/presentation/routes/drawer_routes/Drawer Items/bookmark.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; + + +class BookMark extends ConsumerStatefulWidget { + const BookMark({super.key}); + + @override + ConsumerState createState() => _BookMarkState(); +} + +class _BookMarkState extends ConsumerState { + // Example dynamic data + final List> dataList = [ + { + 'title': 'GDP(Constant)', + 'subtitle': '(2022)(AED)', + 'value': '1.62T', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Trade Value', + 'subtitle': '(Jan-Jan 2024)-AED', + 'value': '215.4B', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Electricity Production', + 'subtitle': '(2022) (GWh)', + 'value': '155,438', + 'valueColor': Color(0xFFAA8E83), + 'isBookmark': true + }, + { + 'title': 'Crude Oil Production', + 'subtitle': '(2022) (1000 b/d)', + 'value': '3,064', + 'valueColor': Color(0xFF80A8CD), + 'isBookmark': true + }, + { + 'title': 'Quantitiy of Export Oil', + 'subtitle': '(2022) (1000 b/d)', + 'value': '2717', + 'valueColor': Color(0xFF7DAFBC), + 'isBookmark': true + }, + { + 'title': 'Desalinated Water Production', + 'subtitle': '(2022) (MCM)', + 'value': '1823.8', + 'valueColor': Color(0xFF7DAFBC), + 'isBookmark': true + }, + ]; + + @override + Widget build(BuildContext context) { + final filteredList = dataList.where((item) => item['isBookmark'] == true) + .toList(); + return BaseScaffold( + title: Text('Bookmark'), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Two columns + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + mainAxisExtent: 100, + ), + itemCount: filteredList.length, + itemBuilder: (context, index) { + return _buildBox(filteredList[index], context); + }, + ), + ), + ); + } + + // Widget for creating a box + Widget _buildBox(Map data, BuildContext context) { + return GestureDetector( + onTap: () { + debugPrint("Box clicked: ${data['title']}"); + }, + child: Container( + width: MediaQuery + .of(context) + .size + .width * 0.4, + height: 100, + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: data['valueColor']), + ), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Align( + alignment: Alignment.center, + child: Text( + data['title'] ?? '', + style: TextStyle( + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + // Truncate long text + maxLines: 1, // Limit to 1 line + ), + ), + ), + GestureDetector( + onTap: () { + debugPrint("Icon clicked in: ${data['title']}"); + setState(() { + // Change isOpen to false when icon is clicked + int index = dataList.indexWhere( + (item) => item['title'] == data['title'],); + if (index != -1) { + dataList[index]['isBookmark'] = false; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Removed From Bookmark', + style: TextStyle(color: Color(0xFF2F692C), + fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Failed to Remove', + style: TextStyle(color: Color(0xFFEB5F24), + fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFF544C4C), + duration: Duration(seconds: 2), + ), + ); + } + }); + }, + child: Icon( + Icons.bookmarks_rounded, + color: Colors.black, + size: 20, + ), + ), + ], + ), + Text( + data['subtitle'] ?? '', + style: TextStyle( + fontWeight: FontWeight.w500, color: Colors.grey), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + Text( + data['value'] ?? '', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: data['valueColor'], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index fbaff1ef..c75e9333 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -683,7 +683,7 @@ class _EditProfileState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Text( - 'Date of Birth*', + AppLocalizations.of(context)!.dob, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 6394c770..48492269 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -189,7 +189,7 @@ class _BaseScaffoldState extends ConsumerState { .read(previousChartsTourProvider .notifier) .state = false; - context.push('/marriages'); + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, ), ), @@ -746,6 +746,11 @@ class _BaseScaffoldState extends ConsumerState { ), onTap: () => context.go('/manageuser'), ), + ListTile( + leading: Icon(Icons.collections_bookmark_rounded), + title: const Text('Bookmark'), + onTap: () => context.go('/bookmark'), + ), ListTile( leading: Icon(Icons.book), title: const Text('User Guide'), diff --git a/pubspec.lock b/pubspec.lock index 99484f5b..f56d59cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "72.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.7.0" analyzer_plugin: dependency: transitive description: @@ -210,10 +210,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" connectivity_plus: dependency: "direct main" description: @@ -306,10 +306,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+6.7.0" dart_style: dependency: transitive description: @@ -433,10 +433,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" + sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" url: "https://pub.dev" source: hosted - version: "0.70.2" + version: "0.69.2" flutter: dependency: "direct main" description: flutter @@ -837,18 +837,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -877,10 +877,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" mailer: dependency: "direct main" description: @@ -1268,7 +1268,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" smart_api_client: dependency: "direct main" description: @@ -1352,10 +1352,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1384,10 +1384,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1424,10 +1424,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" the_validator: dependency: "direct main" description: @@ -1592,10 +1592,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0506e766..68b42804 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.70.2 + fl_chart: ^0.69.2 tutorial_coach_mark: ^1.2.12 marquee: ^2.2.3 url_launcher: ^6.3.1 From f5bfb22d21283ea9b994c8a10e43c322ffe52111 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Mon, 3 Feb 2025 15:23:05 +0530 Subject: [PATCH 13/18] App Tour Bug Fix --- .../routes/bottom_bar_routes/tab_routes/home_route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6d6d6927..e308e7e3 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 @@ -653,7 +653,7 @@ class EconomyStatsState extends ConsumerState { children: [ ElevatedButton( onPressed: () { - tutorialCoachMark.skip(); + handleSkip(); debugPrint('Skip clicked'); }, style: ElevatedButton.styleFrom( From 1c76f19771e6b3f1999a16d989e2692ac880c968 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Tue, 4 Feb 2025 10:25:00 +0530 Subject: [PATCH 14/18] userguide and app tour update --- .../Screens/charts/screens/chart_screen.dart | 329 +++++++++----- .../tab_routes/home_route.dart | 412 +++++++++++------- .../Drawer Items/user_guide/faq_page.dart | 140 +++--- .../Drawer Items/user_guide/features.dart | 99 +++-- .../user_guide/getting_started.dart | 134 ++++-- .../Drawer Items/user_guide/user_guide.dart | 112 ++--- .../drawer_routes/custom_drawer_routes.dart | 195 ++++----- 7 files changed, 861 insertions(+), 560 deletions(-) diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 5068cb62..6ed756d6 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -155,6 +155,7 @@ class _ChartScreen1State extends ConsumerState { identify: 'cardKey', keyTarget: cardKey, shape: ShapeLightFocus.RRect, + radius: 8, paddingFocus: 0, contents: [ createTargetContent( @@ -171,40 +172,46 @@ class _ChartScreen1State extends ConsumerState { child: Stack( children: [ Positioned( - bottom: 35, + bottom: 55, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: EdgeInsets.only(right:10), + child:Text( '3/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), + ) , + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), ), - const SizedBox(height: 3), Row( children: [ Container( @@ -272,11 +279,113 @@ class _ChartScreen1State extends ConsumerState { ), ], ), + // TargetFocus( + // identify: 'chartKey', + // keyTarget: chartKey, + // shape: ShapeLightFocus.RRect, + // paddingFocus: 0, + // contents: [ + // createTargetContent( + // text: AppLocalizations.of(context)!.kpiCards, + // alignment: ContentAlign.top, + // gap:0, + // space: 0, + // ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: double.infinity, + // height: MediaQuery.of(context).size.height * + // 0.30, // Set an appropriate height for the Stack + // child: Stack( + // children: [ + // Positioned( + // bottom: 25, + // left: 16, + // right: 16, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ElevatedButton( + // onPressed: () => handleSkip(), + // style: ElevatedButton.styleFrom( + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12), + // ), + // side: const BorderSide(color: Colors.white), + // backgroundColor: Colors.transparent, + // elevation: 0, + // ), + // child: const Text( + // 'Skip', + // style: TextStyle( + // color: Colors.white, + // fontSize: 14, + // ), + // ), + // ), + // Column( + // children: [ + // Text( + // '5/7', + // style: const TextStyle( + // color: Colors.white, + // fontSize: 14, + // ), + // ), + // const SizedBox(height: 3), + // Row( + // children: [ + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all( + // color: Colors.white, width: 2.0), + // ), + // child: IconButton( + // icon: const Icon(Icons.arrow_back, + // color: Colors.white), + // onPressed: () { + // scrollToTargetThenShowTutorial(bookMarkKey); + // tutorialCoachMark.next(); + // }, + // ), + // ), + // const SizedBox(width: 10), + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: Color(0xFF7DAFBC), + // border: Border.all( + // color: Color(0xFF7DAFBC), width: 1.5), + // ), + // child: IconButton( + // icon: const Icon(Icons.arrow_forward, + // color: Colors.white), + // onPressed: () { + // tutorialCoachMark.next(); + // }, + // ), + // ), + // ], + // ), + // ], + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), TargetFocus( identify: 'BookMarkKey', keyTarget: bookMarkKey, shape: ShapeLightFocus.RRect, - paddingFocus: 1, + radius: 8, + paddingFocus: 12, contents: [ createTargetContent( text: AppLocalizations.of(context)!.bookMark, @@ -292,40 +401,45 @@ class _ChartScreen1State extends ConsumerState { child: Stack( children: [ Positioned( - bottom: 35, + bottom: 55, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child: Padding(padding: EdgeInsets.only(right: 10), + child: Text( '4/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( @@ -402,6 +516,7 @@ class _ChartScreen1State extends ConsumerState { ]; } + void _initpreviousTargets(){ final double screenWidth= MediaQuery.of(context).size.width; final double screenHeight= MediaQuery.of(context).size.height; @@ -410,6 +525,8 @@ class _ChartScreen1State extends ConsumerState { identify: 'BookMarkKey', keyTarget: bookMarkKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 12, contents: [ createTargetContent( text: AppLocalizations.of(context)!.bookMark, @@ -428,37 +545,43 @@ class _ChartScreen1State extends ConsumerState { bottom: 35, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text( '4/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( @@ -536,6 +659,8 @@ class _ChartScreen1State extends ConsumerState { identify: 'cardKey', keyTarget: cardKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 0, contents: [ createTargetContent( text: AppLocalizations.of(context)!.kpiCards, @@ -551,40 +676,44 @@ class _ChartScreen1State extends ConsumerState { child: Stack( children: [ Positioned( - bottom: 35, + bottom: 55, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align(alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( '3/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ),), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( 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 e308e7e3..908ed504 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 @@ -298,12 +298,14 @@ class EconomyStatsState extends ConsumerState { identify: 'cardTopicKey', keyTarget: cardTopicKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, contents: [ createTargetContent( text: AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, - gap:0, - space: 44, + gap: 0, + space: 43, ), TargetContent( align: ContentAlign.bottom, @@ -313,64 +315,71 @@ class EconomyStatsState extends ConsumerState { child: Stack( children: [ Positioned( - bottom: 35, - left: 16, - right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( - '1/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + bottom: 35, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right: 10), // Adjust the value as needed + child: Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), - Row( - children: [ - const SizedBox(width: 10), - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFF7DAFBC), - border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: IconButton( - padding: EdgeInsets.zero, - iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), - onPressed: () { - tutorialCoachMark.next(); - }, + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, ), ), - ], - ), - ], - ), - ], - ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ) ), ], ), @@ -392,19 +401,42 @@ class EconomyStatsState extends ConsumerState { ), ), ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: MediaQuery.of(context).size.width, + // height:MediaQuery.of(context).size.height*0.69 , + // child: Stack( + // children: [ + // Positioned( + // left: MediaQuery.of(context).size.width*0.4, + // top: _imageCardPosition.dy-29, + // child: Image.asset( + // 'assets/app_tour/down_right.png', + // width: 40, + // height: 90, + // ), + // ), + // ] + // ), + // + // ), + // ), ], ), TargetFocus( identify: 'cardsKey', keyTarget: cardsKey, shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, + // targetPosition:TargetPosition(), contents: [ createTargetContent( text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 44,), - + space: 43,), TargetContent( align: ContentAlign.bottom, child: @@ -417,37 +449,43 @@ class EconomyStatsState extends ConsumerState { bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () =>handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), + Align( + alignment: Alignment.bottomRight, + child:Padding(padding: EdgeInsets.only(right: 10), + child: + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ) ), - Column( + const SizedBox(height: 3), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '2/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), Row( children: [ Container( @@ -505,6 +543,27 @@ class EconomyStatsState extends ConsumerState { ), ), ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: MediaQuery.of(context).size.width , + // height:MediaQuery.of(context).size.height*0.69 , + // child: Stack( + // children: [ + // Positioned( + // left: MediaQuery.of(context).size.width*0.40, + // top: _imageTopicPosition.dy-29, + // child: Image.asset( + // 'assets/app_tour/right_down.png', + // width: 40, + // height: 90, + // ), + // ), + // ] + // ), + // + // ), + // ), ], ), ]; @@ -517,12 +576,14 @@ class EconomyStatsState extends ConsumerState { identify: 'cardsKey', keyTarget: cardsKey, shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, contents: [ createTargetContent( text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 44,), + space: 43,), TargetContent( align: ContentAlign.bottom, child: @@ -535,67 +596,79 @@ class EconomyStatsState extends ConsumerState { bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () =>handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), + Align( + alignment: Alignment.bottomRight, + child:Padding(padding: EdgeInsets.only(right: 10), + child: + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ) ), - Column( + SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '2/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), Row( + children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), - ), - child: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - tutorialCoachMark.next(); - }, - ), - ), - const SizedBox(width: 10), - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFF7DAFBC), - border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), - ), - child: IconButton( - iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), - onPressed: () { - ref.read(previousHomeTourProvider.notifier).state=true; - ref.read(chartsTourProvider.notifier).state=false; - tutorialCoachMark.finish(); - }, - ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(previousHomeTourProvider.notifier).state=true; + ref.read(chartsTourProvider.notifier).state=false; + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + tutorialCoachMark.finish(); + }, + ), + ), + ], ), ], ), @@ -630,12 +703,14 @@ class EconomyStatsState extends ConsumerState { identify: 'cardTopicKey', keyTarget: cardTopicKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, contents: [ createTargetContent( text:AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, gap: 0, - space: 44, + space: 43, ), TargetContent( align: ContentAlign.bottom, @@ -648,40 +723,45 @@ class EconomyStatsState extends ConsumerState { bottom: 35, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () { - handleSkip(); - debugPrint('Skip clicked'); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding:EdgeInsets.only(right: 10), + child: Text( '1/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ const SizedBox(width: 10), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart index 880584e7..f9aceff7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart @@ -1,7 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; + class FAQPage extends StatefulWidget { const FAQPage({super.key}); @@ -12,63 +17,90 @@ class FAQPage extends StatefulWidget { class _FAQPageState extends State { @override Widget build(BuildContext context) { - return BaseScaffold( - title: Text('FAQs', - style: TextStyle( - color:Color(0xFF7296BE), - fontWeight: FontWeight.w500, - ),), - showBackButton: true, - body: SafeArea(child:Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text.rich( - TextSpan( - text: 'Here are some common questions about the ', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, + return Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('Key Features',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + }, + ), + ], + + ), + body: SafeArea( + child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child:SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container + ( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'Here are some common questions about the ', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + children: [ + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + TextSpan( + text: 'mobile application:', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + ), + ], + ), + ), ), - children: [ - TextSpan( - text:' Federal Competitiveness and Statistics Centre (FCSC),', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - TextSpan( - text: 'mobile application:', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - ), - ], - ), + Container( + padding: const EdgeInsets.all(0.0), + color: Colors.grey[50], + child: QuestionAnswerScrollView(), + ), + ], ), ), - Expanded(child: Scrollbar( - thumbVisibility: true, - thickness: 8, - radius: Radius.circular(10), - interactive: true, - child:SingleChildScrollView( - child:Container( - padding: const EdgeInsets.all(0.0), - color: Colors.grey[50], - child: QuestionAnswerScrollView(), - ), - ), - ),), - ], - ), + ), + ), ); } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart index fa8bb66a..0f54ed28 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart @@ -1,8 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/custom_text.dart'; +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; + class UsingFeatures extends StatelessWidget { @@ -517,44 +522,74 @@ class UsingFeatures extends StatelessWidget { ]; return - BaseScaffold( title: Text('Key Features', - style: TextStyle( - color: Color(0xFFAA8E83), - ),), - showBackButton: true, - body:SafeArea(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text( - 'FEATURES', - style: TextStyle( - color: Colors.black,// Text color - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), + Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('Key Features',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + }, ), - Expanded(child: Scrollbar( + ], + + ), + body:SafeArea( + + child: Scrollbar( thumbVisibility: true, thickness: 6, radius: Radius.circular(10), interactive: true, - child: - Container( - color: Colors.grey[50], // Set the background color here - // child: SingleChildScrollView( - child: CustomTextRich( - textSpans: textSpans, - padding: const EdgeInsets.all(16.0), - textAlign: TextAlign.start, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text( + 'FEATURES', + style: TextStyle( + color: Colors.black,// Text color + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + Container( + color: Colors.grey[50], // Set the background color here + // child: SingleChildScrollView( + child: CustomTextRich( + textSpans: textSpans, + padding: const EdgeInsets.all(16.0), + textAlign: TextAlign.start, + ), + ), + ], ), - ), - ),), - ], - ), ), + ) + ), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart index c4dafe91..a0e575a9 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart @@ -1,5 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/custom_text.dart'; @@ -7,6 +9,9 @@ import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes. import 'package:uae_stat/infrastructure/services/img_asset_paths/banner_asset_path.dart'; import 'package:url_launcher/url_launcher.dart'; + +import '../../../../components/indicators/locale_provider.dart'; +import '../../../../components/my_toggle.dart'; class GettingStarted extends StatelessWidget { GettingStarted({super.key}); @@ -66,55 +71,90 @@ class GettingStarted extends StatelessWidget { ]; - return BaseScaffold( title: Text('About the App', - style: TextStyle( - color:Color(0xFF265E84), - fontWeight: FontWeight.w500, + return Scaffold( + appBar: AppBar( + elevation: 3, + shadowColor: Colors.black, + title: Text('About the App',), + leading: IconButton( + onPressed: (){ + context.go('/user-guide'); + }, + color: Colors.black, + icon: const Icon(Icons.arrow_back_ios_new),), + actions: [ + Consumer( + builder: (context, ref, _) { + final locale = ref.watch(localeProvider); // Current locale + return MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + ref.read(localeProvider.notifier).toggleLocale(); + }, + ); + // IconButton( + // icon: Icon(locale?.languageCode == 'en' + // ? Icons.toggle_off_outlined + // : Icons.toggle_on_outlined,), + // onPressed: () { + // ref.read(localeProvider.notifier).toggleLocale(); + // }, + // ); + }, + ), + ], ), - ), - showBackButton: true, - body:SafeArea(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text.rich( - TextSpan( - text: 'The', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - children: [ - TextSpan( - text:' Federal Competitiveness and Statistics Centre (FCSC),', + // title: Text('About the App',), + // showBackButton: true, + body:SafeArea( + child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child:SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'The', style: TextStyle( fontSize: 19, fontWeight: FontWeight.w600, - color: Colors.black87, + color: Colors.grey, ), - ), - TextSpan( - text:' in your App is designed to provide registered and approved users with access to accurate and comprehensive statistics about the UAE. The app serves as a centralized platform for exploring key datasets, trends, and insights across various sectors.', - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - color: Colors.grey, + children: [ + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + TextSpan( + text:' in your App is designed to provide registered and approved users with access to accurate and comprehensive statistics about the UAE. The app serves as a centralized platform for exploring key datasets, trends, and insights across various sectors.', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + ), + ], ), ), - ], - ), - ), - ), - Expanded(child: Scrollbar( - thumbVisibility: true, - thickness: 6, - radius: Radius.circular(10), - interactive: true, - child: SingleChildScrollView( - child: Column( + ), + + Column( children: [ Container( color: Colors.grey[50], // Set the background color here @@ -169,11 +209,11 @@ class GettingStarted extends StatelessWidget { ), ], ), - ), - - ),), - ], - ), ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart index c4086555..2455932a 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart @@ -15,15 +15,16 @@ class _UserGuideState extends State { List createRows(List>> items) { return items.map((pair) { return Padding( - padding: const EdgeInsets.only(bottom: 20), // Add space between rows + padding: const EdgeInsets.only(bottom: 20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: pair.length == 1 + ? MainAxisAlignment.center + : MainAxisAlignment.spaceEvenly, children: pair.map((item) { return userGuides( color: item['color'] as Color, text: item['text'] as String, dynamicIcon: item['icon'], - no: item['index'] as String, routePath:item['routePath']as String, ); }).toList(), @@ -36,17 +37,16 @@ class _UserGuideState extends State { title: Text('User Guide'), body: SingleChildScrollView( child: Container( - color: Colors.grey[300], // Grey background for content padding: const EdgeInsets.all(16.0), // Padding around content child: Column( children: createRows([ // Each pair defines a row of (Color, Text) pairs [ - {'routePath':'gettingStarted','color': Color(0xFF90B0D5), 'text': 'Getting Started', 'icon': 'START', 'index': '1'}, - {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined, 'index': '2'}, + {'routePath':'gettingStarted','color': Color(0xFF90B0D5), 'text': 'Getting Started', 'icon': 'START',}, + {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined,}, ], [ - {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp, 'index': '3'}, + {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp,}, // {'routePath':'features','color': Color(0xFF7DAFBC), 'text': 'Advanced Settings', 'icon': Icons.settings_outlined, 'index': '6'}, ], ]), @@ -58,21 +58,20 @@ class _UserGuideState extends State { class userGuides extends StatelessWidget { - const userGuides({super.key, required this.routePath ,required this.color, required this.text,required this.dynamicIcon,required this.no}); + const userGuides({super.key, required this.routePath ,required this.color, required this.text,required this.dynamicIcon}); final Color color; final String text; final dynamic dynamicIcon; - final String no; final String routePath; - Widget buildDynamicWidget(dynamic icons) { + Widget buildDynamicWidget(dynamic icons, Color iconColor) { if (dynamicIcon is IconData) { return Icon( icons, size: 38, - color: Colors.white, + color: iconColor, ); } else if (dynamicIcon is String) { // Create a Text widget if variable is a String @@ -81,7 +80,7 @@ class userGuides extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 20, - color: Colors.white, + color: iconColor, ), ); } else { @@ -96,67 +95,52 @@ class userGuides extends StatelessWidget { @override Widget build(BuildContext context) { + final double screenWidth= MediaQuery.of(context).size.width; + final double screenHeight= MediaQuery.of(context).size.height; return InkWell( onTap: (){ context.push('/user-guide/$routePath'); }, child: Container( - padding: EdgeInsets.symmetric(horizontal: 4), - height: 115, - width: 148, - margin: EdgeInsets.all(1), - decoration: BoxDecoration( - color: color, + padding: EdgeInsets.symmetric(horizontal: 4), + height: 110, + width: screenWidth * 0.40, + margin: EdgeInsets.all(1), + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(10), - ), - alignment: Alignment.center, - child: Stack( - children: [Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Icon( - // size:38, - // icons, // Home icon - // color: Colors.white, - // ), - buildDynamicWidget(dynamicIcon), + border: Border.all(color: color, + width: 2.0) + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Icon( + // size:38, + // icons, // Home icon + // color: Colors.white, + // ), + buildDynamicWidget(dynamicIcon, color), - SizedBox(width: 10,height: 15,), // Space between icon and text - Container( - width: double.infinity, - padding: EdgeInsets.symmetric(horizontal: 4, vertical: 4), // Padding inside the text background - decoration: BoxDecoration( - color: Colors.white, // Background color for text - borderRadius: BorderRadius.circular(5), // Rounded corners for text background - ), - child: Text( - text, - style: TextStyle( - color: color, // Text color - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), + SizedBox(width: 10,height: 10,), // Space between icon and text + Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 4, vertical: 5), // Padding inside the text background + decoration: BoxDecoration( + color: color, // Background color for text + borderRadius: BorderRadius.circular(5), + ), + child: Text( + text, + style: TextStyle( + color: Colors.white, // Text color + fontWeight: FontWeight.bold, ), - ], + textAlign: TextAlign.center, + ), ), - Positioned( - top: 6, // Distance from the top of the container - left: 6, // Distance from the left of the container - child: Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - color: Colors.transparent, // Background color for the number - child: Text( - no, - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - ),], - ) + ], + ), ), ); } diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 14cd5831..9e3702ea 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -124,7 +124,7 @@ class _BaseScaffoldState extends ConsumerState { text: AppLocalizations.of(context)!.tour_navBar, alignment: ContentAlign.top, space: 0, - gap: 100, + gap: 105, ), TargetContent( align: ContentAlign.top, @@ -138,37 +138,43 @@ class _BaseScaffoldState extends ConsumerState { bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( '5/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( @@ -187,7 +193,7 @@ class _BaseScaffoldState extends ConsumerState { .state = true; ref .read(previousChartsTourProvider - .notifier) + .notifier) .state = false; context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, @@ -220,22 +226,6 @@ class _BaseScaffoldState extends ConsumerState { ), ), ), - TargetContent( - padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), - align: ContentAlign.top, - child: Container( - width: 200, - height: 100, - child: Stack( - children: [ - Image.asset( - 'assets/app_tour/leftUp.png', - fit: BoxFit.contain, - ), - ], - ), - ), - ), ], ), TargetFocus( @@ -255,45 +245,50 @@ class _BaseScaffoldState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height * - 0.80, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * 0.80, child: Stack( children: [ Positioned( bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( '6/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( @@ -339,7 +334,7 @@ class _BaseScaffoldState extends ConsumerState { ), TargetContent( padding: EdgeInsets.only( - right: screenWidth * 0.7, top: toggleHeight - 5), + right: screenWidth * 0.7, top: 30), align: ContentAlign.right, child: Container( width: 200, @@ -392,37 +387,43 @@ class _BaseScaffoldState extends ConsumerState { bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child:Padding( + padding: const EdgeInsets.only(right:10.0), + child: Text( '7/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ Container( @@ -448,10 +449,10 @@ class _BaseScaffoldState extends ConsumerState { .state = true; ref .read( - previousChartsTourProvider.notifier) + previousChartsTourProvider.notifier) .state = true; ref.read(homeTourProvider.notifier).state = - true; + true; ref .read(previousHomeTourProvider.notifier) .state = true; @@ -460,7 +461,7 @@ class _BaseScaffoldState extends ConsumerState { .state = true; ref .read(previousScaffoldTourProvider - .notifier) + .notifier) .state = true; tutorialCoachMark.finish(); }, @@ -493,7 +494,7 @@ class _BaseScaffoldState extends ConsumerState { ), TargetContent( padding: - EdgeInsets.only(left: screenWidth * 0.7, top: toggleHeight), + EdgeInsets.only(left: screenWidth * 0.7, top: toggleHeight), align: ContentAlign.left, child: Container( width: 200, From 3ba3650e92d93b0c7095c84d57ca86de4d76013d Mon Sep 17 00:00:00 2001 From: venbaittech Date: Tue, 4 Feb 2025 18:35:02 +0530 Subject: [PATCH 15/18] App Tour first time login and fiter change --- ...kotlin-compiler-3932177861650478103.salive | 0 android/app/build.gradle | 4 +- assets/icons/misc/vector.png | Bin 0 -> 557 bytes assets/icons/misc/visibility_off.png | Bin 0 -> 1584 bytes assets/icons/misc/visible_on.png | Bin 0 -> 818 bytes lib/config/my_router.dart | 5 +- .../icons/misc_icon_asset_path.dart | 3 + lib/l10n/intl_en.arb | 12 +- .../auth_verification/registration.dart | 188 +++++-- .../Screens/charts/screens/chart_screen.dart | 103 ++-- .../Screens/charts/widgets/chart_widget.dart | 516 +++++++++++++----- .../routes/auth_routes/login_route.dart | 186 ++++--- .../tab_routes/home_route.dart | 257 +++++---- pubspec.yaml | 5 +- 14 files changed, 863 insertions(+), 416 deletions(-) create mode 100644 android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive create mode 100644 assets/icons/misc/vector.png create mode 100644 assets/icons/misc/visibility_off.png create mode 100644 assets/icons/misc/visible_on.png diff --git a/android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive b/android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive new file mode 100644 index 00000000..e69de29b diff --git a/android/app/build.gradle b/android/app/build.gradle index 02d9a058..6891a8ad 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 = "6" + flutterVersionCode = "7" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.5" + flutterVersionName = "1.0.6" } def keystorePropertiesFile = rootProject.file("key.properties") diff --git a/assets/icons/misc/vector.png b/assets/icons/misc/vector.png new file mode 100644 index 0000000000000000000000000000000000000000..d3783e75655e5e06d23e3f015bb9579f97dccb26 GIT binary patch literal 557 zcmV+|0@D47P)ZlHai=;sq#OCQBj8Vk=J9NOL(?O>Tj^u1>=DP<&O zvA}*tRIIz$Qa39)Ncrn{_UlIZa+64|g^*?&Te@ad0F3&~N{FYxv!9RGLB9eb!^ai@ z1we;!4X}$z)Llj0n0ED=W;F&-f~~3E1B}Sk4C#m>%%Oq9b%Lh*XqEeIV9_dbRY3%FC)=|} zbH=z#S#bGrVG8F4jU0q&mT)!Cta1})YcLA&bc;;hJo_cBV)QPf>r|(xUdR%Fi3M4- zw>bR*&_nRBfqe7msU9=lwH(YK)+;Twsxt3n$oinM1J+XMQ%^~?2>Dv)!2K93s(xK% zfkPukdMLk~C=JPJtF)ey@@|T|9^~L&^mSv?1_xusN@9dYjrv>zpQnUJmIG8Lg>9?X v9mcavlk#7WzjHbY+gPtV?Eiyx1XlD1{<|5N|I58000000NkvXXu0mjfm9O+S literal 0 HcmV?d00001 diff --git a/assets/icons/misc/visibility_off.png b/assets/icons/misc/visibility_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f01af5ba31a5f0f2afc1a5191a5c28da03906bea GIT binary patch literal 1584 zcmV-02G9A4P)Px)>PbXFRCr$PoLg4aFcgMQabVyiWceU2aUjA##3eqsa1!9aq^>lC_LMaF|GjfL za5isx+V154_ATjU*x6rK1%|HH&G^m$bany*U;sKR(Am62fC1>NKxgw70S2J60-eoU z1Q>wM3UoFPN5GF?zt0{YPtLx6KELWb<~L;?0^r0S$CG@crlA)iCV7Atp-J1ZQ^&P0%(nK7Y*c5TG0qkYo{V0U@-v z-m{k;jY}&#a(Ebqf7iWXWV@hR5STP46PP)xAB-#jH;tRyKliA2GUU@Hoy_|{u6>Nvh}1mgAoO)X1%MTQE|B<5oq4MQ+n1V! z=EaeMxV$(Q>nJ`^D!v4QZpF-l;O)uNkqCfR{Pqy|)W=7I3<3?Hl=wX$xGe^@a5B*# zsc#ejW-$H+3D$En1LwvNE4_yW0S3TKJP67}%dtX$05E&^YawevSZ_VJ+m_5t9R_%a zBgP4b;qg7Pl7|4GB5q9zJzu*{U>8?)Az-O43jn2_n@mdx4g@-GNe_=+JEuu@C5)yX z#88iI3uOH0*w^J8g^)`{$q2x_77Mj70qmOoXsI|$vGPK3<6mAIn@jrost2>C5CV%0 zC(3+u3kZ@lG}jyd%xvl}HHS?A2wjahdCx2dA!n<_JhmV!o{W=+E?W;uq@xrB4FGg5 zTQ;Hq5bu|y9zK8W2ewA2bQI{EDHmZW2YLYj=^P|`D(f71b9*=Da%b#!DTJIz(mKE_ z(qdzO0icEV`>>S(kfx)YV`%{(bAkZmJjEGmG1y#mu#r$3K<@f}J}*<%bL7zpVu5NkK90Fa9#1K~TG z@cQZWuf5OJxYt>0Js%o}pc7w}f|f+tbTpsKgFyV78ILp)nDbI&R;8fjh0$CYA53`= zH6fJzdSo#GY>T?>6&{BB9eKPvJ8c-VA$7$JZD9 z?M+nR+;PPL1cSPilpA)v-&;ed2B1zf)DwPr<(3et0tl0mx2tUgk6CF2p$UN33Zeys zrT|_eh}sak09b~6zw}h+J!Up(b_cMA!M<*aPS|;}J9haYs6$vM929`Hn9JUN|C7a2 z*S^>XnpO(WpC=BVI9m%IgVh_tK?6X&TYFqj2nOJZUM~m+U=cn&AQ*sE{%Q@u07ME% zO9%#Fqk^}BU;q*=R0{|OAk)LvhF}1IMMO;q1^@uy4nZNX&j%R*5P&-@cbAV9f7(DH z?AxIU2omuPfY7DZksuY{00;pr(~(Ae1E2s<>Qt)%rIZw2#{dY0D|MOyD5a$EItD-} iT&dFxKq)1K*ZB{M(x=P9 literal 0 HcmV?d00001 diff --git a/assets/icons/misc/visible_on.png b/assets/icons/misc/visible_on.png new file mode 100644 index 0000000000000000000000000000000000000000..b25be6b4595c3657912cbd9901669a6d0e2a107a GIT binary patch literal 818 zcmV-21I_%2P);00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP?>2K~#7Fl~vtw z+CUKAJz?ybI5Tt*TtP?$mMgIR;KV$DIlBNu1&9h_3P{Ay1B3HGQh{*?qyu#jGI27F zK)2pIfe?}~_P)T}?tR~V`zM4Km^&I3;K8>6;SdxS__r8(Xv+URAh?J9{8*lzPPbv< zvqb%GHjug|K%2n}unkOL2dtna%VqCXghtUHzexaf6NETcD!3N{#xMs@$`H_XIWG5D zQiLT0yyl}~NDMddCf~|ZoFa4wgCfnuow_;-GGSU=s;< zLG0mud1F(xEZf2gYZx;V4q*_HJtgl2Wj}+B3_EojqF-eHL^FV?nxcJK9K#-NQsGg{H9d4Xs?fcpr z)_9YAHxAw~d}c#N{S6t%8=uBrVyy+aJa5)=-h>k;H9jF6zHza-fxNs`7w5Z9n2AMf zZ}@q!7Cj7j24%+JXOWr<$YrjajaU9~fAVZG!H%~^#LJ#ltyWutER%2vO5#7OF$-Cn z$D`90Xieh#ljDj!HX8hp>wLmJupFUwQuVWhYid(z++^IzNMj~$$Cv%92v@{9jlRtt wXLHBBW98wz5i2M4n-@5Lw)g`qw|{`205$lR?3`b$$N&HU07*qoM6N<$g4_&n+yDRo literal 0 HcmV?d00001 diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index c02c0097..b4ef5ea0 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -328,16 +328,15 @@ final GoRouter router = GoRouter( GoRoute( path: '/', //builder: (context, state) => LoginRoute(), - builder: (context, state) => LoginRoute(), + builder: (context, state) => LoginRoute(isLoginScreen: true), ), GoRoute( path: '/internetcheck', builder: (context, state) => InternetCheck(), - ), GoRoute( path: '/login', - builder: (context, state) => LoginRoute(), + builder: (context, state) => LoginRoute(isLoginScreen: true), //builder: (context, state) => LoginRoute(), ), GoRoute( diff --git a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart index 7b4cebd1..55f0bdcd 100644 --- a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart +++ b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart @@ -6,4 +6,7 @@ abstract class MiscIconAssetPath { static const menu = '$_basePath/menu.png'; static const person = '$_basePath/person.png'; static const back = '$_basePath/back.png'; + static const vector = '$_basePath/vector.png'; + static const visibleOn = '$_basePath/visible_on.png'; + static const visibilityOff = '$_basePath/visibility_off.png'; } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index dac0f961..fc0ec46f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -12,14 +12,16 @@ "enter_your_email": "Enter your email", "enter_your_password": "Enter your password", "register_Confirm_password": "Confirm password", - "agree": "I agree to ", + "agree": "I agree to the", "t_and": " and ", - "conditions": " of Fcsc", - "terms_conditions": "terms & conditions", - "privacy_policy":"privacy policy", - "account_confirmation": "Already have an account? Login", + "conditions": " of FCSC", + "terms_conditions": "Terms & Conditions", + "privacy_policy":"Privacy Policy", + "account_confirmation": "Already have an account?", "login_title": "Login", + + "profile_title": "Edit Profile", "my_profile": "My Profile", "logout": "Logout", diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index a4650aad..ab0e9586 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -1,10 +1,13 @@ +import 'package:external_repos/external_repos.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:pocketbase/pocketbase.dart'; +import 'package:uae_stat/config/my_theme.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; import 'package:uae_stat/presentation/Screens/profilepage.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -471,14 +474,15 @@ class _RegisterScreenState extends State { ), ) : Padding( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.only(top: 5.0,bottom: 20, left: 20,right: 20), + child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox(height: screenheight / 15), + SizedBox(height: screenheight / 60), Text( // 'Register', AppLocalizations.of(context)!.register_title, @@ -492,18 +496,42 @@ class _RegisterScreenState extends State { // Display this if registration is pending approval TextFormField( controller: _usernameController, - focusNode: _focusNodes[0], + // focusNode: _focusNodes[0], decoration: InputDecoration( // hintText: _showHints[0] ? 'Username' : null, hintText: _showHints[0] ? AppLocalizations.of(context)!.register_name : null, - prefixIcon: Icon( - Icons.person, - color: Colors.blue, + prefixIcon: Padding( + padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + child: Image.asset( + MiscIconAssetPath.person, + color: Color(0xFF90B0D5), + width: 24, + height: 24, + ), + ), + + // prefixIcon: Icon( + // Icons.person, + // color: Color(0xFF90B0D5), + // + // ), + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border ), - border: OutlineInputBorder(), counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), validator: _validateUsername, maxLength: @@ -513,7 +541,7 @@ class _RegisterScreenState extends State { SizedBox(height: 15), TextFormField( controller: _emailController, - focusNode: _focusNodes[1], + // focusNode: _focusNodes[1], decoration: InputDecoration( // hintText: 'Enter your email', hintText: _showHints[1] @@ -521,12 +549,30 @@ class _RegisterScreenState extends State { .enter_your_email : null, // _showHints[1] ? 'Enter your email' : null, - prefixIcon: Icon( - Icons.email, - color: Colors.blue, + prefixIcon: Padding( + padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + child: Image.asset( + MiscIconAssetPath.vector, + color: Color(0xFF90B0D5), + width: 24, + height: 24, + ), + ), + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border ), - border: OutlineInputBorder(), counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), validator: _validateEmail, maxLength: 320, @@ -539,7 +585,7 @@ class _RegisterScreenState extends State { SizedBox(height: 15), TextFormField( controller: _passwordController, - focusNode: _focusNodes[2], + // focusNode: _focusNodes[2], obscureText: _obscurePassword, decoration: InputDecoration( hintText: _showHints[2] @@ -547,25 +593,59 @@ class _RegisterScreenState extends State { .enter_your_password : null, // _showHints[2] ? 'Enter your password' : null, - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, - ), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility_off - : Icons.visibility, - color: Colors.blue, + prefixIcon: Padding( + padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + child: Image.asset( + MiscIconAssetPath.lock, + color: Color(0xFF90B0D5), + width: 24, + height: 24, ), + ), + // prefixIcon: Icon( + // Icons.lock, + // color: Color(0xFF90B0D5), + // ), + suffixIcon: IconButton( + // icon: Icon( + // _obscurePassword + // ? Icons.visibility_off + // : Icons.visibility, + // color: Color(0xFF9EA2A9), + // ), + + icon: Image.asset( + _obscurePassword + ?MiscIconAssetPath.visibilityOff + :MiscIconAssetPath.visibleOn, + + color: Color(0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + width: 24, + height: 24, + ), + + onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), - border: OutlineInputBorder(), + // border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + ), counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), validator: _validatePassword, maxLength: 40, @@ -574,7 +654,7 @@ class _RegisterScreenState extends State { SizedBox(height: 15), TextFormField( controller: _confirmpasswordController, - focusNode: _focusNodes[3], + // focusNode: _focusNodes[3], obscureText: _obscureConfirmPassword, decoration: InputDecoration( hintText: _showHints[3] @@ -582,16 +662,25 @@ class _RegisterScreenState extends State { .register_Confirm_password : null, // _showHints[3] ? 'Confirm password' : null, - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, + prefixIcon: Padding( + padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + child: Image.asset( + MiscIconAssetPath.lock, + color: Color(0xFF90B0D5), + width: 24, + height: 24, + ), ), suffixIcon: IconButton( - icon: Icon( + + icon: Image.asset( _obscureConfirmPassword - ? Icons.visibility_off - : Icons.visibility, - color: Colors.blue, + ?MiscIconAssetPath.visibilityOff + :MiscIconAssetPath.visibleOn, + + color: Color(0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + width: 24, + height: 24, ), onPressed: () { setState(() { @@ -600,8 +689,23 @@ class _RegisterScreenState extends State { }); }, ), - border: OutlineInputBorder(), + // border: OutlineInputBorder( + // borderSide: BorderSide(color: Colors.blue, width: 2), // Default border color + // ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + ), counterText: '', + hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), validator: _validateConfirmPassword, maxLength: 40, @@ -623,7 +727,7 @@ class _RegisterScreenState extends State { }); }, side: BorderSide( - color: showError ? Colors.red : Colors.grey, + color: showError ? Colors.red : Color(0xFF92722A), width: 1.5, ), ), @@ -637,7 +741,7 @@ class _RegisterScreenState extends State { text: AppLocalizations.of(context)! .terms_conditions, // text: 'Terms & Conditions', - style: TextStyle(color: Colors.blue), + style: TextStyle( color: MyTheme.topicColor(IndicatorTopic.economy).shade600,), ), TextSpan( text: AppLocalizations.of(context)! @@ -645,7 +749,7 @@ class _RegisterScreenState extends State { TextSpan( text: AppLocalizations.of(context)! .privacy_policy, - style: TextStyle(color: Colors.blue), + style: TextStyle( color: MyTheme.topicColor(IndicatorTopic.economy).shade600,), ), TextSpan( text: AppLocalizations.of(context)! @@ -695,8 +799,10 @@ class _RegisterScreenState extends State { fontSize: 16, color: Colors.white), ), SizedBox(width: 8), - Icon(Icons.arrow_forward, - color: Colors.white), + const Icon( + Icons.chevron_right_outlined, + color: Colors.white, // Set your desired color here + ) ], ), ), @@ -730,8 +836,10 @@ class _RegisterScreenState extends State { fontSize: 16, color: Colors.white), ), SizedBox(width: 8), - Icon(Icons.arrow_forward, - color: Colors.white), + const Icon( + Icons.chevron_right_outlined, + color: Colors.white, // Set your desired color here + ) ], ), diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index c962a673..87d07f82 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -369,9 +369,15 @@ class _ChartScreen1State extends ConsumerState { child: ListView.builder( itemCount: filters.length, itemBuilder: (context, index) { + filters.sort((a, b) => a["filter_text_and_order"]["order"] + .compareTo(b["filter_text_and_order"]["order"])); + final filter = filters[index]; final filterKey = filter["filter_key"]; final filterData = filter["filter_data"]; + final filter_text_and_order = + filter["filter_text_and_order"]; + final fieldOrder = filter_text_and_order['order']; return StatefulBuilder( builder: (context, setState) { @@ -386,7 +392,9 @@ class _ChartScreen1State extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - capitalizeAndSplit(filterKey), + context.translate(filter_text_and_order['en'], + filter_text_and_order['ar']), + // capitalizeAndSplit(filterKey), style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -399,8 +407,13 @@ class _ChartScreen1State extends ConsumerState { builder: (BuildContext context) { return StatefulBuilder( builder: (context, dialogSetState) { + final locale = + ref.watch(localeProvider); + print('localelocale $locale'); return AlertDialog( - title: Text('Select $filterKey'), + title: Text( + // "${locale == 'ar' ? 'يختار ' : 'Select '}" + "${context.translate(filter_text_and_order['en'], filter_text_and_order['ar'])}"), content: SingleChildScrollView( child: ListBody( children: filterData @@ -465,7 +478,9 @@ class _ChartScreen1State extends ConsumerState { children: selectedFilter["filter_data"].isEmpty ? [ - Text('Select $filterKey', + Text( + // "${locale == 'ar' ? 'يختار ' : 'Select '}" + "${context.translate(filter_text_and_order['en'], filter_text_and_order['ar'])}", style: TextStyle( color: Colors.grey)) ] @@ -1023,26 +1038,31 @@ class _ChartScreen1State extends ConsumerState { Flexible( fit: FlexFit.loose, child: FittedBox( - child: Text( - '${chart_heading ?? 'NA'}', - textAlign: TextAlign.center, - maxLines: 2, // Limit to 2 lines - softWrap: - true, // Enable soft wrapping - // overflow: TextOverflow.ellipsis, // Handle overflow gracefully - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Colors.black87, + child: SizedBox( + // height: 70.0, + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), ), ), ), ), + const SizedBox(height: 1), Text( '(${data['lastYear'] ?? 'NA'})', style: const TextStyle( - fontSize: 11, color: Colors.grey), + fontSize: 10, color: Colors.grey), ), + const SizedBox(height: 1), Flexible( fit: FlexFit.loose, child: FittedBox( @@ -1051,7 +1071,7 @@ class _ChartScreen1State extends ConsumerState { apiService.formatAmount( data['lastYearValue']), style: const TextStyle( - fontSize: 26, + fontSize: 18, fontWeight: FontWeight.w900, color: Color(0xFF90B0D5), ), @@ -1074,7 +1094,7 @@ class _ChartScreen1State extends ConsumerState { apiService.formatAmount( data['secondLastYearValue']), style: const TextStyle( - fontSize: 16, + fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFFD83731), ), @@ -1084,7 +1104,7 @@ class _ChartScreen1State extends ConsumerState { Text( '(${data['secondLastYear'] ?? 'NA'})', style: const TextStyle( - fontSize: 8, + fontSize: 9, fontWeight: FontWeight.w500, color: Colors.grey), ), @@ -1120,37 +1140,42 @@ class _ChartScreen1State extends ConsumerState { const SizedBox(height: 5), Flexible( fit: FlexFit.loose, - child: FittedBox( - child: Text( - '${chart_heading ?? 'NA'}', - textAlign: TextAlign.center, - maxLines: 2, // Limit to 2 lines - softWrap: - true, // Enable soft wrapping - // overflow: TextOverflow.ellipsis, // Handle overflow gracefully - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Colors.black87, - ), + // child: FittedBox( + child: Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + maxLines: 2, // Limit to 2 lines + softWrap: + true, // Enable soft wrapping + // overflow: TextOverflow.ellipsis, // Handle overflow gracefully + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w400, + color: Colors.black87, ), ), + // ), ), + const SizedBox(height: 1), Text( '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', style: const TextStyle( fontSize: 10, color: Colors.grey), ), + const SizedBox(height: 1), Flexible( fit: FlexFit.loose, - child: FittedBox( - fit: BoxFit.contain, - child: Text( - '${data['roundedAverage'] ?? 'NA'}', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.w900, - color: Color(0xFF90B0D5), + child: SizedBox( + height: 50.0, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + '${data['roundedAverage'] ?? 'NA'}', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: Color(0xFF90B0D5), + ), ), ), ), diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index ca303309..59321013 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -538,6 +538,7 @@ class ChartWidget extends StatelessWidget { color: groupColorMap[group], ), SizedBox(width: 6), + Text( group, style: TextStyle(fontSize: 14), @@ -789,12 +790,26 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, + interval: 1, getTitlesWidget: (value, meta) { if (value.toInt() < xAxisData.length) { + String title = xAxisData[value.toInt()]; + String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; + return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text(xAxisData[value.toInt()]), - ); + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: 60, // Limit width to force wrapping + child: Transform.rotate( + angle: -0.5, + child:Tooltip( + message: title, + child: Text( + displayTitle, + softWrap: true, + overflow: TextOverflow.ellipsis, + )),) + )); } return Container(); }, @@ -994,19 +1009,36 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 20, // Added space for rotated titles + reservedSize: 40, // Added space for rotated titles getTitlesWidget: (value, meta) { if (value < groupByValues.length) { String title = groupByValues.elementAt(value.toInt()); - return Transform.rotate( - angle: - -0.5, // Rotation in radians (~ -30 degrees) - child: Text( - title, - style: const TextStyle(fontSize: 12), - ), - ); + + String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: 60, // Limit width to force wrapping + child: Transform.rotate( + angle: -0.5, + child:Tooltip( + message: title, + child: Text( + displayTitle, + softWrap: true, + overflow: TextOverflow.ellipsis, + )),) + )); + // return Transform.rotate( + // angle: + // -0.5, // Rotation in radians (~ -30 degrees) + // child: Text( + // title, + // style: const TextStyle(fontSize: 12), + // ), + // ); } return const SizedBox.shrink(); }, @@ -1150,152 +1182,341 @@ class ChartWidget extends StatelessWidget { SizedBox(height: 10), // Chart Expanded( - child: BarChart( - BarChartData( - maxY: 400000, - rotationQuarterTurns: rotationTurns, - barTouchData: BarTouchData( - touchTooltipData: BarTouchTooltipData( - tooltipHorizontalAlignment: FLHorizontalAlignment.center, - // Only show tooltip when touched - getTooltipItem: (group, groupIndex, rod, rodIndex) { - if (rod.toY == 0 || touchedGroupIndex == -1) { - return null; // Don't show the tooltip if the value is 0 or there's no touch - } - - if (groupIndex == touchedGroupIndex) { - // print('Group Index: $groupIndex, Group : $group'); - - // Get the group label dynamically - String groupLabel = groupByValues.elementAt(groupIndex); - - // Fetch the crop for the current group from groupedCrops - String cropType = groupByValues.elementAt(groupIndex); - String crop = groupedCrops[cropType]![rodIndex]; - double value = rod.toY; - - String formattedValue; - if (value >= 1000000) { - formattedValue = - (value / 1000000).toStringAsFixed(1) + 'M'; - } else if (value >= 1000) { - formattedValue = - (value / 1000).toStringAsFixed(1) + 'K'; - } else { - formattedValue = value - .toStringAsFixed(0); // for values smaller than 1000 + child: BarChart( + BarChartData( + maxY: 400000, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipHorizontalAlignment: FLHorizontalAlignment.center, + tooltipRoundedRadius: 8, + fitInsideHorizontally: true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0 || touchedGroupIndex == -1) { + return null; // Don't show the tooltip if the value is 0 or there's no touch } - return BarTooltipItem( - '$groupLabel\n$crop', - const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - children: [ - TextSpan( - text: ' Value: $formattedValue', - style: const TextStyle( - color: Colors.yellow, - fontWeight: FontWeight.w500, + if (groupIndex == touchedGroupIndex) { + // print('Group Index: $groupIndex, Group : $group'); + + // Get the group label dynamically + String groupLabel = groupByValues.elementAt(groupIndex); + + // Fetch the crop for the current group from groupedCrops + String cropType = groupByValues.elementAt(groupIndex); + String crop = groupedCrops[cropType]![rodIndex]; + double value = rod.toY; + + String formattedValue; + if (value >= 1000000) { + formattedValue = + (value / 1000000).toStringAsFixed(1) + 'M'; + } else if (value >= 1000) { + formattedValue = + (value / 1000).toStringAsFixed(1) + 'K'; + } else { + formattedValue = value.toStringAsFixed( + 0); // for values smaller than 1000 + } + + return BarTooltipItem( + '$groupLabel\n$crop', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + text: ' Value: $formattedValue', + style: const TextStyle( + color: Colors.yellow, + fontWeight: FontWeight.w500, + ), ), - ), - ], - ); - } - return null; - }, - ), - touchCallback: (event, response) { - if (event.isInterestedForInteractions && - response != null && - response.spot != null) { - // setState(() { - touchedGroupIndex = response.spot!.touchedBarGroupIndex; - // }); - } else { - // setState(() { - touchedGroupIndex = -1; // Reset if no interaction - // }); - } - }, - ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: false, - reservedSize: 20, - interval: 100000, - getTitlesWidget: (value, meta) { - return Text( - value.toInt().toString(), - style: const TextStyle(fontSize: 12), - ); - }, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 80, // Added space for rotated titles - getTitlesWidget: (value, meta) { - if (value < groupByValues.length) { - String title = groupByValues.elementAt(value.toInt()); - return Transform.rotate( - angle: -1.58, // Rotation in radians (~ -30 degrees) - child: Center( - child: SizedBox( - width: 80, - child: Text( - title, - style: const TextStyle(fontSize: 12), - softWrap: true, - maxLines: 2, - ), - ), - ), + ], ); } - return const SizedBox.shrink(); + return null; }, ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + touchedGroupIndex = -1; // Reset if no interaction + // }); + } + }, ), - rightTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), - ), - borderData: FlBorderData( - show: true, - border: const Border( - // left: BorderSide(color: Colors.grey), - bottom: BorderSide(color: Colors.white), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 20, + interval: 100000, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: const TextStyle(fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 80, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = groupByValues.elementAt(value.toInt()); + return Transform.rotate( + angle: -1.58, // Rotation in radians (~ -30 degrees) + child: Center( + child: SizedBox( + width: 80, + child: Text( + title, + style: const TextStyle(fontSize: 12), + softWrap: true, + maxLines: 2, + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), ), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.white), + ), + ), + gridData: FlGridData( + show: false, + drawVerticalLine: true, + verticalInterval: 1, + horizontalInterval: 100000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + ), + barGroups: + _buildHorizontalRotateBarGroups(chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, ), - gridData: FlGridData( - show: false, - drawVerticalLine: true, - verticalInterval: 1, - horizontalInterval: 100000, - getDrawingHorizontalLine: (value) { - return FlLine( - color: Colors.grey.withOpacity(0.5), - strokeWidth: 1, - ); - }, - getDrawingVerticalLine: (value) { - return FlLine( - color: Colors.grey.withOpacity(0.5), - strokeWidth: 1, - ); - }, - ), - barGroups: - _buildHorizontalRotateBarGroups(chartData, groupByValues), - alignment: BarChartAlignment.spaceAround, ), - )) + ) + // Expanded( + // child: Stack( + // children:[ + // BarChart( + // BarChartData( + // maxY: 400000, + // rotationQuarterTurns: rotationTurns, + // barTouchData: BarTouchData( + // touchTooltipData: BarTouchTooltipData( + // tooltipHorizontalAlignment: FLHorizontalAlignment.center, + // // Only show tooltip when touched + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0 || touchedGroupIndex == -1) { + // return null; // Don't show the tooltip if the value is 0 or there's no touch + // } + // + // if (groupIndex == touchedGroupIndex) { + // // print('Group Index: $groupIndex, Group : $group'); + // + // // Get the group label dynamically + // String groupLabel = groupByValues.elementAt(groupIndex); + // + // // Fetch the crop for the current group from groupedCrops + // String cropType = groupByValues.elementAt(groupIndex); + // String crop = groupedCrops[cropType]![rodIndex]; + // double value = rod.toY; + // + // String formattedValue; + // if (value >= 1000000) { + // formattedValue = + // (value / 1000000).toStringAsFixed(1) + 'M'; + // } else if (value >= 1000) { + // formattedValue = + // (value / 1000).toStringAsFixed(1) + 'K'; + // } else { + // formattedValue = value + // .toStringAsFixed(0); // for values smaller than 1000 + // } + // + // return BarTooltipItem( + // '$groupLabel\n$crop', + // const TextStyle( + // color: Colors.white, + // fontWeight: FontWeight.bold, + // ), + // children: [ + // TextSpan( + // text: ' Value: $formattedValue', + // style: const TextStyle( + // color: Colors.yellow, + // fontWeight: FontWeight.w500, + // ), + // ), + // ], + // ); + // } + // return null; + // }, + // ), + // touchCallback: (event, response) { + // if (event.isInterestedForInteractions && + // response != null && + // response.spot != null) { + // // setState(() { + // touchedGroupIndex = response.spot!.touchedBarGroupIndex; + // // }); + // } else { + // // setState(() { + // touchedGroupIndex = -1; // Reset if no interaction + // // }); + // } + // }, + // ), + // titlesData: FlTitlesData( + // leftTitles: AxisTitles( + // sideTitles: SideTitles( + // showTitles: false, + // reservedSize: 20, + // interval: 100000, + // getTitlesWidget: (value, meta) { + // return Text( + // value.toInt().toString(), + // style: const TextStyle(fontSize: 12), + // ); + // }, + // ), + // ), + // bottomTitles: AxisTitles( + // sideTitles: SideTitles( + // showTitles: true, + // reservedSize: 80, // Added space for rotated titles + // getTitlesWidget: (value, meta) { + // if (value < groupByValues.length) { + // String title = groupByValues.elementAt(value.toInt()); + // return Transform.rotate( + // angle: -1.58, // Rotation in radians (~ -30 degrees) + // child: Center( + // child: SizedBox( + // width: 80, + // child: Text( + // title, + // style: const TextStyle(fontSize: 12), + // softWrap: true, + // maxLines: 2, + // ), + // ), + // ), + // ); + // } + // return const SizedBox.shrink(); + // }, + // ), + // ), + // rightTitles: + // AxisTitles(sideTitles: SideTitles(showTitles: false)), + // topTitles: + // AxisTitles(sideTitles: SideTitles(showTitles: false)), + // ), + // borderData: FlBorderData( + // show: true, + // border: const Border( + // // left: BorderSide(color: Colors.grey), + // bottom: BorderSide(color: Colors.white), + // ), + // ), + // gridData: FlGridData( + // show: false, + // drawVerticalLine: true, + // verticalInterval: 1, + // horizontalInterval: 100000, + // getDrawingHorizontalLine: (value) { + // return FlLine( + // color: Colors.grey.withOpacity(0.5), + // strokeWidth: 1, + // ); + // }, + // getDrawingVerticalLine: (value) { + // return FlLine( + // color: Colors.grey.withOpacity(0.5), + // strokeWidth: 1, + // ); + // }, + // ), + // barGroups: + // _buildHorizontalRotateBarGroups(chartData, groupByValues), + // alignment: BarChartAlignment.spaceAround, + // ), + // + // ), + // // Overlay Texts + // ..._buildHorizontalRotateBarGroups(chartData, groupByValues) + // .asMap() + // .entries + // .expand((entry) { + // int groupIndex = entry.key; + // BarChartGroupData groupData = entry.value; + // return groupData.barRods.asMap().entries.map((rodEntry) { + // int rodIndex = rodEntry.key; + // BarChartRodData rodData = rodEntry.value; + // + // // Position calculation + // double barHeight = rodData.toY / 400000 * MediaQuery.of(context).size.height; + // + // return Positioned( + // left: (groupIndex * 80).toDouble() + 20, // Adjust based on spacing + // bottom: barHeight / 2, // Centered inside the bar + // child: Transform.rotate( + // angle: -0.1, + // child: Text( + // // '${rodData.toY.toInt()}', + // "123", + // style: TextStyle( + // color: Colors.black, + // fontWeight: FontWeight.bold, + // fontSize: 12, + // ), + // ), + // ), + // ); + // }).toList(); + // }).toList(), + // ])) ]); + default: return Center(child: Text('Unknown chart type')); } @@ -1778,12 +1999,11 @@ class ChartWidget extends StatelessWidget { toY: value, // Use the parsed value color: barColor, // Dynamic color width: 20, - // backDrawRodData: BackgroundBarChartRodData( + // backDrawRodData: BackgroundBarChartRodData( // show: true, // toY: 400000, // color: Colors.grey.shade300, // ), - // ); }).toList(); diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index b74d62f2..5a43b9ee 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -1,5 +1,6 @@ import 'package:external_repos/external_repos.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -17,7 +18,7 @@ import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart'; import 'package:uae_stat/presentation/components/space.dart'; import 'package:uae_stat/presentation/components/themed_text_field.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; - +import 'package:http/http.dart' as http; import '../../Screens/auth_verification/registration.dart'; import 'package:pocketbase/pocketbase.dart'; @@ -27,7 +28,9 @@ import '../../components/my_toggle.dart'; class LoginRoute extends HookConsumerWidget { final pb = PocketBase('https://pb.venbait.in'); // final _pb = PocketBase('http://127.0.0.1:8090'); - LoginRoute({super.key}); + final bool isLoginScreen; // Pass `true` if this is the login screen + LoginRoute({Key? key, required this.isLoginScreen}) : super(key: key); + // LoginRoute({super.key}); dynamic userData; String? role; @@ -253,6 +256,22 @@ class LoginRoute extends HookConsumerWidget { if (!context.mounted || session == null) return; final userId = session.id; if (userId.isNotEmpty) { + final url = + Uri.parse("https://pb.venbait.in/api/login_success?id=$userId"); + + try { + final response = await http.get(url); + + if (response.statusCode == 200) { + print("Login success API call successful: ${response.body}"); + } else { + print( + "Failed to call login success API. Status code: ${response.statusCode}"); + } + } catch (e) { + print("Error calling login success API: $e"); + } + await saveUserId(userId); } try { @@ -340,7 +359,10 @@ class LoginRoute extends HookConsumerWidget { ), ), 6.horizontalSpace, - const Icon(Icons.chevron_right_outlined), + const Icon( + Icons.chevron_right_outlined, + color: Colors.white, // Set your desired color here + ) ], ), ), @@ -368,8 +390,7 @@ class LoginRoute extends HookConsumerWidget { validator: (text) { if (text == null || text.isEmpty) { return context.translate('Required', 'مطلوب'); - } - else if (text.length < 8) { + } else if (text.length < 8) { return 'The password must be at least 8 characters'; } return FieldValidator.password(minLength: 8)(text); @@ -411,7 +432,7 @@ class LoginRoute extends HookConsumerWidget { 10.verticalSpace, Text( context.translate( - 'Please login to access UAE’s key official statistics', + 'Please login to access \n UAE’s key official statistics', 'يرجى تسجيل الدخول للوصول إلى الإحصاءات الرسمية الرئيسية لدولة الإمارات العربية المتحدة', ), textAlign: TextAlign.center, @@ -422,7 +443,7 @@ class LoginRoute extends HookConsumerWidget { ), fontSize: 18, color: const Color(0xff898C81), - fontWeight: FontWeight.w700, + fontWeight: FontWeight.w600, ), ), ], @@ -466,61 +487,61 @@ class LoginRoute extends HookConsumerWidget { ), ), ); - final continueAsGuestBtn = SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () async { - final prefs = await SharedPreferences.getInstance(); - prefs.clear(); - final userId = 'guest'; - if (userId.isNotEmpty) { - await saveUserId(userId); - } - context.go('/myhomepage'); - //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), - }, - //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), - style: ButtonStyle( - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - padding: const WidgetStatePropertyAll( - EdgeInsets.symmetric(vertical: 10.5), - ), - textStyle: WidgetStatePropertyAll( - TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - ), - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - backgroundColor: WidgetStatePropertyAll( - MyTheme.topicColor(IndicatorTopic.environment), - ), - foregroundColor: const WidgetStatePropertyAll( - Colors.white, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - context.translate( - 'Continue as Guest', - 'استمر كضيف', - ), - ), - 6.horizontalSpace, - const Icon(Icons.chevron_right_outlined), - ], - ), - ), - ); + // final continueAsGuestBtn = SizedBox( + // width: double.infinity, + // child: ElevatedButton( + // onPressed: () async { + // final prefs = await SharedPreferences.getInstance(); + // prefs.clear(); + // final userId = 'guest'; + // if (userId.isNotEmpty) { + // await saveUserId(userId); + // } + // context.go('/myhomepage'); + // //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + // }, + // //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'), + // style: ButtonStyle( + // shape: WidgetStatePropertyAll( + // RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // ), + // padding: const WidgetStatePropertyAll( + // EdgeInsets.symmetric(vertical: 10.5), + // ), + // textStyle: WidgetStatePropertyAll( + // TextStyle( + // fontFamily: context.translate( + // 'Roboto', + // 'NotoKufi', + // ), + // fontSize: 16, + // fontWeight: FontWeight.w600, + // ), + // ), + // backgroundColor: WidgetStatePropertyAll( + // MyTheme.topicColor(IndicatorTopic.environment), + // ), + // foregroundColor: const WidgetStatePropertyAll( + // Colors.white, + // ), + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // context.translate( + // 'Continue as Guest', + // 'استمر كضيف', + // ), + // ), + // 6.horizontalSpace, + // const Icon(Icons.chevron_right_outlined), + // ], + // ), + // ), + // ); final fcscBanner = Image.asset( BannerAssetPath.fcsc, height: 40, @@ -554,15 +575,46 @@ class LoginRoute extends HookConsumerWidget { 20.verticalSpace, dontHaveAnAccountRegisterBtn, 20.verticalSpace, - continueAsGuestBtn, + // continueAsGuestBtn, 25.verticalSpace, fcscBanner, ], ); - final bgScaffold = Scaffold( - backgroundColor: Colors.white, - body: SafeArea(child: scaffoldBody), + // final bgScaffold = Scaffold( + // backgroundColor: Colors.white, + // body: SafeArea(child: scaffoldBody), + // ); + // return bgScaffold; + return PopScope( + canPop: !isLoginScreen, // Allow back navigation only if not login screen + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + _showExitConfirmation(context); // Show exit confirmation dialog + }, + child: Scaffold( + backgroundColor: Colors.white, + body: SafeArea(child: scaffoldBody), + ), + ); + } + + void _showExitConfirmation(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("Exit App"), + content: Text("Are you sure you want to exit?"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), // Close dialog + child: Text("Cancel"), + ), + TextButton( + onPressed: () => SystemNavigator.pop(), // Exit the app + child: Text("Exit"), + ), + ], + ), ); - return bgScaffold; } } diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index ab7cc0f0..176146f2 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 @@ -165,6 +165,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart'; @@ -205,7 +207,7 @@ class EconomyStatsWidget extends ConsumerStatefulWidget { class EconomyStatsState extends ConsumerState { List data = []; - + final _pb = PocketBase('https://pb.venbait.in'); bool isLoading = true; final GlobalKey cardTopicKey = GlobalKey(); @@ -213,11 +215,12 @@ class EconomyStatsState extends ConsumerState { late TutorialCoachMark tutorialCoachMark; late List homeTargets; late List previousHomeTargets; - Offset _imageCardPosition =Offset.zero; - Offset _imageTopicPosition =Offset.zero; + Offset _imageCardPosition = Offset.zero; + Offset _imageTopicPosition = Offset.zero; void _calculateCardPosition() { - final RenderBox cardRenderBox = cardsKey.currentContext!.findRenderObject() as RenderBox; + final RenderBox cardRenderBox = + cardsKey.currentContext!.findRenderObject() as RenderBox; final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); final Size cardSize = cardRenderBox.size; @@ -233,10 +236,10 @@ class EconomyStatsState extends ConsumerState { 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; } @@ -288,11 +291,10 @@ class EconomyStatsState extends ConsumerState { }, )..show(context: context); } - } - void _initTarget(){ - homeTargets=[ + void _initTarget() { + homeTargets = [ TargetFocus( identify: 'cardTopicKey', keyTarget: cardTopicKey, @@ -308,7 +310,7 @@ class EconomyStatsState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.69, + height: MediaQuery.of(context).size.height * 0.69, child: Stack( children: [ Positioned( @@ -353,12 +355,14 @@ class EconomyStatsState 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( padding: EdgeInsets.zero, iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -379,21 +383,18 @@ class EconomyStatsState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] - ), - + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.4, + top: _imageCardPosition.dy - 29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ]), ), ), ], @@ -404,17 +405,17 @@ class EconomyStatsState extends ConsumerState { shape: ShapeLightFocus.RRect, contents: [ createTargetContent( - text:AppLocalizations.of(context)!.economy, + text: AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), - + space: 40, + ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -425,7 +426,7 @@ class EconomyStatsState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip, + onPressed: () => handleSkip, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -457,11 +458,13 @@ class EconomyStatsState 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(); }, @@ -472,11 +475,13 @@ class EconomyStatsState 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: () { tutorialCoachMark.next(); }, @@ -496,22 +501,19 @@ class EconomyStatsState extends ConsumerState { TargetContent( align: ContentAlign.bottom, child: SizedBox( - width: MediaQuery.of(context).size.width , - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] - ), - + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.40, + top: _imageTopicPosition.dy - 29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ]), ), ), ], @@ -519,24 +521,25 @@ class EconomyStatsState extends ConsumerState { ]; } - void _initPreviousTarget(){ - previousHomeTargets=[ + void _initPreviousTarget() { + previousHomeTargets = [ TargetFocus( identify: 'cardsKey', keyTarget: cardsKey, shape: ShapeLightFocus.RRect, contents: [ createTargetContent( - text:AppLocalizations.of(context)!.economy, + text: AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), + space: 40, + ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -547,7 +550,7 @@ class EconomyStatsState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip(), + onPressed: () => handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -579,10 +582,12 @@ class EconomyStatsState 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( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -593,14 +598,21 @@ class EconomyStatsState 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(previousHomeTourProvider.notifier).state=true; - ref.read(chartsTourProvider.notifier).state=false; + ref + .read( + previousHomeTourProvider.notifier) + .state = true; + ref + .read(chartsTourProvider.notifier) + .state = false; tutorialCoachMark.finish(); }, ), @@ -620,21 +632,18 @@ class EconomyStatsState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] - ), - + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.40, + top: _imageTopicPosition.dy - 29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ]), ), ), ], @@ -645,7 +654,7 @@ class EconomyStatsState extends ConsumerState { shape: ShapeLightFocus.RRect, contents: [ createTargetContent( - text:AppLocalizations.of(context)!.home_topic, + text: AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, gap: 45, space: 40, @@ -654,7 +663,7 @@ class EconomyStatsState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.69, + height: MediaQuery.of(context).size.height * 0.69, child: Stack( children: [ Positioned( @@ -702,11 +711,13 @@ class EconomyStatsState 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(); }, @@ -727,21 +738,18 @@ class EconomyStatsState extends ConsumerState { align: ContentAlign.bottom, child: SizedBox( width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , - child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] - ), - + height: MediaQuery.of(context).size.height * 0.69, + child: Stack(children: [ + Positioned( + left: MediaQuery.of(context).size.width * 0.4, + top: _imageCardPosition.dy - 29, + child: Image.asset( + 'assets/app_tour/down_right.png', + width: 40, + height: 90, + ), + ), + ]), ), ), ], @@ -749,15 +757,12 @@ class EconomyStatsState extends ConsumerState { ]; } - @override void initState() { super.initState(); final locale = ref.read(localeProvider); fetchData(locale?.languageCode ?? 'en'); - WidgetsBinding.instance.addPostFrameCallback((_) { - _starTourRender(); - }); + _fetchUserData(); } // @override @@ -768,6 +773,37 @@ class EconomyStatsState extends ConsumerState { // fetchData(locale); // Pass the locale to the fetchData method // } + Future getUserId() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('userId'); // Retrieve the userId + } + + Future _fetchUserData() async { + try { + final userId = await getUserId(); + print('EDIT PROFILE isPageLoad'); + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + print('adminToken- $adminToken'); + final userDetailsResponse = await _pb.collection('users').getOne( + userId!, + headers: { + 'Authorization': 'Bearer $adminToken', + }, + ); + final loginCount = userDetailsResponse.data['login_count']; + print(loginCount); + if (loginCount == 1) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _starTourRender(); + }); + } + } catch (e) { + print('Error fetching user details: $e'); + } + } + Future fetchData(locale) async { const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; try { @@ -875,7 +911,7 @@ class EconomyStatsState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ RoundedCornerContainer( - key: mainTopic['main_topic'] =='ECONOMY' + key: mainTopic['main_topic'] == 'ECONOMY' ? cardTopicKey : null, text: mainTopic['main_topic'], @@ -889,7 +925,7 @@ class EconomyStatsState extends ConsumerState { SizedBox( height: myheight / 4.8, // Set dynamic height child: Container( - key:mainTopic['main_topic'] == 'ECONOMY' + key: mainTopic['main_topic'] == 'ECONOMY' ? cardsKey : null, // color: Colors.grey[200], // Set a background color for the scrollable container @@ -1111,7 +1147,6 @@ class InfoCard extends StatelessWidget { final encodedTitle = Uri.encodeComponent(title); final encodedKey = Uri.encodeQueryComponent('home'); - return GestureDetector( onTap: () { context.go( diff --git a/pubspec.yaml b/pubspec.yaml index 073fd01c..907eb18d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" #version: 0.5.10 -version: 1.0.5+6 +version: 1.0.6+7 environment: sdk: ">=3.2.3 <4.0.0" @@ -117,6 +117,9 @@ flutter: - assets/app_tour/ - assets/icons/uae_numbers/bookmarks.png - assets/icons/uae_numbers/share.png + - assets/icons/misc/vector.png + - assets/icons/misc/visible_on.png + - assets/icons/misc/visibility_off.png fonts: - family: Segoe From 969a2160a960aeb345806adeebfb38038b6b80c8 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Wed, 5 Feb 2025 12:23:16 +0530 Subject: [PATCH 16/18] home screen app_tour change --- .../tab_routes/home_route.dart | 520 +++++++++--------- 1 file changed, 273 insertions(+), 247 deletions(-) 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 d05843ae..d8674d83 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 @@ -215,22 +215,7 @@ final _pb = PocketBase('https://pb.venbait.in'); late TutorialCoachMark tutorialCoachMark; late List homeTargets; late List previousHomeTargets; - Offset _imageCardPosition =Offset.zero; - Offset _imageTopicPosition =Offset.zero; - void _calculateCardPosition() { - final RenderBox cardRenderBox = cardsKey.currentContext!.findRenderObject() as RenderBox; - final Offset cardPosition = cardRenderBox.localToGlobal(Offset.zero); - final Size cardSize = cardRenderBox.size; - - // Calculate the position for the image - final double imageX = cardPosition.dx + cardSize.width / 2; - final double imageY = cardPosition.dy + cardSize.height; - - setState(() { - _imageCardPosition = Offset(imageX, imageY); - }); - } void handleSkip() { tutorialCoachMark.skip(); @@ -246,6 +231,7 @@ final _pb = PocketBase('https://pb.venbait.in'); //Method waits app tour to render Targets void _starTourRender() { if (cardTopicKey.currentContext != null) { + print('render finished'); _showHomeTour(); } else { Future.delayed(Duration(milliseconds: 100), _starTourRender); @@ -261,7 +247,6 @@ final _pb = PocketBase('https://pb.venbait.in'); if (!homeTour) { // Show Home Tour _initTarget(); - _calculateCardPosition(); tutorialCoachMark = TutorialCoachMark( paddingFocus: 0, useSafeArea: true, @@ -272,7 +257,7 @@ final _pb = PocketBase('https://pb.venbait.in'); ref.read(homeTourProvider.notifier).state = true; ref.read(chartsTourProvider.notifier).state = false; debugPrint('Home Tutorial Finished'); - context.push('/marriages'); + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } else if (!previousHomeTour) { @@ -286,7 +271,7 @@ final _pb = PocketBase('https://pb.venbait.in'); onFinish: () { ref.read(previousHomeTourProvider.notifier).state = true; debugPrint('Previous Home Tutorial Finished'); - context.push('/marriages'); + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } @@ -294,108 +279,113 @@ final _pb = PocketBase('https://pb.venbait.in'); } void _initTarget(){ + final double screenWidth = MediaQuery.of(context).size.width; homeTargets=[ TargetFocus( identify: 'cardTopicKey', keyTarget: cardTopicKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, contents: [ createTargetContent( text: AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, - gap: 45, - space: 40, + gap: 0, + space: 43, ), TargetContent( align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.69, + height: MediaQuery.of(context).size.height*0.70, child: Stack( children: [ Positioned( - bottom: 35, - left: 16, - right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () => handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( - '1/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + bottom: 20, + left: 16, + right: 16, + child: Column( + children: [ + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right: 10), // Adjust the value as needed + child: Text( + '1/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), - Row( - children: [ - const SizedBox(width: 10), - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFF7DAFBC), - border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: IconButton( - padding: EdgeInsets.zero, - iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), - onPressed: () { - tutorialCoachMark.next(); - }, + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, ), ), - ], - ), - ], - ), - ], - ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + ], + ), + ], + ), + ], + ) ), ], ), ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], @@ -404,13 +394,15 @@ final _pb = PocketBase('https://pb.venbait.in'); identify: 'cardsKey', keyTarget: cardsKey, shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, + // targetPosition:TargetPosition(), contents: [ createTargetContent( text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), - + space: 43,), TargetContent( align: ContentAlign.bottom, child: @@ -423,37 +415,43 @@ final _pb = PocketBase('https://pb.venbait.in'); bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () =>handleSkip, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), + Align( + alignment: Alignment.bottomRight, + child:Padding(padding: EdgeInsets.only(right: 10), + child: + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ) ), - Column( + const SizedBox(height: 3), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '2/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), Row( children: [ Container( @@ -496,43 +494,62 @@ final _pb = PocketBase('https://pb.venbait.in'); ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width , - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), + // TargetContent( + // align: ContentAlign.bottom, + // child: SizedBox( + // width: MediaQuery.of(context).size.width , + // height:MediaQuery.of(context).size.height*0.69 , + // child: Stack( + // children: [ + // Positioned( + // left: MediaQuery.of(context).size.width*0.40, + // top: _imageTopicPosition.dy-29, + // child: Image.asset( + // 'assets/app_tour/right_down.png', + // width: 40, + // height: 90, + // ), + // ), + // ] + // ), + // + // ), + // ), ], ), ]; } void _initPreviousTarget(){ + final double screenWidth = MediaQuery.of(context).size.width; previousHomeTargets=[ TargetFocus( identify: 'cardsKey', keyTarget: cardsKey, shape: ShapeLightFocus.RRect, + radius: 7, + paddingFocus: 6, contents: [ createTargetContent( text:AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 40,), + space: 43,), TargetContent( align: ContentAlign.bottom, child: @@ -545,67 +562,79 @@ final _pb = PocketBase('https://pb.venbait.in'); bottom: 25, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () =>handleSkip(), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), + Align( + alignment: Alignment.bottomRight, + child:Padding(padding: EdgeInsets.only(right: 10), + child: + Text( + '2/7', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ) ), - Column( + SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '2/7', - style: const TextStyle( - color: Colors.white, - fontSize: 14, + ElevatedButton( + onPressed: () =>handleSkip(), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), ), - const SizedBox(height: 3), Row( + children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2.0), - ), - child: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - tutorialCoachMark.next(); - }, - ), - ), - const SizedBox(width: 10), - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFF7DAFBC), - border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), - ), - child: IconButton( - iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), - onPressed: () { - ref.read(previousHomeTourProvider.notifier).state=true; - ref.read(chartsTourProvider.notifier).state=false; - tutorialCoachMark.finish(); - }, - ), + Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + tutorialCoachMark.next(); + }, + ), + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF7DAFBC), + border: Border.all(color: Color(0xFF7DAFBC), width: 1.5), + ), + child: IconButton( + iconSize: 20, + icon: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + ref.read(previousHomeTourProvider.notifier).state=true; + ref.read(chartsTourProvider.notifier).state=false; + context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + tutorialCoachMark.finish(); + }, + ), + ), + ], ), ], ), @@ -619,24 +648,19 @@ final _pb = PocketBase('https://pb.venbait.in'); ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.40, - top: _imageTopicPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], @@ -645,12 +669,14 @@ final _pb = PocketBase('https://pb.venbait.in'); identify: 'cardTopicKey', keyTarget: cardTopicKey, shape: ShapeLightFocus.RRect, + radius: 8, + paddingFocus: 16, contents: [ createTargetContent( text:AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, - gap: 45, - space: 40, + gap: 0, + space: 43, ), TargetContent( align: ContentAlign.bottom, @@ -660,43 +686,48 @@ final _pb = PocketBase('https://pb.venbait.in'); child: Stack( children: [ Positioned( - bottom: 35, + bottom: 20, left: 16, right: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton( - onPressed: () { - tutorialCoachMark.skip(); - debugPrint('Skip clicked'); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - side: const BorderSide(color: Colors.white), - backgroundColor: Colors.transparent, - elevation: 0, - ), - child: const Text( - 'Skip', - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - ), - Column( - children: [ - Text( + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding:EdgeInsets.only(right: 10), + child: Text( '1/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - const SizedBox(height: 3), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + tutorialCoachMark.skip(); + debugPrint('Skip clicked'); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: Colors.white), + backgroundColor: Colors.transparent, + elevation: 0, + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), Row( children: [ const SizedBox(width: 10), @@ -726,24 +757,19 @@ final _pb = PocketBase('https://pb.venbait.in'); ), ), TargetContent( + padding: EdgeInsets.only(left: screenWidth * 0.4, bottom: 0), align: ContentAlign.bottom, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height:MediaQuery.of(context).size.height*0.69 , + child: Container( + width: 200, + height: 77, child: Stack( - children: [ - Positioned( - left: MediaQuery.of(context).size.width*0.4, - top: _imageCardPosition.dy-29, - child: Image.asset( - 'assets/app_tour/down_right.png', - width: 40, - height: 90, - ), - ), - ] + children: [ + Image.asset( + 'assets/app_tour/right_down.png', + fit: BoxFit.contain, + ), + ], ), - ), ), ], From 53f1e107213d0bf5401c5e7bf6bba1721bea136d Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 5 Feb 2025 14:44:27 +0530 Subject: [PATCH 17/18] UI changes added --- lib/l10n/intl_ar.arb | 4 +- lib/l10n/intl_en.arb | 2 +- .../UAE_Numbers/uae_numbers.dart | 7 +- .../auth_verification/registration.dart | 102 +++++++++------ .../Screens/charts/screens/chart_screen.dart | 37 +++--- lib/presentation/Screens/profilepage.dart | 117 ++++++++++-------- .../components/themed_text_field.dart | 14 +-- .../routes/auth_routes/login_route.dart | 16 +-- .../Drawer Items/edit_profile.dart | 96 +++++++++++--- .../drawer_routes/custom_drawer_routes.dart | 50 ++++++-- pubspec.lock | 52 ++++---- pubspec.yaml | 2 +- 12 files changed, 322 insertions(+), 177 deletions(-) diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 1f5eb600..8766c03b 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -15,8 +15,8 @@ "agree": "أوافق على الشروط والأحكام وسياسة الخصوصية للمركز الإتحادي للتنافسية والإحصاء", "t_and": "", "conditions": "", - "terms_conditions": "", - "privacy_policy":"", + "terms_conditions": "الشروط والأحكام", + "privacy_policy":"سياسة الخصوصية", "account_confirmation": "هل لديك حساب؟ سجل الدخول", "login_title": "تسجيل الدخول", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index addb66f5..f99002cb 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -12,7 +12,7 @@ "enter_your_email": "Enter your email", "enter_your_password": "Enter your password", "register_Confirm_password": "Confirm password", - "agree": "I agree to the", + "agree": "I agree to the ", "t_and": " and ", "conditions": " of FCSC", "terms_conditions": "Terms & Conditions", 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 6917a159..c5bd1e8a 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart @@ -83,12 +83,13 @@ class _UaenumberWidgetState extends ConsumerState { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(30.0), - border: Border.all(width: 1)), + border: Border.all(width: 1,color: Color(0xFFAA8E83)),), child: TextField( decoration: InputDecoration( hintText: "Search", - prefixIcon: Icon(Icons.search), - border: InputBorder.none, + prefixIcon: Icon(Icons.search, + color: Color(0xFFAA8E83),), + border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), ), diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index ab0e9586..973d3481 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -474,7 +474,7 @@ class _RegisterScreenState extends State { ), ) : Padding( - padding: const EdgeInsets.only(top: 5.0,bottom: 20, left: 20,right: 20), + padding: const EdgeInsets.only(top: 5.0,bottom: 20, left: 30,right: 30), child: Form( key: _formKey, @@ -503,12 +503,12 @@ class _RegisterScreenState extends State { ? AppLocalizations.of(context)!.register_name : null, prefixIcon: Padding( - padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed child: Image.asset( MiscIconAssetPath.person, color: Color(0xFF90B0D5), - width: 24, - height: 24, + width: 25, + height: 15, ), ), @@ -520,15 +520,20 @@ class _RegisterScreenState extends State { // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border - ), +borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + width: 1), // Enabled border + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -550,26 +555,31 @@ class _RegisterScreenState extends State { : null, // _showHints[1] ? 'Enter your email' : null, prefixIcon: Padding( - padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed child: Image.asset( MiscIconAssetPath.vector, color: Color(0xFF90B0D5), - width: 24, - height: 24, + width: 25, + height: 15, ), ), // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border - ), +borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + width: 1), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -594,12 +604,12 @@ class _RegisterScreenState extends State { : null, // _showHints[2] ? 'Enter your password' : null, prefixIcon: Padding( - padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed child: Image.asset( MiscIconAssetPath.lock, color: Color(0xFF90B0D5), - width: 24, - height: 24, + width: 25, + height: 15, ), ), // prefixIcon: Icon( @@ -634,16 +644,22 @@ class _RegisterScreenState extends State { // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border - ), +borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + width: 1), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color + ), + counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), ), @@ -663,12 +679,12 @@ class _RegisterScreenState extends State { : null, // _showHints[3] ? 'Confirm password' : null, prefixIcon: Padding( - padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed child: Image.asset( MiscIconAssetPath.lock, color: Color(0xFF90B0D5), - width: 24, - height: 24, + width: 25, + height: 15, ), ), suffixIcon: IconButton( @@ -694,15 +710,20 @@ class _RegisterScreenState extends State { // ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFF90B0D5), width: 2), // Enabled border - ), +borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + width: 1), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFD83731), width: 1), // Error border + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -727,8 +748,8 @@ class _RegisterScreenState extends State { }); }, side: BorderSide( - color: showError ? Colors.red : Color(0xFF92722A), - width: 1.5, + color: showError ? Color(0xFFD83731) : Color(0xFF92722A), + width: 1, ), ), Expanded( @@ -741,16 +762,25 @@ class _RegisterScreenState extends State { text: AppLocalizations.of(context)! .terms_conditions, // text: 'Terms & Conditions', - style: TextStyle( color: MyTheme.topicColor(IndicatorTopic.economy).shade600,), - ), +style: TextStyle(fontWeight: FontWeight.bold, + color: Color(0xFF648CBA), + // Makes the text bold + decoration: TextDecoration.underline, + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), + ), TextSpan( text: AppLocalizations.of(context)! .t_and), TextSpan( text: AppLocalizations.of(context)! .privacy_policy, - style: TextStyle( color: MyTheme.topicColor(IndicatorTopic.economy).shade600,), - ), + style: TextStyle(fontWeight: FontWeight.bold, + color: Color(0xFF648CBA), + decoration: TextDecoration.underline, + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), + ), TextSpan( text: AppLocalizations.of(context)! .conditions), @@ -769,7 +799,7 @@ class _RegisterScreenState extends State { child: Text( 'Please agree to terms and conditions.', style: TextStyle( - color: Colors.red[700], + color: Color(0xFFb22222), fontSize: 12, ), ), @@ -778,7 +808,7 @@ class _RegisterScreenState extends State { ), SizedBox(height: 20), SizedBox( - width: screenwidth / 1.3, + width: screenwidth / 1, child: ElevatedButton( onPressed: isRegistering ? null : _registerUser, style: ElevatedButton.styleFrom( @@ -807,15 +837,15 @@ class _RegisterScreenState extends State { ), ), ), - SizedBox(height: 10), + SizedBox(height: 15), Text( AppLocalizations.of(context)!.account_confirmation, style: TextStyle( fontSize: 16, color: Colors.grey[600]), ), - SizedBox(height: 10), + SizedBox(height: 15), SizedBox( - width: screenwidth / 1.3, + width: screenwidth / 1, child: ElevatedButton( onPressed: () { context.go('/'); diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index fdd621f0..d6edd328 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -1295,15 +1295,22 @@ class _ChartScreen1State extends ConsumerState { }); selectedFiltersStorage.clear(); Navigator.pop(context); - }, + }, + + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 12), // Added padding + horizontal: 0.0, vertical: 14.2), // Added padding child: Text( context.translate('Clear', 'واضح'), style: TextStyle( color: Colors.white, - fontSize: 16, + fontSize: 13, fontWeight: FontWeight.bold, ), overflow: @@ -1311,12 +1318,6 @@ class _ChartScreen1State extends ConsumerState { maxLines: 1, // Ensures single line ), ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), ), ), SizedBox(width: 16), @@ -1334,9 +1335,15 @@ class _ChartScreen1State extends ConsumerState { selectedFilters); selectedFiltersStorage = List.from(selectedFilters); }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 12), // Added padding + horizontal: 0.0, vertical: 16.0), // Added padding child: FittedBox( fit: BoxFit .scaleDown, // Ensures text resizes if necessary @@ -1345,7 +1352,7 @@ class _ChartScreen1State extends ConsumerState { 'Apply Filter', 'تطبيق الفلتر'), style: TextStyle( color: Colors.white, - fontSize: 16, + fontSize: 12, fontWeight: FontWeight.bold, ), overflow: @@ -1353,13 +1360,7 @@ class _ChartScreen1State extends ConsumerState { maxLines: 1, // Ensures single line ), ), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), + ), ), ), ], diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index 04eb0a0d..3274d331 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -74,6 +74,8 @@ class _ProfileScreenState extends State { // Regular expression to validate Full Name (no special characters) final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); late Future userDetails; + bool _isHoveringDate = false; + bool _isHoveringDropdown = false; @override void initState() { @@ -368,7 +370,7 @@ class _ProfileScreenState extends State { child: Form( key: _formKey, child: Padding( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.only(top: 20.0,bottom: 20.0, left: 25,right: 25), child: Column( children: [ // CircleAvatar( @@ -539,7 +541,10 @@ class _ProfileScreenState extends State { ], ), SizedBox(height: 10), - TextFormField( + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDate = true), + onExit: (_) => setState(() => _isHoveringDate = false), + child: TextFormField( controller: _dateController, focusNode: _focusNodes[3], decoration: InputDecoration( @@ -549,12 +554,22 @@ class _ProfileScreenState extends State { hintText: _showHints[3] ? 'DD/MM/YYYY' : null, //_showHints[3] ? 'Select your Date of Birth' : null, hintStyle: TextStyle(color: Colors.grey), - suffixIcon: const Icon(Icons.arrow_drop_down_sharp), - ), + + suffixIcon: Container( + width: 45, + padding: EdgeInsets.only(right: 1), + alignment: Alignment.center, // Center the icon vertically + child: Icon( + Icons.keyboard_arrow_down_sharp, + color: _isHoveringDate ? Colors.black : Colors.grey, + + ), + ), + ), readOnly: true, onTap: _pickDate, validator: _validateDob, - ), + ),), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.start, @@ -567,7 +582,10 @@ class _ProfileScreenState extends State { ], ), SizedBox(height: 10), - DropdownButtonFormField( + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDropdown = true), + onExit: (_) => setState(() => _isHoveringDropdown = false), + child: DropdownButtonFormField( value: _selectedCountry, decoration: InputDecoration( border: OutlineInputBorder( @@ -575,6 +593,8 @@ class _ProfileScreenState extends State { ), labelText: 'Select', ), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: _isHoveringDropdown ? Colors.black : Colors.grey ), items: _countries .map((item) => DropdownMenuItem( value: item, @@ -587,7 +607,7 @@ class _ProfileScreenState extends State { }); }, validator: _validateDropdown, - ), + ),), SizedBox(height: 20), Row( children: [ @@ -600,8 +620,8 @@ class _ProfileScreenState extends State { }); }, side: BorderSide( - color: showError ? Colors.red : Colors.grey, - width: 1.5, + color: showError ? Color(0xFFb22222) : Color(0xFF92722A), + width: 1, ), ), Expanded( @@ -620,8 +640,11 @@ class _ProfileScreenState extends State { TextSpan( text: 'Terms & Conditions', style: TextStyle( - color: Colors.blue, + color: Color(0xFF648CBA), decoration: TextDecoration.underline, + fontWeight: FontWeight.bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, ), recognizer: TapGestureRecognizer() ..onTap = () { @@ -635,8 +658,12 @@ class _ProfileScreenState extends State { TextSpan( text: 'Privacy Policy', style: TextStyle( - color: Colors.blue, + color: Color(0xFF648CBA), decoration: TextDecoration.underline, + fontWeight: FontWeight.bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, + ), recognizer: TapGestureRecognizer() ..onTap = () { @@ -668,7 +695,7 @@ class _ProfileScreenState extends State { child: Text( 'Please agree to terms and conditions', style: TextStyle( - color: Colors.red[700], + color: Color(0xFFb22222), fontSize: 12, ), ), @@ -685,53 +712,30 @@ class _ProfileScreenState extends State { context.go('/createNewPw/$userId/$email'); }, child: Text( - "Change Password", + 'Change Password', style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline), + color: Color(0xFF648CBA), + fontSize: 14, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + // decoration: TextDecoration.underline), + ), ), ), ), SizedBox(height: 20), - ElevatedButton.icon( + // ElevatedButton.icon( + ElevatedButton( onPressed: () { - if ((_formKey.currentState?.validate() ?? false) && - (isChecked)) { + if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { _formKey.currentState?.save(); showConfirmationDialog(context); } else { setState(() { - showError = - !isChecked; // Show error if the checkbox is not checked + showError = !isChecked; // Show error if the checkbox is not checked }); - } - // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { - // _formKey.currentState?.save(); - // showConfirmationDialog(context); - // // if (isChecked) { - // // _formKey.currentState?.save(); - // // // _confirmSaveProfile(); - // // showConfirmationDialog(context); - // // } - // else { - // setState(() { - // showError = !isChecked; // Show error if the checkbox is not checked - // }); - - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)), - // ); - //} - //} - }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - 'Save', - style: TextStyle(color: Colors.white), - ), + } + }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF92722A), minimumSize: Size(double.infinity, 50), @@ -739,7 +743,22 @@ class _ProfileScreenState extends State { borderRadius: BorderRadius.circular(8), ), ), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, // Center the content + children: [ + Text( + 'Save', + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), // Add space between text and icon + Icon( + Icons.save_outlined, + color: Colors.white, + ), + ], + ), + ) + ], ), ), diff --git a/lib/presentation/components/themed_text_field.dart b/lib/presentation/components/themed_text_field.dart index 27a526ec..82b58ce6 100644 --- a/lib/presentation/components/themed_text_field.dart +++ b/lib/presentation/components/themed_text_field.dart @@ -25,13 +25,13 @@ class ThemedFormField extends HookWidget { final isObscured = useState(true); final boxDecoration = BoxDecoration( borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - offset: const Offset(0, 1), - blurRadius: 2, - color: Colors.black.withOpacity(0.1), - ), - ], + // boxShadow: [ + // BoxShadow( + // offset: const Offset(0, 1), + // blurRadius: 2, + // color: Colors.black.withOpacity(0.1), + // ), + // ], ); final obscureBtn = IconButton( onPressed: () => isObscured.value = !isObscured.value, diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 5a43b9ee..d68fc20d 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -177,7 +177,7 @@ class LoginRoute extends HookConsumerWidget { ), fontWeight: FontWeight.bold, fontSize: 14, - color: MyTheme.topicColor(IndicatorTopic.economy).shade600, + color: Color(0xFF985400), ), ), ); @@ -361,7 +361,7 @@ class LoginRoute extends HookConsumerWidget { 6.horizontalSpace, const Icon( Icons.chevron_right_outlined, - color: Colors.white, // Set your desired color here + color: Colors.white, // Set your desired color here ) ], ), @@ -390,7 +390,8 @@ class LoginRoute extends HookConsumerWidget { validator: (text) { if (text == null || text.isEmpty) { return context.translate('Required', 'مطلوب'); - } else if (text.length < 8) { + } + else if (text.length < 8) { return 'The password must be at least 8 characters'; } return FieldValidator.password(minLength: 8)(text); @@ -402,6 +403,7 @@ class LoginRoute extends HookConsumerWidget { imgPath: MiscIconAssetPath.lock, controller: pwCtl, isObscurable: true, + ), // 6.verticalSpace, Align( @@ -441,7 +443,7 @@ class LoginRoute extends HookConsumerWidget { 'Roboto', 'NotoKufi', ), - fontSize: 18, + fontSize: context.translate(18.0, 14.0), color: const Color(0xff898C81), fontWeight: FontWeight.w600, ), @@ -480,8 +482,8 @@ class LoginRoute extends HookConsumerWidget { 'سجل الان', ), style: TextStyle( - color: MyTheme.topicColor(IndicatorTopic.economy).shade600, - ), + color: Color(0xFF985400), + ), ), ], ), @@ -576,7 +578,7 @@ class LoginRoute extends HookConsumerWidget { dontHaveAnAccountRegisterBtn, 20.verticalSpace, // continueAsGuestBtn, - 25.verticalSpace, + 95.verticalSpace, fcscBanner, ], ); 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 7ea353b1..e8a36040 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -76,6 +76,8 @@ class _EditProfileState extends State { dynamic collectionId; dynamic role; + bool _isHoveringDropdown = false; + @override void initState() { super.initState(); @@ -426,13 +428,15 @@ class _EditProfileState extends State { body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0), + child: Column( children: [ SingleChildScrollView( child: Form( key: _formKey, child: Padding( - padding: const EdgeInsets.all(20.0), + // padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.only(top: 20.0,bottom: 20.0, left: 25,right: 25), child: isPageLoad ? Center( child: CircularProgressIndicator(), @@ -720,8 +724,16 @@ class _EditProfileState extends State { ), // hintText: _showHints[3] ? 'Select your Date of Birth' : null, hintStyle: TextStyle(color: Colors.grey), - suffixIcon: - const Icon(Icons.arrow_drop_down_sharp), + suffixIcon: Container( + width: 45, + padding: EdgeInsets.only(right: 1), + alignment: Alignment.center, // Center the icon vertically + child: Icon( + Icons.keyboard_arrow_down_sharp, + color: Colors.grey, + + ), + ), enabled: !_isProfileCompleted, ), readOnly: true, @@ -742,7 +754,10 @@ class _EditProfileState extends State { ], ), SizedBox(height: 10), - DropdownButtonFormField( + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDropdown = true), + onExit: (_) => setState(() => _isHoveringDropdown = false), + child: DropdownButtonFormField( value: _selectedCountry, decoration: InputDecoration( border: OutlineInputBorder( @@ -750,6 +765,8 @@ class _EditProfileState extends State { ), labelText: 'Select', ), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: _isHoveringDropdown ? Colors.black : Colors.grey ), items: _countries .map( (item) => DropdownMenuItem( @@ -764,7 +781,7 @@ class _EditProfileState extends State { }); }, validator: _validateDropdown, - ), + ),), SizedBox(height: 20), if (!_isProfileCompleted) // Conditional rendering Column( @@ -809,7 +826,8 @@ class _EditProfileState extends State { )! .terms_conditions, style: TextStyle( - color: Colors.blue, + // color: Colors.blue, + color: Color(0xFF648CBA), decoration: TextDecoration .underline, @@ -835,7 +853,8 @@ class _EditProfileState extends State { )! .privacy_policy, style: TextStyle( - color: Colors.blue, + // color: Colors.blue, + color: Color(0xFF648CBA), decoration: TextDecoration .underline, @@ -898,25 +917,47 @@ class _EditProfileState extends State { AppLocalizations.of(context)! .change_password, style: TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, + color: Color(0xFF648CBA), + fontSize: 14, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + // decoration: TextDecoration.underline ), ), ), ), SizedBox(height: 20), - ElevatedButton.icon( + // ElevatedButton.icon( + // onPressed: () { + // showConfirmationDialog(context); + // }, + // icon: Icon( + // Icons.save, + // color: Colors.white, + // ), + // label: Text( + // AppLocalizations.of(context)!.save, + // style: TextStyle(color: Colors.white), + // ), + // style: ElevatedButton.styleFrom( + // backgroundColor: Color(0xFF92722A), + // minimumSize: Size(double.infinity, 50), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // ), + ElevatedButton( onPressed: () { - showConfirmationDialog(context); + if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { + _formKey.currentState?.save(); + showConfirmationDialog(context); + } else { + setState(() { + showError = !isChecked; // Show error if the checkbox is not checked + }); + } }, - icon: Icon( - Icons.save, - color: Colors.white, - ), - label: Text( - AppLocalizations.of(context)!.save, - style: TextStyle(color: Colors.white), - ), style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF92722A), minimumSize: Size(double.infinity, 50), @@ -924,7 +965,22 @@ class _EditProfileState extends State { borderRadius: BorderRadius.circular(8), ), ), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, // Center the content + children: [ + Text( + 'Save', + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), // Add space between text and icon + Icon( + Icons.save_outlined, + color: Colors.white, + ), + ], + ), + ) + ], ), ), diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 9e3702ea..9fb351b9 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -10,6 +10,7 @@ import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/bottom_bar_asset_icon_path.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/components/my_toggle.dart'; @@ -124,7 +125,7 @@ class _BaseScaffoldState extends ConsumerState { text: AppLocalizations.of(context)!.tour_navBar, alignment: ContentAlign.top, space: 0, - gap: 105, + gap: 100, ), TargetContent( align: ContentAlign.top, @@ -816,29 +817,64 @@ class _BaseScaffoldState extends ConsumerState { type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( - icon: Icon(Icons.home), + icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.home, + color: _getSelectedIndex(currentRoute) == 0 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), // label: 'Home', label: AppLocalizations.of(context)!.nav_home, ), BottomNavigationBarItem( - icon: Icon(Icons.map), + // icon: Icon(Icons.map), + icon:Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.uaeMap, + color: _getSelectedIndex(currentRoute) == 1 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), // label: 'UAE Numbers' label: AppLocalizations.of(context)!.nav_uae, ), BottomNavigationBarItem( - icon: Icon(Icons.bar_chart), - // label: 'Competitiveness', +icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.ranking, + color: _getSelectedIndex(currentRoute) == 2 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), + // label: 'Competitiveness', label: AppLocalizations.of(context)!.nav_competitiveness, ), BottomNavigationBarItem( - icon: Icon(Icons.public), - // label: 'Country Profile', + icon: Padding( + padding: const EdgeInsets.only(bottom: 4), // Adjust this for spacing + child: Image.asset( + BottomBarAssetIconPath.globe, + color: _getSelectedIndex(currentRoute) == 2 ? Colors.black : Color(0xFF989898), + width: 20, + height: 20, + ),), + // label: 'Country Profile', label: AppLocalizations.of(context)!.country_profile, ), ], key: bottomNavKey, selectedItemColor: Colors.black, unselectedItemColor: Colors.grey, + selectedLabelStyle: TextStyle( + fontSize: 10, // Font size for unselected label + ), + unselectedLabelStyle: TextStyle( + fontSize: 10, // Font size for unselected label + ), showUnselectedLabels: true, ), body: widget.body, diff --git a/pubspec.lock b/pubspec.lock index f56d59cd..99484f5b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" analyzer_plugin: dependency: transitive description: @@ -210,10 +210,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" connectivity_plus: dependency: "direct main" description: @@ -306,10 +306,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" + sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 url: "https://pub.dev" source: hosted - version: "1.0.0+6.7.0" + version: "1.0.0+6.11.0" dart_style: dependency: transitive description: @@ -433,10 +433,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" + sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" url: "https://pub.dev" source: hosted - version: "0.69.2" + version: "0.70.2" flutter: dependency: "direct main" description: flutter @@ -837,18 +837,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -877,10 +877,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" mailer: dependency: "direct main" description: @@ -1268,7 +1268,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" smart_api_client: dependency: "direct main" description: @@ -1352,10 +1352,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -1384,10 +1384,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1424,10 +1424,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" the_validator: dependency: "direct main" description: @@ -1592,10 +1592,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 41ccf85d..907eb18d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.69.2 + fl_chart: ^0.70.2 tutorial_coach_mark: ^1.2.12 marquee: ^2.2.3 url_launcher: ^6.3.1 From 5b922b43557b43322efc2355f9d9a6c5a07fca6a Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 6 Feb 2025 12:32:46 +0530 Subject: [PATCH 18/18] login and register UI changes added --- ...kotlin-compiler-3932177861650478103.salive | 0 android/app/build.gradle | 4 +- assets/icons/drawer/feedback.png | Bin 0 -> 366 bytes assets/icons/drawer/guide.png | Bin 0 -> 468 bytes assets/icons/drawer/manageusr.png | Bin 0 -> 339 bytes assets/icons/misc/save.png | Bin 0 -> 330 bytes lib/config/my_router.dart | 18 +- .../icons/drawer_asset_icon_path.dart | 3 + .../icons/misc_icon_asset_path.dart | 2 + .../auth_verification/registration.dart | 222 +++-- lib/presentation/Screens/profilepage.dart | 812 +++++++++--------- .../routes/auth_routes/login_route.dart | 162 ++-- .../auth_routes/pocketbase_service.dart | 66 ++ .../tab_routes/home_route.dart | 230 ++--- .../Drawer Items/edit_profile.dart | 57 +- .../drawer_routes/custom_drawer_routes.dart | 24 +- pubspec.yaml | 6 +- 17 files changed, 945 insertions(+), 661 deletions(-) delete mode 100644 android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive create mode 100644 assets/icons/drawer/feedback.png create mode 100644 assets/icons/drawer/guide.png create mode 100644 assets/icons/drawer/manageusr.png create mode 100644 assets/icons/misc/save.png create mode 100644 lib/presentation/routes/auth_routes/pocketbase_service.dart diff --git a/android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive b/android/.kotlin/sessions/kotlin-compiler-3932177861650478103.salive deleted file mode 100644 index e69de29b..00000000 diff --git a/android/app/build.gradle b/android/app/build.gradle index 6891a8ad..374c1b19 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 = "7" + flutterVersionCode = "9" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.6" + flutterVersionName = "1.0.8" } def keystorePropertiesFile = rootProject.file("key.properties") diff --git a/assets/icons/drawer/feedback.png b/assets/icons/drawer/feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..2d190a84fb5db235be2f5dab0f7c930074498f60 GIT binary patch literal 366 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`j)FbFd;%$g$s6l5$8 za(7}_cTVOdki(Mh=Xfs-haCjm_D6o>DQWPIP<1=i zTgo25_>LhEh%b3sDcqUxp-WRyRbkScM=3_IB-br^fm1op@p*cm$#V;oczkSxp@Lh`D@j>&?zsC z*Drj=uab1zBsApJPyf``zKxbu3G26MNHx2!epzR%e_^t%!reeGn@_4Gz~Eu8O4*&oF literal 0 HcmV?d00001 diff --git a/assets/icons/drawer/guide.png b/assets/icons/drawer/guide.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9c837898ccae2d844e02e691f31f6be544e0de GIT binary patch literal 468 zcmV;_0W1EAP)}`g=&$FywDfj~^CbE)Yt`ZWFTU&7H^T<@!mTL)K_emcTeHX;Cx8!HkU7?^W zr+l;5+gNVb7W90+H`V`SS@L$9uSGG&S^H!`v3n&^$>Sj8CksmEjMubixiuJ2$3$E# z4sXUWUc5$2wn82UhgK3+!V!qOAm5ikoieU%M;J%8nIsQ^j>81I+{xat)W-U{nfT2J ztWW`+G26nY&Ra7^*JTdN_>C$--U_(#I0RK<8oxT_%Xs_1o&{e{`gR$_Jc}~`0000< KMNUMnLSTYrwZY*4 literal 0 HcmV?d00001 diff --git a/assets/icons/drawer/manageusr.png b/assets/icons/drawer/manageusr.png new file mode 100644 index 0000000000000000000000000000000000000000..cfcba90b10d43d078774a703e364d9b1fba949a9 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W;!3HFgc;@~FQk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFK>iC)7srqa#;KF{3N|?iv|gXjw1L^l*w9#5 z`vTvQ3m$73);btF>pE&3;Jd=5VltWU%UYeor)182OtYJ@_h$9?&DAddiw?>iadP0j zsx@e1-iExAEZ!&hqoGRNJSm<%p%Sx-FLH9-TJPFx@+&WVqt~eGT{xf{9d20F0|1%?c zq%^0TY`>HuZ&IF}>*s2Js+e;-)gy9ZT0&@Q1#`L9tm`W7WxEzX5@}S_ jf7t(4cJ0YWnstnC4fVFi1?+1B`kBGg)z4*}Q$iB}b|Hdz literal 0 HcmV?d00001 diff --git a/assets/icons/misc/save.png b/assets/icons/misc/save.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f0b947a8565cb92fa3e6eb5fc49d4b7e34c7c6 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{(Vmu$B+ufw^JHv@?CoaVCJVNk!o z+auf|y`i!10Nc3-Y&{3qzAg^+Sh~&Cx96p|-t;f){(kwSDZuG4gWGGR@MISWcf}+3 z!hCodn>5p}^oZ3h>6ujh(JMn^Q<%95Q$qd9?{AO2>U^#!z3fm>+$Xl{vkw$0>kArf zUt{m~N=T~imdKI;Vst07mA4Gynhq literal 0 HcmV?d00001 diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index e6b1a5fa..9a58cbc2 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -300,6 +300,7 @@ import 'package:uae_stat/presentation/Screens/charts/chart.dart'; import 'package:uae_stat/presentation/Screens/charts/screens/chart_screen.dart'; import 'package:uae_stat/presentation/Screens/demo_home2.dart'; import 'package:uae_stat/presentation/Screens/online_offline_verification/internet_check.dart'; +import 'package:uae_stat/presentation/routes/auth_routes/pocketbase_service.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/getting_started.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; @@ -324,20 +325,33 @@ import '../presentation/routes/drawer_routes/Drawer Items/user_guide/features.da import '../presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart'; import '../presentation/routes/drawer_routes/Drawer Items/manage_users.dart'; +final pbService = PocketBaseService(); + +Future getInitialRoute() async { + bool isLoggedIn = await pbService.loadSession(); + return isLoggedIn ? '/myhomepage' : '/login'; +} + final GoRouter router = GoRouter( routes: [ GoRoute( path: '/', //builder: (context, state) => LoginRoute(), - builder: (context, state) => LoginRoute(isLoginScreen: true), + builder: (context, state) => LoginRoute(), ), + // GoRoute( + // path: '/', + // redirect: (context, state) async { + // return await getInitialRoute(); + // }, + // ), GoRoute( path: '/internetcheck', builder: (context, state) => InternetCheck(), ), GoRoute( path: '/login', - builder: (context, state) => LoginRoute(isLoginScreen: true), + builder: (context, state) => LoginRoute(), //builder: (context, state) => LoginRoute(), ), GoRoute( diff --git a/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart b/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart index 265d41f7..cbd0bc48 100644 --- a/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart +++ b/lib/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart @@ -8,4 +8,7 @@ abstract class DrawerAssetIconPath { static const message = '$_basePath/message.png'; static const scroll = '$_basePath/scroll.png'; static const manageuser = '$_basePath/manageuser.png'; + static const manageUser = '$_basePath/manageusr.png'; + static const guide = '$_basePath/guide.png'; + static const feedback = '$_basePath/feedback.png'; } diff --git a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart index 55f0bdcd..7912f789 100644 --- a/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart +++ b/lib/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart @@ -9,4 +9,6 @@ abstract class MiscIconAssetPath { static const vector = '$_basePath/vector.png'; static const visibleOn = '$_basePath/visible_on.png'; static const visibilityOff = '$_basePath/visibility_off.png'; + static const save = '$_basePath/save.png'; + } diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index 973d3481..e8008c78 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -271,7 +271,13 @@ class _RegisterScreenState extends State { 'Email ID already exists. Please use a different email.'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: () { + Navigator.pop(context); + setState(() { + isRegistering = + false; // Re-enable the button after error + }); + }, child: Text('OK'), ), ], @@ -356,6 +362,7 @@ class _RegisterScreenState extends State { }, ), ], + backgroundColor: Colors.white, ), backgroundColor: Colors.white, body: registrationSuccess @@ -474,8 +481,8 @@ class _RegisterScreenState extends State { ), ) : Padding( - padding: const EdgeInsets.only(top: 5.0,bottom: 20, left: 30,right: 30), - + padding: const EdgeInsets.only( + top: 5.0, bottom: 20, left: 30, right: 30), child: Form( key: _formKey, child: SingleChildScrollView( @@ -502,13 +509,23 @@ class _RegisterScreenState extends State { hintText: _showHints[0] ? AppLocalizations.of(context)!.register_name : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), prefixIcon: Padding( - padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), child: Image.asset( MiscIconAssetPath.person, - color: Color(0xFF90B0D5), + fit: BoxFit.fitHeight, + height: 25, width: 25, - height: 15, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, ), ), @@ -520,20 +537,29 @@ class _RegisterScreenState extends State { // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), -borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, - width: 1), // Enabled border - ), + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border ), - focusedErrorBorder: OutlineInputBorder( + focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -554,32 +580,52 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 .enter_your_email : null, // _showHints[1] ? 'Enter your email' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), prefixIcon: Padding( - padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), child: Image.asset( MiscIconAssetPath.vector, - color: Color(0xFF90B0D5), + fit: BoxFit.fitHeight, + height: 20, width: 25, - height: 15, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade100, ), ), + // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), -borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, width: 1), - ), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border ), - focusedErrorBorder: OutlineInputBorder( + focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -603,15 +649,26 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 .enter_your_password : null, // _showHints[2] ? 'Enter your password' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), prefixIcon: Padding( - padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), child: Image.asset( MiscIconAssetPath.lock, - color: Color(0xFF90B0D5), + fit: BoxFit.fitHeight, + height: 25, width: 25, - height: 15, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, ), ), + // prefixIcon: Icon( // Icons.lock, // color: Color(0xFF90B0D5), @@ -626,15 +683,15 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 icon: Image.asset( _obscurePassword - ?MiscIconAssetPath.visibilityOff - :MiscIconAssetPath.visibleOn, + ? MiscIconAssetPath.visibilityOff + : MiscIconAssetPath.visibleOn, - color: Color(0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + color: Color( + 0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. width: 24, height: 24, ), - onPressed: () { setState(() { _obscurePassword = !_obscurePassword; @@ -644,20 +701,29 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 // border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), -borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, width: 1), - ), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border ), - focusedErrorBorder: OutlineInputBorder( + focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color ), counterText: '', @@ -678,23 +744,34 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 .register_Confirm_password : null, // _showHints[3] ? 'Confirm password' : null, + prefixIconConstraints: const BoxConstraints( + maxWidth: 25 + 16 + 10, + maxHeight: 25 + (8 * 2), + ), prefixIcon: Padding( - padding: const EdgeInsets.only(bottom: 1.0,top:1.0,right:10.0,left:15.0 ), // Adjust the padding as needed + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 10, + ), child: Image.asset( MiscIconAssetPath.lock, - color: Color(0xFF90B0D5), + fit: BoxFit.fitHeight, + height: 25, width: 25, - height: 15, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade300, ), ), - suffixIcon: IconButton( + suffixIcon: IconButton( icon: Image.asset( _obscureConfirmPassword - ?MiscIconAssetPath.visibilityOff - :MiscIconAssetPath.visibleOn, + ? MiscIconAssetPath.visibilityOff + : MiscIconAssetPath.visibleOn, - color: Color(0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. + color: Color( + 0xFF9EA2A9), // Apply color if needed, but keep in mind `Image.asset` might not support color directly. width: 24, height: 24, ), @@ -710,20 +787,29 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 // ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), -borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade400, + borderSide: BorderSide( + color: MyTheme.topicColor( + IndicatorTopic.economy) + .shade400, width: 1), - ), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Colors.deepPurple, width: 2), // Focused border + borderSide: BorderSide( + color: Colors.deepPurple, + width: 2), // Focused border ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Error border + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Error border ), - focusedErrorBorder: OutlineInputBorder( + focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFFb22222), width: 1), // Match the error color + borderSide: BorderSide( + color: Color(0xFFb22222), + width: 1), // Match the error color ), counterText: '', hintStyle: TextStyle(color: Color(0xFFC3C6CB)), @@ -748,7 +834,9 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 }); }, side: BorderSide( - color: showError ? Color(0xFFD83731) : Color(0xFF92722A), + color: showError + ? Color(0xFFD83731) + : Color(0xFF92722A), width: 1, ), ), @@ -762,25 +850,31 @@ borderSide: BorderSide(color: MyTheme.topicColor(IndicatorTopic.economy).shade40 text: AppLocalizations.of(context)! .terms_conditions, // text: 'Terms & Conditions', -style: TextStyle(fontWeight: FontWeight.bold, - color: Color(0xFF648CBA), - // Makes the text bold - decoration: TextDecoration.underline, - decorationColor: Color(0xFF648CBA), - decorationThickness: 1), - ), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Color(0xFF648CBA), + // Makes the text bold + decoration: + TextDecoration.underline, + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), + ), TextSpan( text: AppLocalizations.of(context)! .t_and), TextSpan( text: AppLocalizations.of(context)! .privacy_policy, - style: TextStyle(fontWeight: FontWeight.bold, - color: Color(0xFF648CBA), - decoration: TextDecoration.underline, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + fontFamily: 'Roboto', + color: Color(0xFF648CBA), + decoration: + TextDecoration.underline, decorationColor: Color(0xFF648CBA), decorationThickness: 1), - ), + ), TextSpan( text: AppLocalizations.of(context)! .conditions), @@ -831,7 +925,8 @@ style: TextStyle(fontWeight: FontWeight.bold, SizedBox(width: 8), const Icon( Icons.chevron_right_outlined, - color: Colors.white, // Set your desired color here + color: Colors + .white, // Set your desired color here ) ], ), @@ -841,7 +936,7 @@ style: TextStyle(fontWeight: FontWeight.bold, Text( AppLocalizations.of(context)!.account_confirmation, style: TextStyle( - fontSize: 16, color: Colors.grey[600]), + fontSize: 14, color: Colors.grey[600]), ), SizedBox(height: 15), SizedBox( @@ -868,7 +963,8 @@ style: TextStyle(fontWeight: FontWeight.bold, SizedBox(width: 8), const Icon( Icons.chevron_right_outlined, - color: Colors.white, // Set your desired color here + color: Colors + .white, // Set your desired color here ) ], ), diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index 3274d331..f9667d3b 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -10,6 +10,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; import '../components/my_bottom_nav_bar.dart'; @@ -74,7 +75,7 @@ class _ProfileScreenState extends State { // Regular expression to validate Full Name (no special characters) final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); late Future userDetails; - bool _isHoveringDate = false; + bool _isHoveringDate = false; bool _isHoveringDropdown = false; @override @@ -336,435 +337,450 @@ class _ProfileScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: Icon(Icons.arrow_back_ios_new, color: Colors.black), - onPressed: () { - context.go('/'); - }, - ), - title: Text( - 'My Profile', - style: TextStyle(color: Colors.orange), - ), - actions: [ - TextButton( + return PopScope( + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + context.go('/'); + }, + child: Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: Colors.black), onPressed: () { context.go('/'); }, - child: Text( - 'Logout', - style: TextStyle(color: Colors.orange), + ), + title: Text( + 'My Profile', + style: TextStyle(color: Color(0xFF985400)), + ), + actions: [ + TextButton( + onPressed: () { + context.go('/'); + }, + child: Text( + 'Logout', + style: TextStyle(color: Colors.orange), + ), ), - ), - IconButton( - icon: Icon(Icons.logout, color: Colors.black), - onPressed: () {}, - ), - ], - ), - body: SingleChildScrollView( - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.only(top: 20.0,bottom: 20.0, left: 25,right: 25), - child: Column( - children: [ - // CircleAvatar( - // radius: 50, - // backgroundImage: _profileImage != null - // ? FileImage(_profileImage!) - // : AssetImage("assets/edit_profile/profile.png") - // as ImageProvider, - // child: Align( - // alignment: Alignment.bottomRight, - // child: GestureDetector( - // onTap: _pickImage, // Call `_pickImage` on tap - // child: CircleAvatar( - // radius: 15, - // backgroundColor: Colors.white, - // child: Icon( - // Icons.camera_alt, - // size: 15, - // color: Colors.grey, - // ), - // ), - // ), - // ), - // ), + IconButton( + icon: Icon(Icons.logout, color: Colors.black), + onPressed: () {}, + ), + ], + ), + body: SingleChildScrollView( + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.only( + top: 20.0, bottom: 20.0, left: 25, right: 25), + child: Column( + children: [ + // CircleAvatar( + // radius: 50, + // backgroundImage: _profileImage != null + // ? FileImage(_profileImage!) + // : AssetImage("assets/edit_profile/profile.png") + // as ImageProvider, + // child: Align( + // alignment: Alignment.bottomRight, + // child: GestureDetector( + // onTap: _pickImage, // Call `_pickImage` on tap + // child: CircleAvatar( + // radius: 15, + // backgroundColor: Colors.white, + // child: Icon( + // Icons.camera_alt, + // size: 15, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), - Stack( - alignment: Alignment.center, - children: [ - // CircleAvatar with the profile image - CircleAvatar( - radius: 50, - backgroundImage: _profileImage != null - ? FileImage(_profileImage!) - : AssetImage("assets/edit_profile/profile.png") - as ImageProvider, - child: Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: _pickImage, // Call `_pickImage` on tap - child: CircleAvatar( - radius: 15, - backgroundColor: Colors.white, - child: Icon( - Icons.camera_alt, - size: 15, - color: Colors.grey, + Stack( + alignment: Alignment.center, + children: [ + // CircleAvatar with the profile image + CircleAvatar( + radius: 50, + backgroundImage: _profileImage != null + ? FileImage(_profileImage!) + : AssetImage("assets/edit_profile/profile.png") + as ImageProvider, + child: Align( + alignment: Alignment.bottomRight, + child: GestureDetector( + onTap: _pickImage, // Call `_pickImage` on tap + child: CircleAvatar( + radius: 15, + backgroundColor: Colors.white, + child: Icon( + Icons.camera_alt, + size: 15, + color: Colors.grey, + ), ), ), ), ), - ), - // Conditional loader that shows when _isLoading is true - if (_isLoading) - Positioned( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: - AlwaysStoppedAnimation(Colors.grey), + // Conditional loader that shows when _isLoading is true + if (_isLoading) + Positioned( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation(Colors.grey), + ), ), - ), - ], - ), - - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "User Name", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _usernameController, - focusNode: _focusNodes[0], - decoration: InputDecoration( - // hintText: _showHints[0] ? 'Enter the User Name' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, + ], ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Email ID", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.grey), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - controller: _emailController, - focusNode: _focusNodes[1], - decoration: InputDecoration( - // hintText: - // _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - enabled: false, - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Full Name*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - TextFormField( - enabled: true, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - //RegExp(r"^[a-zA-Z\s]+$"); - final nameRegex = RegExp( - r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); - - if (!nameRegex.hasMatch(value)) { - return 'Invalid Characters'; - } - return null; - }, - controller: _fullNameController, - focusNode: _focusNodes[2], - decoration: InputDecoration( - hintText: _showHints[2] ? 'Enter the name' : null, - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - counterText: '', - ), - maxLength: 40, // Set the maximum length to 20 characters - maxLengthEnforcement: MaxLengthEnforcement.enforced, - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Date of Birth*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - MouseRegion( - onEnter: (_) => setState(() => _isHoveringDate = true), - onExit: (_) => setState(() => _isHoveringDate = false), - child: TextFormField( - controller: _dateController, - focusNode: _focusNodes[3], - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - hintText: _showHints[3] ? 'DD/MM/YYYY' : null, - //_showHints[3] ? 'Select your Date of Birth' : null, - hintStyle: TextStyle(color: Colors.grey), - - suffixIcon: Container( - width: 45, - padding: EdgeInsets.only(right: 1), - alignment: Alignment.center, // Center the icon vertically - child: Icon( - Icons.keyboard_arrow_down_sharp, - color: _isHoveringDate ? Colors.black : Colors.grey, - - ), - ), - ), - readOnly: true, - onTap: _pickDate, - validator: _validateDob, - ),), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Country/Region*", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 10), - MouseRegion( - onEnter: (_) => setState(() => _isHoveringDropdown = true), - onExit: (_) => setState(() => _isHoveringDropdown = false), - child: DropdownButtonFormField( - value: _selectedCountry, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - labelText: 'Select', - ), - icon: Icon(Icons.keyboard_arrow_down_sharp, - color: _isHoveringDropdown ? Colors.black : Colors.grey ), - items: _countries - .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) - .toList(), - onChanged: (String? newValue) { - setState(() { - _selectedCountry = newValue; - }); - }, - validator: _validateDropdown, - ),), - SizedBox(height: 20), - Row( - children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: showError ? Color(0xFFb22222) : Color(0xFF92722A), - width: 1, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 10, - ), - Text.rich( - TextSpan( - text: 'I agree to the ', - style: TextStyle(color: Colors.black), - children: [ - TextSpan( - text: 'Terms & Conditions', - style: TextStyle( - color: Color(0xFF648CBA), - decoration: TextDecoration.underline, - fontWeight: FontWeight.bold, // Makes the text bold - decorationColor: Color(0xFF648CBA), - decorationThickness: 1, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // Add action for Terms & Conditions tap - }, - ), - TextSpan( - text: ' and ', - style: TextStyle(color: Colors.black), - ), - TextSpan( - text: 'Privacy Policy', - style: TextStyle( - color: Color(0xFF648CBA), - decoration: TextDecoration.underline, - fontWeight: FontWeight.bold, // Makes the text bold - decorationColor: Color(0xFF648CBA), - decorationThickness: 1, - - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // Add action for Privacy Policy tap - }, - ), - TextSpan( - text: ' of FCSC.', - style: TextStyle(color: Colors.black), - ), - ], - ), - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.visible, - softWrap: true, - ), - ], - ), - ), - ], - ), - if (showError) + SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: Text( - 'Please agree to terms and conditions', - style: TextStyle( - color: Color(0xFFb22222), - fontSize: 12, + Text( + "User Name", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _usernameController, + focusNode: _focusNodes[0], + decoration: InputDecoration( + // hintText: _showHints[0] ? 'Enter the User Name' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Email ID", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.grey), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + controller: _emailController, + focusNode: _focusNodes[1], + decoration: InputDecoration( + // hintText: + // _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + enabled: false, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Full Name*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + TextFormField( + enabled: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + + //RegExp(r"^[a-zA-Z\s]+$"); + final nameRegex = RegExp( + r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); + + if (!nameRegex.hasMatch(value)) { + return 'Invalid Characters'; + } + return null; + }, + controller: _fullNameController, + focusNode: _focusNodes[2], + decoration: InputDecoration( + hintText: _showHints[2] ? 'Enter the name' : null, + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + counterText: '', + ), + maxLength: 40, // Set the maximum length to 20 characters + maxLengthEnforcement: MaxLengthEnforcement.enforced, + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Date of Birth*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDate = true), + onExit: (_) => setState(() => _isHoveringDate = false), + child: TextFormField( + controller: _dateController, + focusNode: _focusNodes[3], + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: _showHints[3] ? 'DD/MM/YYYY' : null, + //_showHints[3] ? 'Select your Date of Birth' : null, + hintStyle: TextStyle(color: Colors.grey), + + suffixIcon: Container( + width: 45, + padding: EdgeInsets.only(right: 1), + alignment: + Alignment.center, // Center the icon vertically + child: Icon( + Icons.keyboard_arrow_down_sharp, + color: _isHoveringDate ? Colors.black : Colors.grey, ), ), ), + readOnly: true, + onTap: _pickDate, + validator: _validateDob, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region*", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => setState(() => _isHoveringDropdown = true), + onExit: (_) => setState(() => _isHoveringDropdown = false), + child: DropdownButtonFormField( + value: _selectedCountry, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + labelText: 'Select', + ), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: + _isHoveringDropdown ? Colors.black : Colors.grey), + items: _countries + .map((item) => DropdownMenuItem( + value: item, + child: Text(item), + )) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCountry = newValue; + }); + }, + validator: _validateDropdown, + ), + ), + SizedBox(height: 20), + Row( + children: [ + Checkbox( + value: isChecked, + onChanged: (value) { + setState(() { + isChecked = value ?? false; + showError = false; + }); + }, + side: BorderSide( + color: + showError ? Color(0xFFb22222) : Color(0xFF92722A), + width: 1, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + Text.rich( + TextSpan( + text: 'I agree to the ', + style: TextStyle(color: Colors.black), + children: [ + TextSpan( + text: 'Terms & Conditions', + style: TextStyle( + color: Color(0xFF648CBA), + decoration: TextDecoration.underline, + fontWeight: FontWeight + .bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // Add action for Terms & Conditions tap + }, + ), + TextSpan( + text: ' and ', + style: TextStyle(color: Colors.black), + ), + TextSpan( + text: 'Privacy Policy', + style: TextStyle( + color: Color(0xFF648CBA), + decoration: TextDecoration.underline, + fontWeight: FontWeight + .bold, // Makes the text bold + decorationColor: Color(0xFF648CBA), + decorationThickness: 1, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // Add action for Privacy Policy tap + }, + ), + TextSpan( + text: ' of FCSC.', + style: TextStyle(color: Colors.black), + ), + ], + ), + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.visible, + softWrap: true, + ), + ], + ), + ), ], ), - SizedBox(height: 20), - Center( - child: GestureDetector( - onTap: () { - final userId = widget.userId; - final email = _emailController.text; + if (showError) + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text( + 'Please agree to terms and conditions', + style: TextStyle( + color: Color(0xFFb22222), + fontSize: 12, + ), + ), + ), + ], + ), + SizedBox(height: 20), + Center( + child: GestureDetector( + onTap: () { + final userId = widget.userId; + final email = _emailController.text; - context.go('/createNewPw/$userId/$email'); - }, - child: Text( - 'Change Password', - style: TextStyle( - color: Color(0xFF648CBA), + context.go('/createNewPw/$userId/$email'); + }, + child: Text( + 'Change Password', + style: TextStyle( + color: Color(0xFF648CBA), fontSize: 14, - fontWeight: FontWeight.w700, - fontFamily: 'Roboto', - // decoration: TextDecoration.underline), + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + // decoration: TextDecoration.underline), + ), ), ), ), - ), - SizedBox(height: 20), - // ElevatedButton.icon( + SizedBox(height: 20), + // ElevatedButton.icon( ElevatedButton( - onPressed: () { - if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { - _formKey.currentState?.save(); - showConfirmationDialog(context); - } else { - setState(() { - showError = !isChecked; // Show error if the checkbox is not checked - }); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF92722A), - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + onPressed: () { + if ((_formKey.currentState?.validate() ?? false) && + (isChecked)) { + _formKey.currentState?.save(); + showConfirmationDialog(context); + } else { + setState(() { + showError = + !isChecked; // Show error if the checkbox is not checked + }); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, // Center the content - children: [ - Text( - 'Save', - style: TextStyle(color: Colors.white), - ), - SizedBox(width: 8), // Add space between text and icon - Icon( - Icons.save_outlined, - color: Colors.white, - ), - ], - ), - ) - - ], + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, // Center the content + children: [ + Text( + 'Save', + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), // Add space between text and icon + Image.asset( + MiscIconAssetPath.save, + color: Colors.white, + width: 20, + height: 20, + ), + ], + ), + ) + ], + ), ), ), ), ), - //bottomNavigationBar: MyBottomNavBar(), ); } } diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index d68fc20d..e4f1ee20 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -17,6 +17,7 @@ import 'package:uae_stat/presentation/components/lang_toggle.dart'; import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart'; import 'package:uae_stat/presentation/components/space.dart'; import 'package:uae_stat/presentation/components/themed_text_field.dart'; +import 'package:uae_stat/presentation/routes/auth_routes/pocketbase_service.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart'; import 'package:http/http.dart' as http; import '../../Screens/auth_verification/registration.dart'; @@ -27,10 +28,11 @@ import '../../components/my_toggle.dart'; class LoginRoute extends HookConsumerWidget { final pb = PocketBase('https://pb.venbait.in'); + final _pbService = PocketBaseService(); // final _pb = PocketBase('http://127.0.0.1:8090'); - final bool isLoginScreen; // Pass `true` if this is the login screen - LoginRoute({Key? key, required this.isLoginScreen}) : super(key: key); - // LoginRoute({super.key}); + // final bool isLoginScreen; // Pass `true` if this is the login screen + // LoginRoute({Key? key, required this.isLoginScreen}) : super(key: key); + LoginRoute({super.key}); dynamic userData; String? role; @@ -96,52 +98,91 @@ class LoginRoute extends HookConsumerWidget { final forgotPwFormKey = GlobalKey(); final email = await showDialog( context: context, - builder: (dialogCtx) => AlertDialog( - title: Text( - context.translate( - 'Reset Password', - 'إعادة تعيين كلمة المرور', + builder: (dialogCtx) => SizedBox( + // width: 850, // Increase dialog width + child: AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), // Rounded corners ), + elevation: 10, + shadowColor: Colors.black12, + title: Text( + context.translate( + 'Reset Password', + 'إعادة تعيين كلمة المرور', + ), + style: TextStyle( + fontSize: 20, + color: Color(0xFF898C81), + fontWeight: FontWeight.w600), + ), + content: Form( + key: forgotPwFormKey, + child: TextFormField( + validator: FieldValidator.email(), + controller: emailCtl, + decoration: InputDecoration( + labelText: context.translate( + 'Email address', + 'عنوان البريد الإلكتروني', + ), + labelStyle: TextStyle( + fontSize: 16, + color: Color(0xFF898C81), // For label text color + // fontWeight: FontWeight.w600, + ), + ), + ), + ), + actions: [ + TextButton( + onPressed: Navigator.of(dialogCtx, rootNavigator: true).pop, + style: TextButton.styleFrom( + // backgroundColor: Colors.white, // Background color + foregroundColor: Color(0xFF92722A), // Font (text) color + side: BorderSide( + color: Color(0xFF92722A)), // Border outline color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8), // Optional: Rounded corners + ), + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 12), // Optional: Padding + ), + child: Text( + context.translate( + 'Return', + 'يعود', + ), + ), + ), + ElevatedButton( + onPressed: () { + final isValid = forgotPwFormKey.currentState!.validate(); + if (!isValid) return; + Navigator.of(dialogCtx, rootNavigator: true).pop( + emailCtl.text, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF92722A), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 15, vertical: 15), // Optional: Adjusts size + shape: RoundedRectangleBorder( + // Optional: Adds rounded corners + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + context.translate( + 'Send Reset Email', + 'إرسال إعادة تعيين البريد الإلكتروني', + ), + ), + ), + ], ), - content: Form( - key: forgotPwFormKey, - child: TextFormField( - validator: FieldValidator.email(), - controller: emailCtl, - decoration: InputDecoration( - labelText: context.translate( - 'Email address', - 'عنوان البريد الإلكتروني', - ), - ), - ), - ), - actions: [ - TextButton( - onPressed: Navigator.of(dialogCtx, rootNavigator: true).pop, - child: Text( - context.translate( - 'Return', - 'يعود', - ), - ), - ), - ElevatedButton( - onPressed: () { - final isValid = forgotPwFormKey.currentState!.validate(); - if (!isValid) return; - Navigator.of(dialogCtx, rootNavigator: true).pop( - emailCtl.text, - ); - }, - child: Text( - context.translate( - 'Send Reset Email', - 'إرسال إعادة تعيين البريد الإلكتروني', - ), - ), - ), - ], ), ); if (email == null) return; @@ -177,7 +218,7 @@ class LoginRoute extends HookConsumerWidget { ), fontWeight: FontWeight.bold, fontSize: 14, - color: Color(0xFF985400), + color: Color(0xFF985400), ), ), ); @@ -361,7 +402,7 @@ class LoginRoute extends HookConsumerWidget { 6.horizontalSpace, const Icon( Icons.chevron_right_outlined, - color: Colors.white, // Set your desired color here + color: Colors.white, // Set your desired color here ) ], ), @@ -390,8 +431,7 @@ class LoginRoute extends HookConsumerWidget { validator: (text) { if (text == null || text.isEmpty) { return context.translate('Required', 'مطلوب'); - } - else if (text.length < 8) { + } else if (text.length < 8) { return 'The password must be at least 8 characters'; } return FieldValidator.password(minLength: 8)(text); @@ -403,12 +443,14 @@ class LoginRoute extends HookConsumerWidget { imgPath: MiscIconAssetPath.lock, controller: pwCtl, isObscurable: true, - ), // 6.verticalSpace, - Align( - alignment: AlignmentDirectional.topEnd, - child: forgotPwBtn, + Padding( + padding: EdgeInsets.zero, + child: Align( + alignment: AlignmentDirectional.topEnd, + child: forgotPwBtn, + ), ), 10.verticalSpace, loginBtn, @@ -460,7 +502,7 @@ class LoginRoute extends HookConsumerWidget { 'Roboto', 'NotoKufi', ), - fontSize: 18, + fontSize: 14, fontWeight: FontWeight.bold, ), children: [ @@ -482,8 +524,8 @@ class LoginRoute extends HookConsumerWidget { 'سجل الان', ), style: TextStyle( - color: Color(0xFF985400), - ), + color: Color(0xFF985400), + ), ), ], ), @@ -574,7 +616,7 @@ class LoginRoute extends HookConsumerWidget { helloAndPleaseLoginTexts, 42.verticalSpace, form, - 20.verticalSpace, + 15.verticalSpace, dontHaveAnAccountRegisterBtn, 20.verticalSpace, // continueAsGuestBtn, @@ -588,7 +630,7 @@ class LoginRoute extends HookConsumerWidget { // ); // return bgScaffold; return PopScope( - canPop: !isLoginScreen, // Allow back navigation only if not login screen + canPop: false, // Allow back navigation only if not login screen onPopInvokedWithResult: (didPop, result) { if (didPop) return; _showExitConfirmation(context); // Show exit confirmation dialog diff --git a/lib/presentation/routes/auth_routes/pocketbase_service.dart b/lib/presentation/routes/auth_routes/pocketbase_service.dart new file mode 100644 index 00000000..8e5cc2c8 --- /dev/null +++ b/lib/presentation/routes/auth_routes/pocketbase_service.dart @@ -0,0 +1,66 @@ +import 'package:pocketbase/pocketbase.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class PocketBaseService { + final PocketBase pb = PocketBase('https://pb.venbait.in'); + + // Save session to local storage + Future saveSession() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('pb_auth', pb.authStore.token); + await prefs.setString('pb_user', pb.authStore.model.toJson()); + await prefs.setInt('pb_auth_time', DateTime.now().millisecondsSinceEpoch); + } + + // Load session from local storage + Future loadSession() async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('pb_auth'); + final userJson = prefs.getString('pb_user'); + + if (token != null && userJson != null) { + pb.authStore + .save(token, RecordModel.fromJson(userJson as Map)); + + if (!pb.authStore.isValid || isSessionExpired(prefs)) { + await clearSession(); + return false; + } + return true; + } + return false; + } + + // Check if the session is expired (4 days) + bool isSessionExpired(SharedPreferences prefs) { + final savedTime = prefs.getInt('pb_auth_time') ?? 0; + final currentTime = DateTime.now().millisecondsSinceEpoch; + const sessionDuration = Duration(days: 4); + return (currentTime - savedTime) > sessionDuration.inMilliseconds; + } + + // User login + Future loginUser(String email, String password) async { + try { + await pb.collection('users').authWithPassword(email, password); + await saveSession(); + return true; + } catch (e) { + return false; + } + } + + // User logout + Future logoutUser() async { + pb.authStore.clear(); + await clearSession(); + } + + // Clear session from local storage + Future clearSession() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('pb_auth'); + await prefs.remove('pb_user'); + await prefs.remove('pb_auth_time'); + } +} diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index d8674d83..e4d22b45 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -207,7 +207,7 @@ class EconomyStatsWidget extends ConsumerStatefulWidget { class EconomyStatsState extends ConsumerState { List data = []; -final _pb = PocketBase('https://pb.venbait.in'); + final _pb = PocketBase('https://pb.venbait.in'); bool isLoading = true; final GlobalKey cardTopicKey = GlobalKey(); @@ -216,14 +216,13 @@ final _pb = PocketBase('https://pb.venbait.in'); late List homeTargets; late List previousHomeTargets; - void handleSkip() { tutorialCoachMark.skip(); debugPrint('Skip clicked'); - ref.read(chartsTourProvider.notifier).state = true; - ref.read(previousChartsTourProvider.notifier).state = true; - ref.read(homeTourProvider.notifier).state = true; - ref.read( previousHomeTourProvider.notifier).state = true; + ref.read(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; } @@ -257,7 +256,8 @@ final _pb = PocketBase('https://pb.venbait.in'); ref.read(homeTourProvider.notifier).state = true; ref.read(chartsTourProvider.notifier).state = false; debugPrint('Home Tutorial Finished'); - context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } else if (!previousHomeTour) { @@ -271,16 +271,16 @@ final _pb = PocketBase('https://pb.venbait.in'); onFinish: () { ref.read(previousHomeTourProvider.notifier).state = true; debugPrint('Previous Home Tutorial Finished'); - context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); }, )..show(context: context); } - } - void _initTarget(){ + void _initTarget() { final double screenWidth = MediaQuery.of(context).size.width; - homeTargets=[ + homeTargets = [ TargetFocus( identify: 'cardTopicKey', keyTarget: cardTopicKey, @@ -298,7 +298,7 @@ final _pb = PocketBase('https://pb.venbait.in'); align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.70, + height: MediaQuery.of(context).size.height * 0.70, child: Stack( children: [ Positioned( @@ -310,7 +310,8 @@ final _pb = PocketBase('https://pb.venbait.in'); Align( alignment: Alignment.bottomRight, child: Padding( - padding: const EdgeInsets.only(right: 10), // Adjust the value as needed + padding: const EdgeInsets.only( + right: 10), // Adjust the value as needed child: Text( '1/7', style: const TextStyle( @@ -320,7 +321,9 @@ final _pb = PocketBase('https://pb.venbait.in'); ), ), ), - SizedBox(height: 5,), + SizedBox( + height: 5, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -350,12 +353,14 @@ final _pb = PocketBase('https://pb.venbait.in'); 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( padding: EdgeInsets.zero, iconSize: 20, - icon: const Icon(Icons.arrow_forward, color: Colors.white), + icon: const Icon(Icons.arrow_forward, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -366,8 +371,7 @@ final _pb = PocketBase('https://pb.venbait.in'); ], ), ], - ) - ), + )), ], ), ), @@ -399,16 +403,17 @@ final _pb = PocketBase('https://pb.venbait.in'); // targetPosition:TargetPosition(), contents: [ createTargetContent( - text:AppLocalizations.of(context)!.economy, + text: AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 43,), + space: 43, + ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -419,23 +424,22 @@ final _pb = PocketBase('https://pb.venbait.in'); children: [ Align( alignment: Alignment.bottomRight, - child:Padding(padding: EdgeInsets.only(right: 10), - child: - Text( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( '2/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - ) - ), + )), const SizedBox(height: 3), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip(), + onPressed: () => handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -457,11 +461,13 @@ final _pb = PocketBase('https://pb.venbait.in'); 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(); }, @@ -472,11 +478,13 @@ final _pb = PocketBase('https://pb.venbait.in'); 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: () { tutorialCoachMark.next(); }, @@ -535,9 +543,9 @@ final _pb = PocketBase('https://pb.venbait.in'); ]; } - void _initPreviousTarget(){ + void _initPreviousTarget() { final double screenWidth = MediaQuery.of(context).size.width; - previousHomeTargets=[ + previousHomeTargets = [ TargetFocus( identify: 'cardsKey', keyTarget: cardsKey, @@ -546,16 +554,17 @@ final _pb = PocketBase('https://pb.venbait.in'); paddingFocus: 6, contents: [ createTargetContent( - text:AppLocalizations.of(context)!.economy, + text: AppLocalizations.of(context)!.economy, alignment: ContentAlign.bottom, gap: 0, - space: 43,), + space: 43, + ), TargetContent( align: ContentAlign.bottom, - child: - SizedBox( + child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height *0.5, // Set an appropriate height for the Stack + height: MediaQuery.of(context).size.height * + 0.5, // Set an appropriate height for the Stack child: Stack( children: [ Positioned( @@ -566,23 +575,24 @@ final _pb = PocketBase('https://pb.venbait.in'); children: [ Align( alignment: Alignment.bottomRight, - child:Padding(padding: EdgeInsets.only(right: 10), - child: - Text( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( '2/7', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), - ) + )), + SizedBox( + height: 5, ), - SizedBox(height: 5,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () =>handleSkip(), + onPressed: () => handleSkip(), style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -600,17 +610,18 @@ final _pb = PocketBase('https://pb.venbait.in'); ), ), Row( - children: [ Row( children: [ 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( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, + color: Colors.white), onPressed: () { tutorialCoachMark.next(); }, @@ -621,15 +632,24 @@ final _pb = PocketBase('https://pb.venbait.in'); 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(previousHomeTourProvider.notifier).state=true; - ref.read(chartsTourProvider.notifier).state=false; - context.go('/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); + ref + .read(previousHomeTourProvider + .notifier) + .state = true; + ref + .read(chartsTourProvider.notifier) + .state = false; + context.go( + '/chartScreen/hotels?bgColor=0xFF90B0D5&mainTopic=ECONOMY&title=Hotel+Establishments'); tutorialCoachMark.finish(); }, ), @@ -673,7 +693,7 @@ final _pb = PocketBase('https://pb.venbait.in'); paddingFocus: 16, contents: [ createTargetContent( - text:AppLocalizations.of(context)!.home_topic, + text: AppLocalizations.of(context)!.home_topic, alignment: ContentAlign.bottom, gap: 0, space: 43, @@ -682,7 +702,7 @@ final _pb = PocketBase('https://pb.venbait.in'); align: ContentAlign.bottom, child: SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height*0.69, + height: MediaQuery.of(context).size.height * 0.69, child: Stack( children: [ Positioned( @@ -694,7 +714,7 @@ final _pb = PocketBase('https://pb.venbait.in'); Align( alignment: Alignment.bottomRight, child: Padding( - padding:EdgeInsets.only(right: 10), + padding: EdgeInsets.only(right: 10), child: Text( '1/7', style: const TextStyle( @@ -735,11 +755,13 @@ final _pb = PocketBase('https://pb.venbait.in'); 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(); }, @@ -777,15 +799,13 @@ final _pb = PocketBase('https://pb.venbait.in'); ]; } - -@override - void initState() { -super.initState(); -final locale = ref.read(localeProvider); -fetchData(locale?.languageCode ?? 'en'); -_fetchUserData(); - } - + @override + void initState() { + super.initState(); + final locale = ref.read(localeProvider); + fetchData(locale?.languageCode ?? 'en'); + _fetchUserData(); + } // @override // void didChangeDependencies() { @@ -795,43 +815,38 @@ _fetchUserData(); // fetchData(locale); // Pass the locale to the fetchData method // } + Future getUserId() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('userId'); // Retrieve the userId + } -Future getUserId() async { -final prefs = await SharedPreferences.getInstance(); -return prefs.getString('userId'); // Retrieve the userId - } - - -Future _fetchUserData() async { -try { -final userId = await getUserId(); -print('EDIT PROFILE isPageLoad'); -final adminAuth = await _pb.admins - .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); -final adminToken = adminAuth.token; -print('adminToken- $adminToken'); -final userDetailsResponse = await _pb.collection('users').getOne( -userId!, -headers: { -'Authorization': 'Bearer $adminToken', - }, - ); -final loginCount = userDetailsResponse.data['login_count']; -print(loginCount); -if (loginCount == 1) { -WidgetsBinding.instance.addPostFrameCallback((_) { -_starTourRender(); - }); - } - } catch (e) { -print('Error fetching user details: $e'); - } - } - - - - - + Future _fetchUserData() async { + try { + final userId = await getUserId(); + print('EDIT PROFILE isPageLoad'); + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + print('adminToken- $adminToken'); + final userDetailsResponse = await _pb.collection('users').getOne( + userId!, + headers: { + 'Authorization': 'Bearer $adminToken', + }, + ); + final loginCount = userDetailsResponse.data['login_count']; + print(loginCount); + if (loginCount == 1) { + setState(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + _starTourRender(); + }); + }); + } + } catch (e) { + print('Error fetching user details: $e'); + } + } Future fetchData(locale) async { const baseUrl = 'https://pb.venbait.in/api/getHomePageData'; @@ -940,7 +955,7 @@ print('Error fetching user details: $e'); crossAxisAlignment: CrossAxisAlignment.start, children: [ RoundedCornerContainer( - key: mainTopic['main_topic'] =='ECONOMY' + key: mainTopic['main_topic'] == 'ECONOMY' ? cardTopicKey : null, text: mainTopic['main_topic'], @@ -954,7 +969,7 @@ print('Error fetching user details: $e'); SizedBox( height: myheight / 4.8, // Set dynamic height child: Container( - key:mainTopic['main_topic'] == 'ECONOMY' + key: mainTopic['main_topic'] == 'ECONOMY' ? cardsKey : null, // color: Colors.grey[200], // Set a background color for the scrollable container @@ -1176,7 +1191,6 @@ class InfoCard extends StatelessWidget { final encodedTitle = Uri.encodeComponent(title); final encodedKey = Uri.encodeQueryComponent('home'); - return GestureDetector( onTap: () { context.go( @@ -1263,4 +1277,4 @@ class InfoCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index e8a36040..55d92b96 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -11,6 +11,7 @@ import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; class EditProfile extends StatefulWidget { @@ -424,11 +425,11 @@ class _EditProfileState extends State { @override Widget build(BuildContext context) { return BaseScaffold( - title: Text(AppLocalizations.of(context)!.my_profile), + title: Text(AppLocalizations.of(context)!.my_profile, + style: TextStyle(color: Color(0xFF985400)),), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0), - child: Column( children: [ SingleChildScrollView( @@ -436,8 +437,9 @@ class _EditProfileState extends State { key: _formKey, child: Padding( // padding: const EdgeInsets.all(20.0), - padding: const EdgeInsets.only(top: 20.0,bottom: 20.0, left: 25,right: 25), - child: isPageLoad +padding: const EdgeInsets.only( + top: 20.0, bottom: 20.0, left: 25, right: 25), + child: isPageLoad ? Center( child: CircularProgressIndicator(), ) @@ -727,11 +729,11 @@ class _EditProfileState extends State { suffixIcon: Container( width: 45, padding: EdgeInsets.only(right: 1), - alignment: Alignment.center, // Center the icon vertically + alignment: Alignment + .center, // Center the icon vertically child: Icon( Icons.keyboard_arrow_down_sharp, color: Colors.grey, - ), ), enabled: !_isProfileCompleted, @@ -755,8 +757,10 @@ class _EditProfileState extends State { ), 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( @@ -766,7 +770,9 @@ class _EditProfileState extends State { labelText: '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( @@ -781,7 +787,8 @@ class _EditProfileState extends State { }); }, validator: _validateDropdown, - ),), + ), + ), SizedBox(height: 20), if (!_isProfileCompleted) // Conditional rendering Column( @@ -827,7 +834,8 @@ class _EditProfileState extends State { .terms_conditions, style: TextStyle( // color: Colors.blue, - color: Color(0xFF648CBA), + color: + Color(0xFF648CBA), decoration: TextDecoration .underline, @@ -854,7 +862,7 @@ class _EditProfileState extends State { .privacy_policy, style: TextStyle( // color: Colors.blue, - color: Color(0xFF648CBA), + color: Color(0xFF648CBA), decoration: TextDecoration .underline, @@ -911,7 +919,8 @@ class _EditProfileState extends State { child: GestureDetector( onTap: () { final email = _emailController.text; - context.go('/createNewPw/$userId/$email?key=editProfile'); + context.go( + '/createNewPw/$userId/$email?key=editProfile'); }, child: Text( AppLocalizations.of(context)! @@ -949,14 +958,14 @@ class _EditProfileState extends State { // ), ElevatedButton( onPressed: () { - if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { - _formKey.currentState?.save(); + // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { + // _formKey.currentState?.save(); showConfirmationDialog(context); - } else { - setState(() { - showError = !isChecked; // Show error if the checkbox is not checked - }); - } + // } else { + // setState(() { + // showError = !isChecked; // Show error if the checkbox is not checked + // }); + // } }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF92722A), @@ -969,13 +978,15 @@ class _EditProfileState extends State { mainAxisAlignment: MainAxisAlignment.center, // Center the content children: [ Text( - 'Save', + AppLocalizations.of(context)!.save, style: TextStyle(color: Colors.white), ), SizedBox(width: 8), // Add space between text and icon - Icon( - Icons.save_outlined, + Image.asset( + MiscIconAssetPath.save, color: Colors.white, + width: 20, + height: 20, ), ], ), diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 9fb351b9..5067f8da 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/bottom_bar_asset_icon_path.dart'; +import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/drawer_asset_icon_path.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/components/my_toggle.dart'; @@ -734,15 +735,25 @@ class _BaseScaffoldState extends ConsumerState { ), if (userId != 'guest') ListTile( - leading: Icon(Icons.feedback), + leading: Image.asset( + DrawerAssetIconPath.feedback, + color: Colors.black , + width: 20, + height: 20, + ), // title: const Text('Feedback'), title: Text(AppLocalizations.of(context)!.feedback_title), onTap: () => context.go('/feedback'), ), if (role == 'admin') ListTile( - leading: Icon(Icons.manage_accounts), - // title: Text('Manage User'), +leading: Image.asset( + DrawerAssetIconPath.manageUser, + color: Colors.black , + width: 20, + height: 20, + ), + // title: Text('Manage User'), title: Text( AppLocalizations.of(context)!.manage_user, ), @@ -754,7 +765,12 @@ class _BaseScaffoldState extends ConsumerState { onTap: () => context.go('/bookmark'), ), ListTile( - leading: Icon(Icons.book), + leading: Image.asset( + DrawerAssetIconPath.guide, + color: Colors.black , + width: 20, + height: 20, + ), // title: const Text('User Guide'), title: Text(AppLocalizations.of(context)!.guide_title), onTap: () => context.go('/user-guide'), diff --git a/pubspec.yaml b/pubspec.yaml index 907eb18d..0b9e373d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" #version: 0.5.10 -version: 1.0.6+7 +version: 1.0.8+9 environment: sdk: ">=3.2.3 <4.0.0" @@ -120,6 +120,10 @@ flutter: - assets/icons/misc/vector.png - assets/icons/misc/visible_on.png - assets/icons/misc/visibility_off.png + - assets/icons/misc/save.png + - assets/icons/drawer/guide.png + - assets/icons/drawer/feedback.png + - assets/icons/drawer/manageusr.png fonts: - family: Segoe