all functonality added in new router flow

This commit is contained in:
venbaittech 2024-12-12 13:51:27 +05:30
parent 617379deef
commit c75ae680b0
10 changed files with 1528 additions and 439 deletions

6
.run/main.dart.run.xml Normal file
View File

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2" />
</configuration>
</component>

View File

@ -303,6 +303,7 @@ import '../presentation/Screens/auth_verification/otp_verification.dart';
import '../presentation/Screens/profilepage.dart';
import '../presentation/routes/auth_routes/login_route.dart';
import '../presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
import '../presentation/routes/drawer_routes/Drawer Items/edit_profile.dart';
import '../presentation/routes/drawer_routes/Drawer Items/feedback.dart';
import '../presentation/routes/drawer_routes/Drawer Items/manage_users.dart';
@ -311,7 +312,7 @@ final GoRouter router = GoRouter(
GoRoute(
path: '/',
//builder: (context, state) => LoginRoute(),
builder: (context, state) => MyHomePage(),
builder: (context, state) => LoginRoute(),
),
GoRoute(
path: '/myhomepage',
@ -328,10 +329,11 @@ final GoRouter router = GoRouter(
),
),
GoRoute(
path: '/changepassword',
builder: (context, state) => Changepassword(
userId: '',
),
path: '/changepassword/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return Changepassword(userId: userId);
},
),
GoRoute(
path: '/confirmpasswd',
@ -347,10 +349,11 @@ final GoRouter router = GoRouter(
builder: (context, state) => FeedbackForm(),
),
GoRoute(
path: '/profile',
builder: (context, state) => ProfileScreen(
userId: '',
),
path: '/profile/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return ProfileScreen(userId: userId);
},
),
// GoRoute(
// path: '/manageuser',
@ -360,10 +363,13 @@ final GoRouter router = GoRouter(
// return ManageUserRouter(title: title);
// },
// ),
GoRoute(
path: '/editProfile',
builder: (context, state) => EditProfile(),
),
GoRoute(
path: '/manageuser',
builder: (context, state) => ManageUserRouter(),
),
],
);

View File

@ -58,7 +58,7 @@ class _ResetPasswordScreenState extends State<Changepassword> {
//print('userDetails ->: $userData');
if (userData['items'].isNotEmpty) {
// Email exists; retrieve user ID
final userId = userData['items'][0]['id'];
// final userId = userData['items'][0]['id'];
// Proceed with OTP request
final otpResponse = await http.post(
@ -87,17 +87,15 @@ class _ResetPasswordScreenState extends State<Changepassword> {
);
// Navigate to the Email Verification screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EmailVerificationScreen(
email: email,
userId: widget.userId, // Pass user ID to the next screen
otp: otp,
otpId: otpId,
sendVerificationCode: sendVerificationCode,
),
),
context.go(
'/mailverification',
extra: {
'email': email,
'userId': widget.userId, // Pass user ID to the next screen
'otp': otp,
'otpId': otpId,
'sendVerificationCode': sendVerificationCode,
},
);
} else {
final error =
@ -132,6 +130,12 @@ class _ResetPasswordScreenState extends State<Changepassword> {
}
}
@override
void initState() {
super.initState();
print(widget.userId);
}
@override
void dispose() {
_emailController.dispose();

View File

@ -6,7 +6,6 @@ import 'package:pocketbase/pocketbase.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/presentation/Screens/profilepage.dart';
import '../../../infrastructure/services/pocketbase_service.dart';
import '../../routes/auth_routes/login_route.dart';
class RegisterScreen extends StatefulWidget {
@ -194,7 +193,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
'email': _emailController.text,
'password': _passwordController.text,
'passwordConfirm': _passwordController.text,
// 'verified': true,
'status': 'Pending',
}, headers: {
'Authorization': adminToken
});

View File

@ -9,8 +9,12 @@ import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
import '../components/my_bottom_nav_bar.dart';
import 'auth_verification/changepassword.dart';
import 'demo_home.dart';
class ProfileScreen extends StatefulWidget {
final String userId; // Add this field to hold the user ID
@ -23,6 +27,25 @@ class ProfileScreen extends StatefulWidget {
class _ProfileScreenState extends State<ProfileScreen> {
final _pb = PocketBase('https://pb.venbait.in');
// final _pb = PocketBase('http://127.0.0.1:8090');
// Add focus nodes and hint states
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
final List<bool> _showHints = [
true,
true,
true,
true
]; // One for each TextField
String _getControllerText(int index) {
if (index == 0) return _usernameController.text;
if (index == 1) return _emailController.text;
if (index == 2) return _fullNameController.text;
if (index == 3) return _dateController.text;
return '';
}
final _formKey = GlobalKey<FormState>();
final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _dateController = TextEditingController();
@ -52,12 +75,58 @@ class _ProfileScreenState extends State<ProfileScreen> {
@override
void initState() {
super.initState();
print(widget.userId);
// print(widget.userId);
// Add listeners for focus nodes
for (int i = 0; i < _focusNodes.length; i++) {
_focusNodes[i].addListener(() {
setState(() {
// Hide hint when focused and text is not empty
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
// Listen to text changes
if (i == 0) {
_usernameController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 1) {
_emailController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 1) {
_fullNameController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 2) {
_dateController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
}
}
_fetchUserData(); // Call the function to fetch user data
}
@override
void dispose() {
for (var focusNode in _focusNodes) {
focusNode.dispose();
}
_fullNameController.dispose();
_dateController.dispose();
super.dispose();
@ -68,6 +137,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
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(
widget.userId,
headers: {
@ -125,7 +195,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
// Function to validate the DOB field
String? _validateDob(String? value) {
if (value == null || value.isEmpty) {
return 'Please select your date of birth';
return 'Required';
}
final DateTime selectedDate = _selectedDate!;
@ -140,7 +210,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
String? _validateDropdown(String? value) {
if (value == null || value.isEmpty) {
return 'Please select an option';
return 'Required';
}
return null;
}
@ -201,6 +271,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
_resetFormFields();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Profile updated successfully!")));
context.go('/myhomepage');
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to update profile: $error")));
@ -299,8 +370,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
SizedBox(height: 10),
TextFormField(
controller: _usernameController,
focusNode: _focusNodes[0],
decoration: InputDecoration(
hintText: 'Mohammad Hassan',
hintText: _showHints[0] ? 'Mohammad Hassan' : null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
@ -322,8 +394,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
SizedBox(height: 10),
TextFormField(
controller: _emailController,
focusNode: _focusNodes[1],
decoration: InputDecoration(
hintText: 'mohammad.hassan@fcsc.gov.ae',
hintText:
_showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
@ -349,15 +423,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (value == null || value.isEmpty) {
return 'Required';
}
final nameRegex = RegExp(r"^[a-zA-Z\s]+$");
//RegExp(r"^[a-zA-Z\s]+$");
final nameRegex =
RegExp(r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF]+$");
if (!nameRegex.hasMatch(value)) {
return 'Invalid Characters';
}
return null;
},
controller: _fullNameController,
focusNode: _focusNodes[2],
decoration: InputDecoration(
hintText: "Mohammad",
hintText: _showHints[2] ? 'Enter the Full Name' : null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
@ -381,11 +459,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
SizedBox(height: 10),
TextFormField(
controller: _dateController,
focusNode: _focusNodes[3],
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
hintText: 'Select your Date of Birth',
hintText:
_showHints[3] ? 'Select your Date of Birth' : null,
//hintText: 'Select your Date of Birth',
suffixIcon: const Icon(Icons.arrow_drop_down_sharp),
),
readOnly: true,
@ -503,7 +584,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
'Required',
'Please agree to terms and conditions',
style: TextStyle(
color: Colors.red[700],
fontSize: 12,
@ -516,15 +597,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
Center(
child: GestureDetector(
onTap: () {
context.go('/confirmpasswd');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Changepassword(userId: widget.userId)),
);
},
// {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => Changepassword(userId: '',)),
// );
// },
child: Text(
"Change Password",
style: TextStyle(
@ -536,38 +615,35 @@ class _ProfileScreenState extends State<ProfileScreen> {
SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () {
context.go('/myhomepage');
if ((_formKey.currentState?.validate() ?? false) &&
(isChecked)) {
_formKey.currentState?.save();
showConfirmationDialog(context);
} else {
setState(() {
showError =
!isChecked; // Show error if the checkbox is not checked
});
}
// if ((_formKey.currentState?.validate() ?? false) && (isChecked)) {
// _formKey.currentState?.save();
// showConfirmationDialog(context);
// // if (isChecked) {
// // _formKey.currentState?.save();
// // // _confirmSaveProfile();
// // showConfirmationDialog(context);
// // }
// else {
// setState(() {
// showError = !isChecked; // Show error if the checkbox is not checked
// });
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)),
// );
//}
//}
},
// {
// if ((_formKey.currentState?.validate() ?? false) &&
// (isChecked)) {
// _formKey.currentState?.save();
// showConfirmationDialog(context);
// } else {
// setState(() {
// showError =
// !isChecked; // Show error if the checkbox is not checked
// });
// }
// // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) {
// // _formKey.currentState?.save();
// // showConfirmationDialog(context);
// // // if (isChecked) {
// // // _formKey.currentState?.save();
// // // // _confirmSaveProfile();
// // // showConfirmationDialog(context);
// // // }
// // else {
// // setState(() {
// // showError = !isChecked; // Show error if the checkbox is not checked
// // });
//
// // ScaffoldMessenger.of(context).showSnackBar(
// // SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)),
// // );
// //}
// //}
// },
icon: Icon(
Icons.save,
color: Colors.white,

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:the_validator/the_validator.dart';
import 'package:uae_stat/config/my_theme.dart';
import 'package:uae_stat/domain/use_cases/auth_use_case.dart';
@ -17,7 +18,6 @@ import 'package:uae_stat/presentation/components/space.dart';
import 'package:uae_stat/presentation/components/themed_text_field.dart';
import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
import '../../Screens/profilepage.dart';
import '../../Screens/auth_verification/registration.dart';
import 'package:pocketbase/pocketbase.dart';
@ -25,6 +25,7 @@ class LoginRoute extends HookConsumerWidget {
final pb = PocketBase('https://pb.venbait.in');
// final _pb = PocketBase('http://127.0.0.1:8090');
LoginRoute({super.key});
dynamic userData;
static final formKey = GlobalKey<FormState>();
@ -45,21 +46,38 @@ class LoginRoute extends HookConsumerWidget {
'Authorization': 'Bearer $adminToken',
},
);
print('userDetailsLogin: $userDetailsResponse');
// Check if 'is_profile_completed' is true or false
if (userDetailsResponse != null &&
userDetailsResponse.data['is_profile_completed'] != null) {
return userDetailsResponse.data['is_profile_completed'];
} else {
return false; // Default to false if the field is missing or response is null
// Check if user is verified
final bool? isVerified = userDetailsResponse.data['verified'];
final bool? isUserMailVerified =
userDetailsResponse.data['user_mail_verify'];
// Check admin and email verification statuses
if (isVerified == false) {
throw Exception('Admin not approved');
}
if (isUserMailVerified == false) {
throw Exception('Email not verified');
}
userData = userDetailsResponse;
// Check if 'is_profile_completed' is true
final bool isProfileCompleted =
userDetailsResponse.data['is_profile_completed'] ?? false;
return isProfileCompleted;
} catch (e) {
print('Error fetching user details: $e');
return false; // Return false on error
rethrow;
}
}
Future<void> saveUserId(String userId) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('userId', userId); // Save userId locally
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final emailCtl = useTextEditingController();
@ -157,105 +175,121 @@ class LoginRoute extends HookConsumerWidget {
final loginBtn = SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
context.go('/profile');
onPressed: () async {
final isValid = formKey.currentState!.validate();
if (!isValid) return;
final session = await context.loaderWithErrorDialog(
() => ref
.read(
authUseCaseProvider.notifier,
)
.login(
emailCtl.text,
pwCtl.text,
),
errorDialogBuilder: (
error, [
StackTrace? stackTrace,
]) {
if (error == LoginError.invalidEmailPw) {
return context.simpleDialog(
title: context.translate(
'Incorrect credentials',
'أوراق غير صحيحة',
),
content: context.translate(
'Your email or password is invalid. Please try again.',
'بريدك الإلكتروني أو كلمة المرور غير صالحة. حاول مرة اخرى.',
),
);
}
// if (error == LoginError.emailAddressNotVerified) {
// return context.simpleDialog(
// title: context.translate(
// 'Verification Error',
// 'خطأ التحقق',
// ),
// content: context.translate(
// '${emailCtl.text} is not a verified email address. Please check your email for a verification link.',
// '${emailCtl.text} ليس عنوان بريد إلكتروني تم التحقق منه. يرجى التحقق من بريدك الإلكتروني للحصول على رابط التحقق.',
// ),
// extraAction: ElevatedButton(
// onPressed: () async {
// Navigator.of(
// context,
// rootNavigator: true,
// ).pop();
// await context.loaderWithErrorDialog(
// () => ref
// .read(authUseCaseProvider.notifier)
// .requestVerificationEmail(emailCtl.text),
// );
// if (!context.mounted) return;
// context.simpleDialog(
// title: 'Email Re-sent',
// content:
// 'We\'ve sent you the verification email at ${emailCtl.text} again.',
// );
// },
// child: Text(
// context.translate(
// 'I did not receive an email',
// 'لم أتلق بريدًا إلكترونيًا',
// ),
// ),
// ),
// );
// }
return context.simpleDialog();
},
);
if (!context.mounted || session == null) return;
final userId = session.id;
if (userId.isNotEmpty) {
await saveUserId(userId);
}
try {
final bool isProfileComplete = await profileStatus(userId);
print(isProfileComplete);
if (isProfileComplete) {
print('home');
context.go('/myhomepage');
} else {
print('profile');
if (userId != null && userId.isNotEmpty) {
context.go('/profile/$userId');
} else {
print('Error: userId is null or empty.');
}
}
} catch (e) {
// Handle specific errors based on their message
if (e.toString().contains('Admin not approved')) {
context.simpleDialog(
title: context.translate(
'Admin Approval Required', 'موافقة المسؤول مطلوبة'),
content: context.translate(
'Your account has not been approved by the admin.',
'لم تتم الموافقة على حسابك من قبل المسؤول.',
),
);
} else if (e.toString().contains('Email not verified')) {
context.simpleDialog(
title: context.translate(
'Email Not Verified', 'البريد الإلكتروني غير مُحقق'),
content: context.translate(
'Your email address is not verified. Please check your email.',
'عنوان بريدك الإلكتروني غير مُحقق. يرجى التحقق من بريدك الإلكتروني.',
),
);
} else {
print('Unexpected error: $e');
}
}
// context.go('/${context.language}/${BottomNavBarItem.home.routePath}');
},
// async {
// final isValid = formKey.currentState!.validate();
// if (!isValid) return;
// final session = await context.loaderWithErrorDialog(
// () => ref
// .read(
// authUseCaseProvider.notifier,
// )
// .login(
// emailCtl.text,
// pwCtl.text,
// ),
// errorDialogBuilder: (
// error, [
// StackTrace? stackTrace,
// ]) {
// if (error == LoginError.invalidEmailPw) {
// return context.simpleDialog(
// title: context.translate(
// 'Incorrect credentials',
// 'أوراق غير صحيحة',
// ),
// content: context.translate(
// 'Your email or password is invalid. Please try again.',
// 'بريدك الإلكتروني أو كلمة المرور غير صالحة. حاول مرة اخرى.',
// ),
// );
// }
// if (error == LoginError.emailAddressNotVerified) {
// return context.simpleDialog(
// title: context.translate(
// 'Verification Error',
// 'خطأ التحقق',
// ),
// content: context.translate(
// '${emailCtl.text} is not a verified email address. Please check your email for a verification link.',
// '${emailCtl.text} ليس عنوان بريد إلكتروني تم التحقق منه. يرجى التحقق من بريدك الإلكتروني للحصول على رابط التحقق.',
// ),
// extraAction: ElevatedButton(
// onPressed: () {
// context.go('/profile');
// },
// // async {
// // Navigator.of(
// // context,
// // rootNavigator: true,
// // ).pop();
// // await context.loaderWithErrorDialog(
// // () => ref
// // .read(authUseCaseProvider.notifier)
// // .requestVerificationEmail(emailCtl.text),
// // );
// // if (!context.mounted) return;
// // context.simpleDialog(
// // title: 'Email Re-sent',
// // content:
// // 'We\'ve sent you the verification email at ${emailCtl.text} again.',
// // );
// // },
// child: Text(
// context.translate(
// 'I did not receive an email',
// 'لم أتلق بريدًا إلكترونيًا',
// ),
// ),
// ),
// );
// }
// return context.simpleDialog();
// },
// );
// // if (!context.mounted || session == null) return;
// // Extract userId from the session
// // final userId = session.id;
// //
// // final bool isUpdate = await profileStatus(userId);
// //
// // print('Is profile completed: $isUpdate');
//
// // if (isUpdate) {
// // Navigator.pushReplacement(
// // context,
// // MaterialPageRoute(builder: (context) => DemoHome()),
// // );
// // }
// // else {
//
// // Navigator.pushReplacement(
// // context,
// // MaterialPageRoute(
// // builder: (context) => ProfileScreen(userId: userId)),
// // );
// //}
//
// // context.go('/${context.language}/${BottomNavBarItem.home.routePath}');
// },
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(

View File

@ -0,0 +1,837 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../Screens/auth_verification/changepassword.dart';
import '../custom_drawer_routes.dart';
class EditProfile extends StatefulWidget {
@override
State<EditProfile> createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
final _pb = PocketBase('https://pb.venbait.in');
// final _pb = PocketBase('http://127.0.0.1:8090');
bool _isProfileCompleted = false;
// Add focus nodes and hint states
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
final List<bool> _showHints = [
true,
true,
true,
true
]; // One for each TextField
String _getControllerText(int index) {
if (index == 0) return _usernameController.text;
if (index == 1) return _emailController.text;
if (index == 2) return _fullNameController.text;
if (index == 3) return _dateController.text;
return '';
}
final _formKey = GlobalKey<FormState>();
final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _dateController = TextEditingController();
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
// To keep track of the selected date
DateTime? _selectedDate;
// Date format for the display
final DateFormat _dateFormat = DateFormat('yyyy-MM-dd');
final List<String> _countries = [
'United Arab Emirates',
'United States',
'India',
'Canada'
];
String? _selectedCountry;
bool isChecked = false;
bool showError = false;
final _picker = ImagePicker();
File? _profileImage;
String _avatarUrl = '';
// Regular expression to validate Full Name (no special characters)
final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$");
late Future<RecordModel> userDetails;
dynamic userId;
@override
void initState() {
super.initState();
// Add listeners for focus nodes
for (int i = 0; i < _focusNodes.length; i++) {
_focusNodes[i].addListener(() {
setState(() {
// Hide hint when focused and text is not empty
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
// Listen to text changes
if (i == 0) {
_usernameController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 1) {
_emailController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 1) {
_fullNameController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
} else if (i == 2) {
_dateController.addListener(() {
setState(() {
_showHints[i] =
!_focusNodes[i].hasFocus && (_getControllerText(i).isEmpty);
});
});
}
}
checkUserId();
}
@override
void dispose() {
for (var focusNode in _focusNodes) {
focusNode.dispose();
}
_fullNameController.dispose();
_dateController.dispose();
super.dispose();
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('userId'); // Retrieve the userId
}
Future<void> checkUserId() async {
userId = await getUserId();
//userId = 'tplu4by67phkfoq';
if (userId != null && userId.isNotEmpty) {
print('User ID: $userId');
_fetchUserData();
} else {
print('No userId found');
// Handle case where userId is not available
}
}
Future<void> _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('userDetails: $userDetailsResponse');
setState(() {
_usernameController.text = userDetailsResponse.data['username'] ?? '';
_emailController.text = userDetailsResponse.data['email'] ?? '';
_fullNameController.text = userDetailsResponse.data['full_name'] ?? '';
_selectedCountry = userDetailsResponse.data['country_region'] ?? '';
_isProfileCompleted =
userDetailsResponse.data['is_profile_completed'] ?? false;
//_isProfileCompleted = true ;
// Parse and format the date
String dateString = userDetailsResponse.data['dob'] ?? '';
if (dateString.isNotEmpty) {
DateTime dob = DateTime.parse(dateString);
_dateController.text =
DateFormat('dd/MM/yyyy').format(dob); // Format to dd/mm/yyyy
} else {
_dateController.text = '';
}
// Set the avatar URL
String avatarFilename = userDetailsResponse.data['avatar'] ?? '';
String recordId = userId;
String collectionId =
userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_';
if (avatarFilename.isNotEmpty && recordId.isNotEmpty) {
_avatarUrl =
'https://pb.venbait.in/api/files/$collectionId/$recordId/$avatarFilename';
} else {
_avatarUrl = ''; // Reset to default or empty
}
// String avatarFilename = userDetailsResponse.data['avatar'] ?? '';
// //print("PROFILE AVAILABLE -$avatarFilename");
// String recordId = userId;
// //String recordId = userDetailsResponse.data['id'] ?? "";
// print("PROFILE AVAILABLE -${userDetailsResponse.data['id']}");
// //print("User ID: $recordId");
//String collectionId = userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_';
// Ensure recordId and avatarFilename are valid
// if (avatarFilename.isNotEmpty && recordId.isNotEmpty) {
// print("PROFILE AVAILABLE");
// String _avatarUrl = 'https://pb.venbait.in/api/files/$collectionId/$recordId/$avatarFilename';
// print("PROFILE AVAILABLE1- $_avatarUrl");
// //_avatarUrl = File(imageUrl); // This won't work directly for a URL, you need to download the image first
// } else {
// print("PROFILE NOT AVAILABLE");
// }
});
} catch (e) {
print('Error fetching user details: $e');
}
}
void _pickImage() async {
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
_profileImage = File(pickedFile.path);
});
}
}
// Function to open the date picker
Future<void> _pickDate() async {
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
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 DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: firstDate,
lastDate: lastDate,
);
if (pickedDate != null && pickedDate != _selectedDate) {
setState(() {
_selectedDate = pickedDate;
_dateController.text = _dateFormat.format(pickedDate);
});
}
}
// Function to validate the DOB field
String? _validateDob(String? value) {
if (value == null || value.isEmpty) {
return 'Required';
}
final DateTime selectedDate = _selectedDate!;
final DateTime today = DateTime.now();
// Check if the selected date is in the future
if (selectedDate.isAfter(today)) {
return 'Date of birth cannot be in the future';
}
return null;
}
String? _validateDropdown(String? value) {
if (value == null || value.isEmpty) {
return 'Required';
}
return null;
}
void _toggleCheckbox(bool? value) {
setState(() {
isChecked = value ?? false;
});
}
void showConfirmationDialog(BuildContext context) async {
final result = await showDialog<bool>(
context: context,
builder: (context) => const ConfirmationDialog(),
);
if (result == true) {
// Validate only the country field
if (_validateDropdown(_selectedCountry) == null) {
try {
String userID = userId;
print("ShowConfirmationuserID - $userID ");
// Retrieve data from the country/region field
String countryRegion =
_selectedCountry ?? ''; // Ensure the country is selected
// Create a multipart request
final uri =
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
// If profile image exists, add it
if (_profileImage != null) {
request.files.add(await http.MultipartFile.fromPath(
'avatar',
_profileImage!.path,
));
}
// Add headers (e.g., authorization)
request.headers['Authorization'] = 'Bearer ${_pb.authStore.token}';
// Send the request
final response = await request.send();
print(response);
// Handle response
if (response.statusCode == 200) {
_resetFormFields();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Profile updated successfully!")),
);
context.go('/myhomepage');
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text("Failed to update profile: ${response.statusCode}")),
);
}
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to update profile: $error")),
);
}
} else {
// Show an error if the country is invalid
setState(() {
showError = true;
});
}
}
}
void _resetFormFields() {
print('reset');
setState(() {
// Reset all text controllers
_fullNameController.clear();
_dateController.clear();
_selectedCountry = null;
isChecked = false;
// Reset profile image
_profileImage = null;
// Reset form validation state
_formKey.currentState?.reset();
});
}
@override
Widget build(BuildContext context) {
return BaseScaffold(
title: Text("Profile"),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0),
child: Column(
children: [
SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(20.0),
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,
),
),
),
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"User Name",
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
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(
"E-mail",
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
SizedBox(height: 10),
TextFormField(
controller: _emailController,
focusNode: _focusNodes[1],
decoration: InputDecoration(
hintText: _showHints[1]
? 'mohammad.hassan@fcsc.gov.ae'
: null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabled: false,
),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Full Name",
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
SizedBox(height: 10),
TextFormField(
enabled: 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]+$");
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),
),
],
),
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,
//hintText: 'Select your Date of Birth',
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(
"Country/Region*",
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
SizedBox(height: 10),
DropdownButtonFormField<String>(
value: _selectedCountry,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
labelText: 'Select',
),
items: _countries
.map((item) => DropdownMenuItem<String>(
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: 'I agree to the ',
style:
TextStyle(color: Colors.black),
children: [
TextSpan(
text: 'Terms & Conditions',
style: TextStyle(
color: Colors.blue,
decoration:
TextDecoration.underline,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
// Add action for Terms & Conditions tap
},
),
TextSpan(
text: ' and ',
style: TextStyle(
color: Colors.black),
),
TextSpan(
text: 'Privacy Policy',
style: TextStyle(
color: Colors.blue,
decoration:
TextDecoration.underline,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
// Add action for Privacy Policy tap
},
),
TextSpan(
text: ' of FCSC.',
style: TextStyle(
color: Colors.black),
),
],
),
textAlign: TextAlign.start,
maxLines: 2,
overflow: TextOverflow.visible,
softWrap: true,
),
],
),
),
],
),
if (showError)
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: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Changepassword(userId: userId)),
);
},
child: Text(
"Change Password",
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline),
),
),
),
SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () {
showConfirmationDialog(context);
// if (_formKey.currentState?.validate() ?? false){
// _formKey.currentState?.save();
// showConfirmationDialog(context);
// } else {
// setState(() {
// showError =
// !isChecked; // Show error if the checkbox is not checked
// });
// }
},
icon: Icon(
Icons.save,
color: Colors.white,
),
label: Text(
'Save',
style: TextStyle(color: Colors.white),
),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF92722A),
minimumSize: Size(double.infinity, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
),
),
],
),
),
),
//bottomNavigationBar: MyBottomNavBar(),
);
}
}
class ConfirmationDialog extends StatelessWidget {
const ConfirmationDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Are you sure you want to save this page?",
style: const TextStyle(
fontSize: 14,
color: Color(0xFF898C81),
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8),
Text.rich(
TextSpan(
text: "Once saved, you will not be able to change your ",
style: TextStyle(
fontSize: 14,
color: Color(0xFF898C81),
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
),
children: [
TextSpan(
text: "Country",
style:
TextStyle(fontWeight: FontWeight.w700), // Bold for "name"
),
TextSpan(
text: " or ",
),
TextSpan(
text: "Profile Image",
style: TextStyle(
fontWeight: FontWeight.w700), // Bold for "date of birth"
),
TextSpan(
text: ".",
),
],
),
textAlign: TextAlign.center,
)
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton(
onPressed: () {
Navigator.of(context).pop(false);
},
style: OutlinedButton.styleFrom(
side: BorderSide(color: Color(0xFF92722A)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7)),
), // Set the border color here
),
child: Text(
"Cancel",
style: TextStyle(color: Color(0xFF92722A)),
),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF92722A), // Brown color
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Confirm',
style: TextStyle(
fontWeight: FontWeight.w500,
),
),
),
],
),
),
SizedBox(
height: 5,
)
],
);
}
}

View File

@ -17,7 +17,6 @@ import '../../../../config/my_theme.dart';
import '../../../../domain/use_cases/preferences_use_case.dart';
import '../../../components/my_toggle.dart';
class FeedbackForm extends StatefulWidget {
const FeedbackForm({super.key});
@ -28,7 +27,7 @@ class FeedbackForm extends StatefulWidget {
class _FeedbackFormState extends State<FeedbackForm>
with WidgetsBindingObserver {
final _pb =
PocketBase('https://pb.venbait.in'); // Initialize PocketBase client
PocketBase('https://pb.venbait.in'); // Initialize PocketBase client
// final _pb =
// PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client
@ -43,34 +42,35 @@ class _FeedbackFormState extends State<FeedbackForm>
bool _isFeedbackFailed = false; // New flag for failed submission
bool _isSmileySelected = true; // Track if smiley is selected
dynamic configEmail;
dynamic userId;
List<Map<String, dynamic>> get _emojiOptions => [
{
"icon": Icons.sentiment_very_dissatisfied,
"label": context.translate("Terrible", "رهيب"), // Translate here
"value": 1,
},
{
"icon": Icons.sentiment_dissatisfied,
"label": context.translate("Bad", "سيء"), // Translate here
"value": 2,
},
{
"icon": Icons.sentiment_neutral,
"label": context.translate("Okay", "تمام"), // Translate here
"value": 3,
},
{
"icon": Icons.sentiment_satisfied,
"label": context.translate("Good", "جيد"), // Translate here
"value": 4,
},
{
"icon": Icons.sentiment_very_satisfied,
"label": context.translate("Amazing", "مدهش"), // Translate here
"value": 5,
},
];
{
"icon": Icons.sentiment_very_dissatisfied,
"label": context.translate("Terrible", "رهيب"), // Translate here
"value": 1,
},
{
"icon": Icons.sentiment_dissatisfied,
"label": context.translate("Bad", "سيء"), // Translate here
"value": 2,
},
{
"icon": Icons.sentiment_neutral,
"label": context.translate("Okay", "تمام"), // Translate here
"value": 3,
},
{
"icon": Icons.sentiment_satisfied,
"label": context.translate("Good", "جيد"), // Translate here
"value": 4,
},
{
"icon": Icons.sentiment_very_satisfied,
"label": context.translate("Amazing", "مدهش"), // Translate here
"value": 5,
},
];
static const int _characterLimit = 1200;
final RegExp _allowedCharacters = RegExp(r'^[a-zA-Z0-9 .,!?-]*$');
@ -83,7 +83,23 @@ class _FeedbackFormState extends State<FeedbackForm>
WidgetsBinding.instance.addObserver(this);
_loadFeedbackText();
_feedbackController.addListener(_handleTextChange);
fetchEmailConfiguration();
// fetchEmailConfiguration();
checkUserId();
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('userId'); // Retrieve the userId
}
Future<void> checkUserId() async {
userId = await getUserId();
if (userId != null && userId.isNotEmpty) {
print('User ID: $userId');
} else {
print('No userId found');
// Handle case where userId is not available
}
}
@override
@ -217,8 +233,8 @@ class _FeedbackFormState extends State<FeedbackForm>
child: _isFeedbackSubmitted
? _buildThankYouMessage()
: _isFeedbackFailed
? _buildFailureMessage()
: _buildFeedbackForm(),
? _buildFailureMessage()
: _buildFeedbackForm(),
),
),
);
@ -303,7 +319,7 @@ class _FeedbackFormState extends State<FeedbackForm>
_buildRatingRow(
context.translate('Ease of use', 'سهولة الاستخدام'),
_easeOfUseRating,
(rating) {
(rating) {
setState(() {
_easeOfUseRating = rating;
});
@ -312,7 +328,7 @@ class _FeedbackFormState extends State<FeedbackForm>
_buildRatingRow(
context.translate('Quality', 'جودة'),
_qualityRating,
(rating) {
(rating) {
setState(() {
_qualityRating = rating;
});
@ -321,7 +337,7 @@ class _FeedbackFormState extends State<FeedbackForm>
_buildRatingRow(
context.translate('Design', 'تصميم'),
_designRating,
(rating) {
(rating) {
setState(() {
_designRating = rating;
});
@ -330,7 +346,7 @@ class _FeedbackFormState extends State<FeedbackForm>
_buildRatingRow(
context.translate('Redundant', 'متكرر'),
_redundancyRating,
(rating) {
(rating) {
setState(() {
_redundancyRating = rating;
});
@ -351,7 +367,7 @@ class _FeedbackFormState extends State<FeedbackForm>
),
),
errorText:
_hasError ? _errorMessage : null, // Show error below field
_hasError ? _errorMessage : null, // Show error below field
),
onChanged: (text) {
// Trigger re-validation and character limit on each change
@ -428,7 +444,7 @@ class _FeedbackFormState extends State<FeedbackForm>
const SizedBox(height: 30),
ElevatedButton(
onPressed: () => context.go('/myhomepage'),
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
child: const Text("Go to Home"),
),
],
@ -495,10 +511,10 @@ class _FeedbackFormState extends State<FeedbackForm>
}
Widget _buildRatingRow(
String label,
double rating,
Function(double) onRatingUpdate,
) {
String label,
double rating,
Function(double) onRatingUpdate,
) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -548,12 +564,13 @@ class _FeedbackFormState extends State<FeedbackForm>
}
final feedbackData = {
"userId": userId,
"ease_of_use": _easeOfUseRating,
"quality": _qualityRating,
"design": _designRating,
"redundancy": _redundancyRating,
"emoji_rating": _emojiOptions[_selectedEmojiIndex!]
["label"], // Emoji rating
["label"], // Emoji rating
"feedback": _feedbackController.text,
};
@ -568,7 +585,7 @@ class _FeedbackFormState extends State<FeedbackForm>
final createdTime = response.created;
// Send email
await sendFeedbackEmail(feedbackData, createdTime);
// await sendFeedbackEmail(feedbackData, createdTime);
await _removeFeedbackText();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Feedback submitted successfully!")),
@ -598,107 +615,107 @@ class _FeedbackFormState extends State<FeedbackForm>
_feedbackController.clear();
}
Future<void> fetchEmailConfiguration() async {
try {
// Fetch data from the email_configuration collection
final response =
await _pb.collection('email_configuration').getFullList();
// Filter the data where the label is "Feedback"
final feedbackConfig = response.firstWhere(
(item) =>
item.data['label'] == 'Feedback', // Accessing the 'data' property
);
// Check if a match is found
if (feedbackConfig != null) {
configEmail = feedbackConfig.data['email']; // Access the 'email' value
} else {
print('No feedback configuration found.');
}
} catch (e) {
print('Error fetching email configuration: $e');
}
}
Future<void> sendFeedbackEmail(
Map<String, dynamic> feedbackData, createdTime) async {
String username = 'emailapikey'; // Your SMTP username (API key)
String password =
'PHtE6r0MFu66jTQp8BAFsP7sH5TwNd4v/+02KwBH5ItACvAES01Tot4okDawqhoiB/FEHfaey4Nvteyf5ePQJG28YW9OCWqyqK3sx/VYSPOZsbq6x00auVwYd0zUVY7pe9ds0yLTvNraNA=='; // Your SMTP password
final smtpServer = SmtpServer('smtp.zeptomail.in',
port: 587,
username: username,
password: password,
ssl: false, // Use TLS
ignoreBadCertificate:
true); // Set to true if you're testing with a self-signed certificate
// Check if additional feedback was provided
String additionalFeedback = feedbackData["feedback"]?.isNotEmpty == true
? feedbackData["feedback"]
: "No additional feedback provided.";
// Format separately for date and time
// Parse the string to DateTime
// Parse the input as UTC and convert to DateTime in UTC timezone
DateTime feedbackDateTime = DateTime.parse(createdTime).toUtc();
// Format date as DD-MM-YYYY in UTC
final String formattedDate =
DateFormat('dd-MM-yyyy').format(feedbackDateTime);
// Format to the desired output: "dd.MM.yyyy HH:mm 'UTC'"
final DateFormat formatter = DateFormat("dd.MM.yyyy HH:mm 'UTC'");
String formattedDateTime = formatter.format(feedbackDateTime);
String email = configEmail; // Direct assignment
// Create the email message
final message = Message()
..from = Address('bbone@venbait.in', 'FCSC')
..recipients.add(email) // Set the recipient email
..subject = 'UAE Stats Feedback'
..text = '''
Dear [App Owner/Admin],
You have received new feedback from a user through the mobile application.
User Details:
1. Name: Guest
2. Date of Submission: $formattedDate
3. Time of Submission: $formattedDateTime
Feedback:
1. How was your experience with us today? Rating: ${feedbackData["emoji_rating"]}
2. How did we perform in key areas?
1. Ease of Use: ${feedbackData["ease_of_use"]}
2. Quality: ${feedbackData["quality"]}
3. Design: ${feedbackData["design"]}
4. Redundancy: ${feedbackData["redundancy"]}
3. Additional Feedback:
1. $additionalFeedback
Thank you,
The FCSC App Team
''';
try {
// Send the email
print("Message details:");
print("From: ${message.from}");
print("To: ${message.recipients}");
print("Subject: ${message.subject}");
print("Body: ${message.text}");
final sendReport = await send(message, smtpServer);
print('Message sent: ' + sendReport.toString());
} catch (e) {
print('Message not sent: $e');
// Handle the error as needed
}
}
// Future<void> fetchEmailConfiguration() async {
// try {
// // Fetch data from the email_configuration collection
// final response =
// await _pb.collection('email_configuration').getFullList();
//
// // Filter the data where the label is "Feedback"
// final feedbackConfig = response.firstWhere(
// (item) =>
// item.data['label'] == 'Feedback', // Accessing the 'data' property
// );
//
// // Check if a match is found
// if (feedbackConfig != null) {
// configEmail = feedbackConfig.data['email']; // Access the 'email' value
// } else {
// print('No feedback configuration found.');
// }
// } catch (e) {
// print('Error fetching email configuration: $e');
// }
// }
//
// Future<void> sendFeedbackEmail(
// Map<String, dynamic> feedbackData, createdTime) async {
// String username = 'emailapikey'; // Your SMTP username (API key)
// String password =
// 'PHtE6r0MFu66jTQp8BAFsP7sH5TwNd4v/+02KwBH5ItACvAES01Tot4okDawqhoiB/FEHfaey4Nvteyf5ePQJG28YW9OCWqyqK3sx/VYSPOZsbq6x00auVwYd0zUVY7pe9ds0yLTvNraNA=='; // Your SMTP password
//
// final smtpServer = SmtpServer('smtp.zeptomail.in',
// port: 587,
// username: username,
// password: password,
// ssl: false, // Use TLS
// ignoreBadCertificate:
// true); // Set to true if you're testing with a self-signed certificate
//
// // Check if additional feedback was provided
// String additionalFeedback = feedbackData["feedback"]?.isNotEmpty == true
// ? feedbackData["feedback"]
// : "No additional feedback provided.";
//
// // Format separately for date and time
// // Parse the string to DateTime
// // Parse the input as UTC and convert to DateTime in UTC timezone
// DateTime feedbackDateTime = DateTime.parse(createdTime).toUtc();
//
// // Format date as DD-MM-YYYY in UTC
// final String formattedDate =
// DateFormat('dd-MM-yyyy').format(feedbackDateTime);
//
// // Format to the desired output: "dd.MM.yyyy HH:mm 'UTC'"
// final DateFormat formatter = DateFormat("dd.MM.yyyy HH:mm 'UTC'");
// String formattedDateTime = formatter.format(feedbackDateTime);
//
// String email = configEmail; // Direct assignment
// // Create the email message
// final message = Message()
// ..from = Address('bbone@venbait.in', 'FCSC')
// ..recipients.add(email) // Set the recipient email
// ..subject = 'UAE Stats Feedback'
// ..text = '''
// Dear [App Owner/Admin],
//
// You have received new feedback from a user through the mobile application.
//
// User Details:
//
// 1. Name: Guest
// 2. Date of Submission: $formattedDate
// 3. Time of Submission: $formattedDateTime
//
// Feedback:
//
// 1. How was your experience with us today? Rating: ${feedbackData["emoji_rating"]}
// 2. How did we perform in key areas?
// 1. Ease of Use: ${feedbackData["ease_of_use"]}
// 2. Quality: ${feedbackData["quality"]}
// 3. Design: ${feedbackData["design"]}
// 4. Redundancy: ${feedbackData["redundancy"]}
// 3. Additional Feedback:
// 1. $additionalFeedback
//
// Thank you,
// The FCSC App Team
// ''';
//
// try {
// // Send the email
// print("Message details:");
// print("From: ${message.from}");
// print("To: ${message.recipients}");
// print("Subject: ${message.subject}");
// print("Body: ${message.text}");
// final sendReport = await send(message, smtpServer);
// print('Message sent: ' + sendReport.toString());
// } catch (e) {
// print('Message not sent: $e');
// // Handle the error as needed
// }
// }
@override
void dispose() {
@ -763,7 +780,7 @@ class LangToggle extends ConsumerWidget {
ref.read(preferencesUseCaseProvider.notifier).updatePreferences(
(prefs) => prefs.copyWith(language: languageAfter),
);
);
await onLoadFeedback(); // Reload feedback text after toggling language
},

View File

@ -1,50 +1,25 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart';
import 'package:uae_stat/presentation/components/my_drawer.dart';
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
class ManageUserRouter extends StatefulWidget {
late final Widget title;
late final Widget title;
@override
State<ManageUserRouter> createState() => _ManageUserRouterState();
}
class _ManageUserRouterState extends State<ManageUserRouter> {
// Sample data for the table
final List<User> userData = List.generate(
5,
(index) => User(
userName: 'Conan Keller ${index + 1}',
emailId: 'ConanKeller${index + 1}@gmail.com',
registrationDate: '18/07/2024',
status: 'Pending',
),
);
// Sorting state
final _pb = PocketBase('https://pb.venbait.in');
List<User> userData = [];
int _sortColumnIndex = 0;
bool _isAscending = true;
List<User> filteredUserData = [];
// Method to sort the data based on a column
void _sort<T>(Comparable<T> Function(User user) getField, int columnIndex,
bool ascending) {
setState(() {
_sortColumnIndex = columnIndex;
_isAscending = ascending;
userData.sort((a, b) {
final aValue = getField(a);
final bValue = getField(b);
return ascending
? Comparable.compare(aValue, bValue)
: Comparable.compare(bValue, aValue);
});
});
}
// Define dropdown items
final List<String> statusOptions = ['Approved', 'Denied', 'Pending'];
// Method to get status color
Color getStatusColor(String status) {
switch (status) {
case 'Approved':
@ -58,9 +33,80 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
}
}
@override
void initState() {
super.initState();
fetchUnverifiedUsers();
}
Future<void> fetchUnverifiedUsers() async {
try {
await _pb.admins
.authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com');
final result = await _pb.collection('users').getFullList(
filter: 'verified = false',
);
setState(() {
userData = result.map((record) {
final createdDate =
DateTime.parse(record.created); // Parse the created date
final formattedDate = DateFormat('dd/MM/yyyy').format(createdDate);
return User(
id: record.id, // Correctly passing the ID
userName: record.getStringValue('username'),
emailId: record.getStringValue('email'),
registrationDate: formattedDate,
status: record.getStringValue('status'),
);
}).toList();
filteredUserData = List.from(userData);
});
} catch (e) {
print('Error fetching unverified users: $e');
}
}
// Method to filter the list based on the search input
void filterUsers(String query) {
print('Filterd User: $query');
setState(() {
filteredUserData = userData.where((user) {
return user.userName.toLowerCase().contains(query.toLowerCase()) ||
user.emailId.toLowerCase().contains(query.toLowerCase()) ||
user.registrationDate.contains(query) ||
user.status.toLowerCase().contains(query.toLowerCase());
}).toList();
});
}
void _sort<T>(
Comparable<T>? Function(User user) getField,
int columnIndex,
bool ascending,
) {
setState(() {
_sortColumnIndex = columnIndex;
_isAscending = ascending;
filteredUserData.sort((a, b) {
final aValue = getField(a);
final bValue = getField(b);
if (aValue == null || bValue == null) {
return 0; // Handle null-safe sorting if needed
}
return ascending
? Comparable.compare(aValue, bValue)
: Comparable.compare(bValue, aValue);
});
});
}
//Method to show a confirmation dialog when status is changed
Future<void> _showConfirmationDialog(User user, String newStatus) async {
print('user $user');
double myheight = MediaQuery.of(context).size.height;
return showDialog<void>(
context: context,
barrierDismissible: false, // User must tap button to dismiss dialog
@ -69,7 +115,8 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0), // Rounded corners
),
contentPadding: EdgeInsets.zero, // Ensure no padding issues with close icon
contentPadding:
EdgeInsets.zero, // Ensure no padding issues with close icon
content: Stack(
children: [
Padding(
@ -77,7 +124,9 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: myheight/30,),
SizedBox(
height: myheight / 30,
),
Text(
'Are you sure you want to change \n the status to $newStatus?',
style: TextStyle(fontSize: 15),
@ -114,25 +163,45 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
),
side: BorderSide(
color: Colors.blue, // Set the outline color
width: 2.0, // Set the border width
width: 2.0, // Set the border width
),
),
child: Text('No'),
),
TextButton(
onPressed: () {
setState(() {
user.status = newStatus; // Change status
});
Navigator.of(context).pop();
onPressed: () async {
try {
// Update status in PocketBase
await _pb.collection('users').update(
user.id, // User's unique ID
body: {
'status': newStatus, // Update status
},
);
// Update local state
// setState(() {
// user.status = newStatus;
// });
fetchUnverifiedUsers();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Status updated successfully!')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error updating status: $e')),
);
}
Navigator.of(context).pop(); // Close dialog
},
style: TextButton.styleFrom(
backgroundColor: Colors.blue, // Set background color
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10.0), // Set text color
)),
backgroundColor: Colors.blue, // Set background color
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10.0), // Set text color
),
),
child: Text(
'Yes',
style: TextStyle(color: Colors.white),
@ -142,7 +211,6 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
)
],
);
},
);
}
@ -165,71 +233,97 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
onChanged: filterUsers,
),
SizedBox(height: myheight / 40),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
sortColumnIndex: _sortColumnIndex,
sortAscending: _isAscending,
columns: [
DataColumn(
label: const Text('User Name'),
onSort: (columnIndex, ascending) => _sort(
(user) => user.userName, columnIndex, ascending),
numeric: false,
),
DataColumn(
label: const Text('Email ID'),
onSort: (columnIndex, ascending) =>
_sort((user) => user.emailId, columnIndex, ascending),
numeric: false,
),
DataColumn(
label: const Text('Registration Date'),
onSort: (columnIndex, ascending) => _sort(
(user) => user.registrationDate,
columnIndex,
ascending),
numeric: false,
),
DataColumn(
label: const Text('Status'),
onSort: (columnIndex, ascending) =>
_sort((user) => user.status, columnIndex, ascending),
numeric: false,
),
],
rows: userData.map((user) {
return DataRow(
cells: [
DataCell(Text(user.userName)),
DataCell(Text(user.emailId)),
DataCell(Text(user.registrationDate)),
DataCell(
DropdownButton<String>(
value: user.status,
items: statusOptions.map((status) {
return DropdownMenuItem<String>(
value: status,
child: Text(
status,
style:
TextStyle(color: getStatusColor(status)),
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width,
),
child: DataTable(
sortColumnIndex: _sortColumnIndex,
sortAscending: _isAscending,
columns: [
DataColumn(
label: const Text('User Name'),
onSort: (columnIndex, ascending) => _sort(
(user) => user.userName, columnIndex, ascending),
),
DataColumn(
label: const Text('Email ID'),
onSort: (columnIndex, ascending) => _sort(
(user) => user.emailId, columnIndex, ascending),
),
DataColumn(
label: const Text('Registration Date'),
onSort: (columnIndex, ascending) {
_sort(
(user) => DateTime.tryParse(
user.registrationDate), // Convert to DateTime
columnIndex,
ascending,
);
},
),
DataColumn(
label: const Text('Status'),
onSort: (columnIndex, ascending) => _sort(
(user) => user.status, columnIndex, ascending),
),
],
rows: filteredUserData.isEmpty
? [
DataRow(
cells: List<DataCell>.generate(
4, // Ensure it matches the number of DataColumns
(index) => DataCell(
index == 0
? Text(
'No results found',
style: TextStyle(
fontStyle: FontStyle.italic),
)
: const Text(
''), // Empty cells for other columns
placeholder: true,
),
);
}).toList(),
onChanged: (String? newStatus) {
if (newStatus != null) {
_showConfirmationDialog(user,
newStatus); // Show dialog for confirmation
}
},
),
),
],
);
}).toList(),
),
),
]
: filteredUserData.map((user) {
return DataRow(
cells: [
DataCell(Text(user.userName)),
DataCell(Text(user.emailId)),
DataCell(Text(user.registrationDate)),
DataCell(
DropdownButton<String>(
value: user.status,
items: statusOptions.map((status) {
return DropdownMenuItem<String>(
value: status,
child: Text(
status,
style: TextStyle(
color: getStatusColor(status)),
),
);
}).toList(),
onChanged: (newStatus) {
print(user);
if (newStatus != null) {
_showConfirmationDialog(
user, newStatus);
}
},
),
),
],
);
}).toList(),
),
),
),
],
@ -242,12 +336,14 @@ class _ManageUserRouterState extends State<ManageUserRouter> {
// User data model
class User {
final String id;
final String userName;
final String emailId;
final String registrationDate;
String status;
User({
required this.id,
required this.userName,
required this.emailId,
required this.registrationDate,

View File

@ -23,9 +23,8 @@ class BaseScaffold extends StatelessWidget {
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: title,
actions: [IconButton(onPressed: (){}, icon: Icon(Icons.toggle_off_outlined)),
appBar: AppBar(title: title, actions: [
IconButton(onPressed: () {}, icon: Icon(Icons.toggle_off_outlined)),
]),
drawer: Drawer(
child: ListView(
@ -45,7 +44,7 @@ class BaseScaffold extends StatelessWidget {
),
Divider(),
InkWell(
onTap: () => context.go('/profile'),
onTap: () => context.go('/editProfile'),
child: Row(
children: [
SizedBox(
@ -69,7 +68,6 @@ class BaseScaffold extends StatelessWidget {
],
),
),
ListTile(
leading: SizedBox(
height: myheight / 15,
@ -79,7 +77,6 @@ class BaseScaffold extends StatelessWidget {
title: Text('Feedback'),
onTap: () => context.go('/feedback'),
),
ListTile(
leading: SizedBox(
height: myheight / 15,
@ -98,18 +95,35 @@ class BaseScaffold extends StatelessWidget {
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: SizedBox(height: myheight/15,width : mywidth/15,child: Image(image: AssetImage('assets/icons/bottom_bar/home.png'))),
icon: SizedBox(
height: myheight / 15,
width: mywidth / 15,
child: Image(
image: AssetImage('assets/icons/bottom_bar/home.png'))),
label: 'Home',
),
BottomNavigationBarItem(
icon: SizedBox(height: myheight/15,width : mywidth/15,child: Image(image: AssetImage('assets/icons/bottom_bar/uae_map.png'))),
icon: SizedBox(
height: myheight / 15,
width: mywidth / 15,
child: Image(
image: AssetImage('assets/icons/bottom_bar/uae_map.png'))),
label: 'UAE Numbers',
),
BottomNavigationBarItem(
icon: SizedBox(height: myheight/15,width : mywidth/15,child: Image(image: AssetImage('assets/icons/bottom_bar/ranking.png'))),
icon: SizedBox(
height: myheight / 15,
width: mywidth / 15,
child: Image(
image:
AssetImage('assets/icons/bottom_bar/ranking.png'))),
label: 'Competitiveness'),
BottomNavigationBarItem(
icon: SizedBox(height: myheight/15,width : mywidth/15,child: Image(image: AssetImage('assets/icons/bottom_bar/globe.png'))),
icon: SizedBox(
height: myheight / 15,
width: mywidth / 15,
child: Image(
image: AssetImage('assets/icons/bottom_bar/globe.png'))),
label: 'Country Profile'),
],
selectedItemColor: Colors.black,