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(