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 4de43a5c..03eff4b6 100644 Binary files a/assets/banners/fcsc.png and b/assets/banners/fcsc.png differ diff --git a/assets/icons/uae_numbers/bookmarks.png b/assets/icons/uae_numbers/bookmarks.png new file mode 100644 index 00000000..82d02a7b Binary files /dev/null and b/assets/icons/uae_numbers/bookmarks.png differ diff --git a/assets/icons/uae_numbers/share.png b/assets/icons/uae_numbers/share.png new file mode 100644 index 00000000..0014595d Binary files /dev/null and b/assets/icons/uae_numbers/share.png differ diff --git a/assets/logos/fcsc.png b/assets/logos/fcsc.png index 044f9490..dfbf3987 100644 Binary files a/assets/logos/fcsc.png and b/assets/logos/fcsc.png differ diff --git a/assets/splash_screen/logo.png b/assets/splash_screen/logo.png index 8c3218e8..03eff4b6 100644 Binary files a/assets/splash_screen/logo.png and b/assets/splash_screen/logo.png differ diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 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