all functonality added in new router flow
This commit is contained in:
parent
617379deef
commit
c75ae680b0
6
.run/main.dart.run.xml
Normal file
6
.run/main.dart.run.xml
Normal 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>
|
||||
@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user