diff --git a/lib/infrastructure/services/pocketbase_service.dart b/lib/infrastructure/services/pocketbase_service.dart index 02d70cb0..20fb6ace 100644 --- a/lib/infrastructure/services/pocketbase_service.dart +++ b/lib/infrastructure/services/pocketbase_service.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:pocketbase/pocketbase.dart'; abstract class PocketBaseService { - static const _host = 'https://pocket.fcsc.gov.ae'; + static const _host = 'https://pb.venbait.in'; // static const _host = 'http://127.0.0.1:8090'; static final _pb = PocketBase(_host); static final users = _pb.collection('users'); diff --git a/lib/main.dart b/lib/main.dart index 4b000a4b..df1ec5b7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,76 +22,75 @@ void main() async { ); } -// class MainApp extends StatelessWidget { -// const MainApp({super.key}); -// -// @override -// Widget build(BuildContext context) { -// return MaterialApp( -// -// home: RegisterScreen(), -// //home: ProfileScreen(), -// debugShowCheckedModeBanner: false, -// ); -// } -// } - - -class MainApp extends ConsumerWidget { +class MainApp extends StatelessWidget { const MainApp({super.key}); @override - Widget build( - BuildContext context, - WidgetRef ref, - ) { - const loading = Material( - child: Center( - child: CircularProgressIndicator(), - ), + Widget build(BuildContext context) { + return MaterialApp( + home: RegisterScreen(), + //home: ProfileScreen(), + debugShowCheckedModeBanner: false, ); - // waits to load preferences (language) - // from local storage - final languageFinalizedWidget = ref - .watch( - routerProvider, - ) - .when( - loading: () => loading, - error: Error.throwWithStackTrace, - data: (data) { - final materialApp = MaterialApp.router( - debugShowCheckedModeBanner: false, - routerConfig: data, - supportedLocales: LanguageLocale.values.map( - (e) => e.toLocale(), - ), - localizationsDelegates: GlobalMaterialLocalizations.delegates, - theme: ThemeData.from( - colorScheme: ColorScheme.fromSeed( - seedColor: MyTheme.topicColor( - IndicatorTopic.economy, - ), - ), - textTheme: OldTheme.textTheme, - ), - ); - return ColoredBox( - color: Colors.white, - child: materialApp, - ); - }, - ); - // waits to load auth from local storage - final authFinalizedWidget = ref - .watch( - authUseCaseProvider, - ) - .when( - loading: () => loading, - error: Error.throwWithStackTrace, - data: (data) => languageFinalizedWidget, - ); - return authFinalizedWidget; } } + + +// class MainApp extends ConsumerWidget { +// const MainApp({super.key}); +// +// @override +// Widget build( +// BuildContext context, +// WidgetRef ref, +// ) { +// const loading = Material( +// child: Center( +// child: CircularProgressIndicator(), +// ), +// ); +// // waits to load preferences (language) +// // from local storage +// final languageFinalizedWidget = ref +// .watch( +// routerProvider, +// ) +// .when( +// loading: () => loading, +// error: Error.throwWithStackTrace, +// data: (data) { +// final materialApp = MaterialApp.router( +// debugShowCheckedModeBanner: false, +// routerConfig: data, +// supportedLocales: LanguageLocale.values.map( +// (e) => e.toLocale(), +// ), +// localizationsDelegates: GlobalMaterialLocalizations.delegates, +// theme: ThemeData.from( +// colorScheme: ColorScheme.fromSeed( +// seedColor: MyTheme.topicColor( +// IndicatorTopic.economy, +// ), +// ), +// textTheme: OldTheme.textTheme, +// ), +// ); +// return ColoredBox( +// color: Colors.white, +// child: materialApp, +// ); +// }, +// ); +// // waits to load auth from local storage +// final authFinalizedWidget = ref +// .watch( +// authUseCaseProvider, +// ) +// .when( +// loading: () => loading, +// error: Error.throwWithStackTrace, +// data: (data) => languageFinalizedWidget, +// ); +// return authFinalizedWidget; +// } +// } diff --git a/lib/presentation/Screens/changepassword.dart b/lib/presentation/Screens/changepassword.dart index f4d4c18e..8c0155e0 100644 --- a/lib/presentation/Screens/changepassword.dart +++ b/lib/presentation/Screens/changepassword.dart @@ -1,5 +1,7 @@ - +import 'dart:convert'; +import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; import 'otp_verification.dart'; class Changepassword extends StatefulWidget { @@ -10,22 +12,118 @@ class Changepassword extends StatefulWidget { } class _ResetPasswordScreenState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); final _formKey = GlobalKey(); final _emailController = TextEditingController(); bool _isLoading = false; + String? _otpId; - void _handleSubmit() { + String? _verificationCode; + Future sendVerificationCode(String email) async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => EmailVerificationScreen()), - ); - // TODO: Implement your password reset logic here - // After API call, set _isLoading back to false + final email = _emailController.text.trim(); + + //print('Email - $email'); + + try { + // Authenticate admin to get the token + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final authToken = adminAuth.token; + + //print('Admin Token: $authToken'); + + // Check if email exists in the user collection + final userCheckResponse = await http.get( + Uri.parse( + 'https://pb.venbait.in/api/collections/users/records?filter=email="$email"'), + headers: { + 'Authorization': 'Bearer $authToken', + }, + ); + + if (userCheckResponse.statusCode == 200) { + final userData = jsonDecode(userCheckResponse.body); + + //print('userDetails ->: $userData'); + if (userData['items'].isNotEmpty) { + // Email exists; retrieve user ID + final userId = userData['items'][0]['id']; + + // Proceed with OTP request + final otpResponse = await http.post( + Uri.parse( + 'https://pb.venbait.in/api/collections/otp_requests/records'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $authToken', + }, + body: jsonEncode({ + 'email': email, + }), + ); + + if (otpResponse.statusCode == 200) { + //print('otpResponse- ${otpResponse.body} '); + + final otpData = jsonDecode(otpResponse.body); + final otp = otpData['otp']; // OTP value from the response + final otpId = otpData['id']; + _otpId = otpData['id']; + //print('OTP: $otp, ID: $otpId, '); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Verification code sent to $email")), + ); + + // Navigate to the Email Verification screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EmailVerificationScreen( + email: email, + userId: userId, // Pass user ID to the next screen + otp: otp, + otpId: otpId, + sendVerificationCode: sendVerificationCode, + ), + ), + ); + } else { + final error = + jsonDecode(otpResponse.body)['error'] ?? "Unknown error"; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $error")), + ); + } + } else { + // Email not found + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please Enter Registered Mail ID")), + ); + } + } else { + // Error in user collection request + final error = + jsonDecode(userCheckResponse.body)['error'] ?? "Unknown error"; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $error")), + ); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Failed to process request: $e")), + ); + } finally { + setState(() { + _isLoading = false; + }); + } } } @@ -134,7 +232,12 @@ class _ResetPasswordScreenState extends State { width: double.infinity, height: 48, child: ElevatedButton( - onPressed: _isLoading ? null : _handleSubmit, + onPressed: _isLoading + ? null + : () { + final email = _emailController.text.trim(); + sendVerificationCode(email); + }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8B7355), foregroundColor: Colors.white, @@ -163,9 +266,14 @@ class _ResetPasswordScreenState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(width: 2,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), - + SizedBox( + width: 2, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, + ), ], ], ), @@ -173,19 +281,21 @@ class _ResetPasswordScreenState extends State { ), ], ), - SizedBox(height: screenheight/3,), - + SizedBox( + height: screenheight / 3, + ), Center( child: Container( - height: screenheight/8, - width: screenwidth/2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), @@ -195,8 +305,3 @@ class _ResetPasswordScreenState extends State { ); } } - - - - - diff --git a/lib/presentation/Screens/confirm_password.dart b/lib/presentation/Screens/confirm_password.dart index 0edd45c6..6bb6b26d 100644 --- a/lib/presentation/Screens/confirm_password.dart +++ b/lib/presentation/Screens/confirm_password.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:uae_stat/presentation/Screens/profilepage.dart'; class ConfirmPassword extends StatefulWidget { - ConfirmPassword({super.key}); + final String email; + final String userId; + ConfirmPassword({required this.email, required this.userId}); @override State createState() => _ConfirmPasswordState(); } class _ConfirmPasswordState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); final _formKey = GlobalKey(); bool _obscurePassword = true; bool _obscureConfirmPassword = true; @@ -32,6 +38,68 @@ class _ConfirmPasswordState extends State { return null; } + // Function to check and update password + Future updatePassword(String userId, String newPassword) async { + try { + //print('userRecord- $userId'); + + // Authenticate as admin + + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final token = adminAuth.token; + + final headers = { + 'Authorization': 'Bearer $token', + }; + + final userRecord = await _pb.collection('users').getOne(userId); + //print('userRecord- $userRecord'); + final oldPassword = userRecord.data['password']; + print(oldPassword); + + // Check if the new password is the same as the old password + if (oldPassword == newPassword) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'New password is similar to the old password. Please try a different one.'), + backgroundColor: Colors.orange, + ), + ); + return; // Exit early + } + + // Update password + await _pb.collection('users').update( + userId, // User ID + body: {'password': newPassword}, // Updated password + ); + + // Show success Snackbar + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Password updated successfully'), + backgroundColor: Colors.blue, + ), + ); + + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ProfileScreen(userId: userId)), + ); + } catch (e) { + print('Error updating password: $e'); + // Show error Snackbar + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to update password'), + backgroundColor: Colors.red, + ), + ); + } + } + @override Widget build(BuildContext context) { double screenheight = MediaQuery.of(context).size.height; @@ -49,7 +117,9 @@ class _ConfirmPasswordState extends State { padding: const EdgeInsets.all(24.0), child: Column( children: [ - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Text( "Create New Password", style: TextStyle( @@ -57,7 +127,9 @@ class _ConfirmPasswordState extends State { fontWeight: FontWeight.bold, ), ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), Text( "Your new password must de different\nform previously used password", textAlign: TextAlign.center, @@ -66,7 +138,9 @@ class _ConfirmPasswordState extends State { color: Colors.grey[600], ), ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), TextFormField( obscureText: _obscurePassword, decoration: InputDecoration( @@ -92,7 +166,9 @@ class _ConfirmPasswordState extends State { ), validator: _validatePassword, ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), TextFormField( obscureText: _obscureConfirmPassword, decoration: InputDecoration( @@ -110,7 +186,8 @@ class _ConfirmPasswordState extends State { ), onPressed: () { setState(() { - _obscureConfirmPassword = !_obscureConfirmPassword; + _obscureConfirmPassword = + !_obscureConfirmPassword; }); }, ), @@ -118,20 +195,28 @@ class _ConfirmPasswordState extends State { ), validator: _validateConfirmPassword, ), - SizedBox(height: screenheight/35,), SizedBox( - width: screenwidth/1.1, + height: screenheight / 35, + ), + SizedBox( + width: screenwidth / 1.1, child: ElevatedButton( - onPressed: () { + onPressed: () async { if ((_formKey.currentState?.validate() ?? false)) { - // Proceed with registration if form is valid and checkbox is checked + //String userId = '4hai9cbn4lg6jt4'; // Replace with the actual user ID + String newPassword = _password ?? + ''; // Replace with the new password + //print("Confirm Passwd- $widget.userId, $newPassword"); + + await updatePassword(widget.userId, newPassword); } // Add verification logic here }, style: ElevatedButton.styleFrom( backgroundColor: Colors.brown[300], - padding: EdgeInsets.symmetric(horizontal: 80, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 80, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -141,10 +226,17 @@ class _ConfirmPasswordState extends State { children: [ Text( "Save", - style: TextStyle(fontSize: 18,color: Colors.white), + style: TextStyle( + fontSize: 18, color: Colors.white), + ), + SizedBox( + width: 2, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, ), - SizedBox(width: 2,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), ], ), ), @@ -152,18 +244,21 @@ class _ConfirmPasswordState extends State { ], ), ), - SizedBox(height: screenheight/15,), + SizedBox( + height: screenheight / 15, + ), Center( child: Container( - height: screenheight/10, - width: screenwidth/2.5, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 10, + width: screenwidth / 2.5, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), diff --git a/lib/presentation/Screens/otp_verification.dart b/lib/presentation/Screens/otp_verification.dart index e2fddc2b..f5c4c6c8 100644 --- a/lib/presentation/Screens/otp_verification.dart +++ b/lib/presentation/Screens/otp_verification.dart @@ -1,20 +1,35 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; import 'confirm_password.dart'; class EmailVerificationScreen extends StatefulWidget { + final String email; + final String userId, otp, otpId; + final Function(String) sendVerificationCode; + + EmailVerificationScreen( + {required this.email, + required this.userId, + required this.otp, + required this.otpId, + required this.sendVerificationCode}); + @override _EmailVerificationScreenState createState() => _EmailVerificationScreenState(); } class _EmailVerificationScreenState extends State { + // final pb = PocketBase('http://127.0.0.1:8090'); + final _pb = PocketBase('https://pb.venbait.in'); final List _otpControllers = - List.generate(4, (_) => TextEditingController()); + List.generate(4, (_) => TextEditingController()); int _secondsRemaining = 120; // 2 minutes timer late Timer _timer; + bool _canResend = false; @override void initState() { @@ -23,12 +38,16 @@ class _EmailVerificationScreenState extends State { } void _startTimer() { + _canResend = false; _timer = Timer.periodic(Duration(seconds: 1), (timer) { if (_secondsRemaining > 0) { setState(() { _secondsRemaining--; }); } else { + setState(() { + _canResend = true; // Enable "Resend Code" when the timer ends + }); timer.cancel(); } }); @@ -49,6 +68,42 @@ class _EmailVerificationScreenState extends State { super.dispose(); } + void verify(BuildContext context, String email, String enteredOTP, + String userId) async { + // Validate OTP length + if (enteredOTP.length != 4) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please enter a valid 4-digit code!")), + ); + return; + } + + try { + if (enteredOTP == widget.otp && email == widget.email) { + // Parse the response + + //print('VERIFIED OTF'); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ConfirmPassword( + email: widget.email, // Pass the email + userId: widget.userId)), + ); + } else { + // No matching record found + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Invalid OTP. Please try again.")), + ); + } + } catch (e) { + // Catch network or other unexpected errors + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Something went wrong. Please try again.")), + ); + } + } @override Widget build(BuildContext context) { @@ -63,7 +118,9 @@ class _EmailVerificationScreenState extends State { children: [ Column( children: [ - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Text( "Verify Your Email", style: TextStyle( @@ -90,11 +147,11 @@ class _EmailVerificationScreenState extends State { height: 50, alignment: Alignment.center, decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400, width: 1), + border: + Border.all(color: Colors.grey.shade400, width: 1), borderRadius: BorderRadius.circular(8), ), - child: - TextField( + child: TextField( controller: _otpControllers[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, @@ -103,11 +160,13 @@ class _EmailVerificationScreenState extends State { counterText: "", border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blue, width: 2), + borderSide: + BorderSide(color: Colors.blue, width: 2), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blue, width: 2), + borderSide: + BorderSide(color: Colors.blue, width: 2), ), ), onChanged: (value) { @@ -127,36 +186,49 @@ class _EmailVerificationScreenState extends State { children: [ TextSpan( text: _formattedTime, - style: TextStyle(fontWeight: FontWeight.w900,color: Colors.black87), + style: TextStyle( + fontWeight: FontWeight.w900, + color: Colors.black87), ), ], ), ), SizedBox(height: 5), TextButton( - onPressed: () { - setState(() { - _secondsRemaining = 120; - _startTimer(); - }); - }, - child: Text("Resend Code",style: TextStyle(color: Colors.brown,fontWeight: FontWeight.bold),), + onPressed: _canResend + ? () { + setState(() { + _secondsRemaining = 120; + _startTimer(); + }); + widget.sendVerificationCode(widget.email); + } + : null, + child: Text( + "Resend Code", + style: TextStyle( + color: Colors.brown, fontWeight: FontWeight.bold), + ), ), SizedBox(height: 5), SizedBox( - width: screenwidth/1.2, + width: screenwidth / 1.2, child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => ConfirmPassword()), - ); + onPressed: () async { + // Combine the entered OTP + String enteredOTP = _otpControllers + .map((controller) => controller.text) + .join(); + + verify( + context, widget.email, enteredOTP, widget.userId); // Add verification logic here }, style: ElevatedButton.styleFrom( backgroundColor: Colors.brown[300], - padding: EdgeInsets.symmetric(horizontal: 80, vertical: 16), + padding: + EdgeInsets.symmetric(horizontal: 80, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -166,32 +238,41 @@ class _EmailVerificationScreenState extends State { children: [ Text( "Verify", - style: TextStyle(fontSize: 18,color: Colors.white), + style: TextStyle(fontSize: 18, color: Colors.white), + ), + SizedBox( + width: 3, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, ), - SizedBox(width: 3,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), ], ), ), ), ], ), - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Center( child: Container( - height: screenheight/8, - width: screenwidth/2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index f39ca581..f0ab349f 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; @@ -11,6 +12,9 @@ import 'package:pocketbase/pocketbase.dart'; import 'changepassword.dart'; class ProfileScreen extends StatefulWidget { + final String userId; // Add this field to hold the user ID + + const ProfileScreen({Key? key, required this.userId}) : super(key: key); @override State createState() => _ProfileScreenState(); } @@ -42,10 +46,12 @@ class _ProfileScreenState extends State { // Regular expression to validate Full Name (no special characters) final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$"); + late Future userDetails; @override void initState() { super.initState(); + print(widget.userId); _fetchUserData(); // Call the function to fetch user data } @@ -56,32 +62,24 @@ class _ProfileScreenState extends State { super.dispose(); } - void _fetchUserData() async { + Future _fetchUserData() async { try { - final userId = 'tsgehyvgy6owypj'; final adminAuth = await _pb.admins .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); final adminToken = adminAuth.token; - final response = await http.get( - Uri.parse( - 'https://pb.venbait.in/api/collections/users/records/$userId'), + final userDetailsResponse = await _pb.collection('users').getOne( + widget.userId, headers: { - 'Authorization': 'Bearer $adminToken', // Add token to header - 'Content-Type': 'application/json', + 'Authorization': 'Bearer $adminToken', }, ); - - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - setState(() { - _usernameController.text = data['username']; - _emailController.text = data['email']; - }); - } else { - print('Failed to fetch user data: ${response.body}'); - } - } catch (error) { - print('Failed to fetch user data: $error'); + print('userDetails: $userDetailsResponse'); + setState(() { + _usernameController.text = userDetailsResponse.data['username'] ?? ''; + _emailController.text = userDetailsResponse.data['email'] ?? ''; + }); + } catch (e) { + print('Error fetching user details: $e'); } } @@ -160,7 +158,7 @@ class _ProfileScreenState extends State { if (result == true) { if (_formKey.currentState?.validate() ?? false) { try { - String userID = 'tsgehyvgy6owypj'; + String userID = widget.userId; // Retrieve data from text fields and other inputs String fullName = _fullNameController.text; // String username = 'users55538'; @@ -174,50 +172,34 @@ class _ProfileScreenState extends State { DateTime dob = DateFormat('dd/MM/yyyy').parse(dateOfBirth); String formattedDob = DateFormat('yyyy-MM-dd').format(dob); - // Prepare form data - final request = http.MultipartRequest( - 'PATCH', - Uri.parse( - 'https://pb.venbait.in/api/collections/users/records/$userID'), // replace with actual user ID - ); + // Create a multipart request + final uri = + Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); + final request = http.MultipartRequest('PATCH', uri); - // Set the fields for the user profile + // Add other fields request.fields['full_name'] = fullName; - // request.fields['username'] = username; - // request.fields['email'] = email; - request.fields['dob'] = formattedDob; // ensure correct format + request.fields['dob'] = formattedDob; request.fields['country_region'] = countryRegion; - request.fields['terms_accepted'] = termsAccepted.toString(); + request.fields['is_profile_completed'] = 'True'; - // Add image file if selected + // Add the file, if available if (_profileImage != null) { request.files.add(await http.MultipartFile.fromPath( - 'avatar', _profileImage!.path)); + 'avatar', + _profileImage!.path, + )); } - final adminAuth = await _pb.admins - .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); - final adminToken = adminAuth.token; - // Add PocketBase auth headers if needed (for authenticated requests) - request.headers['Authorization'] = 'Bearer ${adminToken}'; - print(request); + // Add headers (if required, e.g., authorization) + request.headers['Authorization'] = 'Bearer ${_pb.authStore.token}'; // Send the request final response = await request.send(); - - if (response.statusCode == 200) { - print(response.statusCode); - _resetFormFields(); - Navigator.pushNamed(context, 'home'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Profile updated successfully!"))); - } else { - // Log the response body for better debugging - final responseBody = await response.stream.bytesToString(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - "Failed to update profile: ${response.reasonPhrase}, Body: $responseBody"))); - } + print(response); + _resetFormFields(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Profile updated successfully!"))); } catch (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to update profile: $error"))); @@ -379,7 +361,10 @@ class _ProfileScreenState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), + counterText: '', ), + maxLength: 40, // Set the maximum length to 20 characters + maxLengthEnforcement: MaxLengthEnforcement.enforced, ), SizedBox(height: 10), Row( @@ -526,16 +511,15 @@ class _ProfileScreenState extends State { ), ], ), - SizedBox(height: 20), Center( child: GestureDetector( - onTap: (){ + onTap: () { Navigator.push( context, - MaterialPageRoute(builder: (context) => Changepassword()), + MaterialPageRoute( + builder: (context) => Changepassword()), ); - }, child: Text( "Change Password", @@ -548,14 +532,14 @@ class _ProfileScreenState extends State { SizedBox(height: 20), ElevatedButton.icon( onPressed: () { - - if((_formKey.currentState?.validate() ?? false) && (isChecked)){ + if ((_formKey.currentState?.validate() ?? false) && + (isChecked)) { _formKey.currentState?.save(); showConfirmationDialog(context); - } - else { + } else { setState(() { - showError = !isChecked; // Show error if the checkbox is not checked + showError = + !isChecked; // Show error if the checkbox is not checked }); } // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { @@ -571,10 +555,10 @@ class _ProfileScreenState extends State { // 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),)), - // ); - //} + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)), + // ); + //} //} }, icon: Icon( diff --git a/lib/presentation/Screens/registration.dart b/lib/presentation/Screens/registration.dart index fea5bcd8..d87b0562 100644 --- a/lib/presentation/Screens/registration.dart +++ b/lib/presentation/Screens/registration.dart @@ -1,6 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +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'; + class RegisterScreen extends StatefulWidget { @override _RegisterScreenState createState() => _RegisterScreenState(); @@ -8,12 +14,32 @@ class RegisterScreen extends StatefulWidget { class _RegisterScreenState extends State { final _formKey = GlobalKey(); + final _usernameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); bool _obscurePassword = true; bool _obscureConfirmPassword = true; bool isChecked = false; bool showError = false; - String? _password; + bool registrationSuccess = false; + bool registrationFailed = false; + dynamic userID; + final pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + _usernameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } String? _validateUsername(String? value) { if (value == null || value.isEmpty) { @@ -52,241 +78,433 @@ class _RegisterScreenState extends State { return null; } + Future _registerUser() async { + if (_formKey.currentState?.validate() ?? false) { + if (isChecked) { + try { + final adminAuth = await pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + // Create user in PocketBase + final response = await pb.collection('users').create(body: { + 'username': _usernameController.text, + 'email': _emailController.text, + 'password': _passwordController.text, + 'passwordConfirm': _passwordController + .text, // PocketBase requires password confirmation + }, headers: { + 'Authorization': adminToken + }); + if (response.id != null) { + userID = response.id; + // Request email verification + // PocketBaseService.users.requestVerification(_emailController.text); + // Show success message instead of navigating away + setState(() { + userID = response.id; + registrationSuccess = true; + registrationFailed = false; // Show success message on success + }); + + // Navigate to ProfileScreen after successful registration + // Navigator.pushReplacement( + // context, + // MaterialPageRoute( + // builder: (context) => ProfileScreen(userId: response.id)), + // ); + } else { + throw Exception('User registration failed: missing user ID'); + } + } catch (e) { + setState(() { + registrationFailed = true; + registrationSuccess = false; + }); + } + } else { + setState(() { + showError = true; // Show error if checkbox is not checked + }); + } + } else { + setState(() { + showError = !isChecked; // Show error if the checkbox is not checked + }); + } + } + @override Widget build(BuildContext context) { double screenheight = MediaQuery.of(context).size.height; double screenwidth = MediaQuery.of(context).size.width; + + Widget buildIconContainer(IconData icon, Color iconColor) { + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Color(0xFFF9F9F9), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 6, + offset: Offset(0, 2), + ), + ], + ), + child: Icon( + icon, + color: iconColor, + size: 30, + ), + ); + } + return Scaffold( backgroundColor: Colors.white, - body: Padding( - padding: const EdgeInsets.all(20.0), - child: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(height: screenheight / 6), - Text( - 'Register', - style: TextStyle(fontSize: 32, fontWeight: FontWeight.w400), - ), - SizedBox(height: 10), - Text('Please enter your details'), - SizedBox(height: 20), - TextFormField( - decoration: InputDecoration( - hintText: 'Username', - prefixIcon: Icon( - Icons.person, - color: Colors.blue, - ), - border: OutlineInputBorder(), + body: registrationSuccess + ? Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: screenheight / 8), + buildIconContainer(Icons.report, Color(0xFF7DAFBC)), + SizedBox(height: 20), + Text( + "Your registration is pending for Admin Approval.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF414042)), ), - validator: _validateUsername, - ), - SizedBox(height: 15), - TextFormField( - decoration: InputDecoration( - hintText: 'Enter your email', - prefixIcon: Icon( - Icons.email, - color: Colors.blue, + SizedBox(height: 10), + Text( + "Access will be granted once your account is approved.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Color(0xFF898C81), ), - border: OutlineInputBorder(), ), - validator: _validateEmail, - ), - SizedBox(height: 15), - TextFormField( - obscureText: _obscurePassword, - decoration: InputDecoration( - hintText: 'Enter your password', - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, - ), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility - : Icons.visibility_off, - color: Colors.blue, + SizedBox(height: 10), + ElevatedButton( + onPressed: () => { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + ProfileScreen(userId: userID)), ), - onPressed: () { - setState(() { - _obscurePassword = !_obscurePassword; - }); - }, + }, + child: Text( + 'Go to Login', ), - border: OutlineInputBorder(), ), - validator: _validatePassword, - ), - SizedBox(height: 15), - TextFormField( - obscureText: _obscureConfirmPassword, - decoration: InputDecoration( - hintText: 'Confirm password', - prefixIcon: Icon( - Icons.lock, - color: Colors.blue, - ), - suffixIcon: IconButton( - icon: Icon( - _obscureConfirmPassword - ? Icons.visibility - : Icons.visibility_off, - color: Colors.blue, - ), - onPressed: () { - setState(() { - _obscureConfirmPassword = !_obscureConfirmPassword; - }); - }, - ), - border: OutlineInputBorder(), - ), - validator: _validateConfirmPassword, - ), - SizedBox(height: 10), - Row( - children: [ - Checkbox( - value: isChecked, - onChanged: (value) { - setState(() { - isChecked = value ?? false; - showError = false; - }); - }, - side: BorderSide( - color: showError ? Colors.red : Colors.grey, - width: 1.5, + SizedBox(height: screenheight / 5), + Center( + child: Container( + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, ), ), - Expanded( - child: Text.rich( - TextSpan( - text: 'I agree to ', - children: [ - TextSpan( - text: 'Terms & Conditions', - style: TextStyle(color: Colors.blue), - ), - TextSpan(text: ' and '), - TextSpan( - text: 'Privacy Policy', - style: TextStyle(color: Colors.blue), - ), - ], + )) + ], + ), + ) + : registrationFailed + ? Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: screenheight / 8), + buildIconContainer(Icons.report, Colors.red), + SizedBox(height: 20), + Text( + "Sorry ${_usernameController.text}!", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF414042)), + ), + SizedBox(height: 10), + Text( + "Your registration process failed. For further assistance, please contact support.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Color(0xFF898C81), ), ), - ), - ], - ), - if (showError) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0), + SizedBox(height: 10), + ElevatedButton( + onPressed: () => context.go( + '/${context.language}/login', + ), child: Text( - 'Required', - style: TextStyle( - color: Colors.red[700], - fontSize: 12, + 'Retry', + ), + ), + SizedBox(height: screenheight / 5), + Center( + child: Container( + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, ), ), - ), + )) ], ), - SizedBox(height: 20), - SizedBox( - width: screenwidth/1.3, - child: ElevatedButton( - onPressed: () { - if ((_formKey.currentState?.validate() ?? false) && isChecked) { - // Proceed with registration if form is valid and checkbox is checked - Navigator.push( - context, - MaterialPageRoute(builder: (context) => ProfileScreen()), - ); - } else { - setState(() { - showError = !isChecked; // Show error if the checkbox is not checked - }); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFA7887A), // Brownish color for Register - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + ) + : Padding( + padding: const EdgeInsets.all(20.0), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: screenheight / 6), + Text( + 'Register', + style: TextStyle( + fontSize: 32, fontWeight: FontWeight.w400), + ), + SizedBox(height: 10), + Text('Please enter your details'), + SizedBox(height: 20), + // Display this if registration is pending approval + TextFormField( + controller: _usernameController, + decoration: InputDecoration( + hintText: 'Username', + prefixIcon: Icon( + Icons.person, + color: Colors.blue, + ), + border: OutlineInputBorder(), + counterText: '', + ), + validator: _validateUsername, + maxLength: + 40, // Set the maximum length to 20 characters + maxLengthEnforcement: MaxLengthEnforcement.enforced, + ), + SizedBox(height: 15), + TextFormField( + controller: _emailController, + decoration: InputDecoration( + hintText: 'Enter your email', + prefixIcon: Icon( + Icons.email, + color: Colors.blue, + ), + border: OutlineInputBorder(), + ), + validator: _validateEmail, + ), + SizedBox(height: 15), + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + decoration: InputDecoration( + hintText: 'Enter your password', + prefixIcon: Icon( + Icons.lock, + color: Colors.blue, + ), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword + ? Icons.visibility + : Icons.visibility_off, + color: Colors.blue, + ), + onPressed: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); + }, + ), + border: OutlineInputBorder(), + ), + validator: _validatePassword, + ), + SizedBox(height: 15), + TextFormField( + obscureText: _obscureConfirmPassword, + decoration: InputDecoration( + hintText: 'Confirm password', + prefixIcon: Icon( + Icons.lock, + color: Colors.blue, + ), + suffixIcon: IconButton( + icon: Icon( + _obscureConfirmPassword + ? Icons.visibility + : Icons.visibility_off, + color: Colors.blue, + ), + onPressed: () { + setState(() { + _obscureConfirmPassword = + !_obscureConfirmPassword; + }); + }, + ), + border: OutlineInputBorder(), + ), + validator: _validateConfirmPassword, + ), + SizedBox(height: 10), + 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: Text.rich( + TextSpan( + text: 'I agree to ', + children: [ + TextSpan( + text: 'Terms & Conditions', + style: TextStyle(color: Colors.blue), + ), + TextSpan(text: ' and '), + TextSpan( + text: 'Privacy Policy', + style: TextStyle(color: Colors.blue), + ), + ], + ), + ), + ), + ], + ), + if (showError) + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text( + 'Required', + style: TextStyle( + color: Colors.red[700], + fontSize: 12, + ), + ), + ), + ], + ), + SizedBox(height: 20), + SizedBox( + width: screenwidth / 1.3, + child: ElevatedButton( + onPressed: _registerUser, + style: ElevatedButton.styleFrom( + backgroundColor: Color( + 0xFFA7887A), // Brownish color for Register + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Register", + style: TextStyle( + fontSize: 16, color: Colors.white), + ), + SizedBox(width: 8), + Icon(Icons.arrow_forward, + color: Colors.white), + ], + ), + ), + ), + SizedBox(height: 10), + Text( + "Already have an account?", + style: TextStyle( + fontSize: 16, color: Colors.grey[600]), + ), + SizedBox(height: 10), + SizedBox( + width: screenwidth / 1.3, + child: ElevatedButton( + onPressed: () { + // Add your login logic here + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color( + 0xFF82AFCB), // Blueish color for Login + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Login", + style: TextStyle( + fontSize: 16, color: Colors.white), + ), + SizedBox(width: 8), + Icon(Icons.arrow_forward, + color: Colors.white), + ], + ), + ), + ), + SizedBox( + height: 10, + ), + Center( + child: Container( + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) + ], ), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Register", - style: TextStyle(fontSize: 16, color: Colors.white), - ), - SizedBox(width: 8), - Icon(Icons.arrow_forward, color: Colors.white), - ], - ), ), ), - SizedBox(height: 10), - Text( - "Already have an account?", - style: TextStyle(fontSize: 16, color: Colors.grey[600]), - ), - SizedBox(height: 10), - SizedBox( - width: screenwidth/1.3, - child: ElevatedButton( - onPressed: () { - // Add your login logic here - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF82AFCB), // Blueish color for Login - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Login", - style: TextStyle(fontSize: 16, color: Colors.white), - ), - SizedBox(width: 8), - Icon(Icons.arrow_forward, color: Colors.white), - ], - ), - ), - ), - SizedBox(height: 10,), - Center( - child: Container( - height: screenheight/8, - width: screenwidth/2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - ))], - ), - ), - ), - ), ); } } - - - diff --git a/lib/presentation/routes/drawer_routes/feedback_route.dart b/lib/presentation/routes/drawer_routes/feedback_route.dart index ac97ff5b..d23f43fa 100644 --- a/lib/presentation/routes/drawer_routes/feedback_route.dart +++ b/lib/presentation/routes/drawer_routes/feedback_route.dart @@ -25,10 +25,12 @@ class FeedbackForm extends StatefulWidget { _FeedbackFormState createState() => _FeedbackFormState(); } -class _FeedbackFormState extends State { +class _FeedbackFormState extends State + with WidgetsBindingObserver { final _pb = PocketBase('https://pb.venbait.in'); // Initialize PocketBase client - // final _pb = PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client + // final _pb = + // PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client final TextEditingController _feedbackController = TextEditingController(); @@ -78,17 +80,34 @@ class _FeedbackFormState extends State { @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); _loadFeedbackText(); _feedbackController.addListener(_handleTextChange); fetchEmailConfiguration(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.detached || + state == AppLifecycleState.paused) { + _removeFeedbackText(); + } + } + + // Detect when navigating to another page + @override + void didPushNext() { + // Called when a new page is pushed on top of FeedbackPage + _removeFeedbackText(); + } + Future _saveFeedbackText( int emojiIndex, String ratingKey, double ratingValue) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('feedbackText', _feedbackController.text); await prefs.setInt('selected_emoji_index', emojiIndex); await prefs.setDouble(ratingKey, ratingValue); + print('one save'); } Future _saveAllRatings() async { @@ -101,24 +120,36 @@ class _FeedbackFormState extends State { Future _loadFeedbackText() async { final prefs = await SharedPreferences.getInstance(); + + // Step 1: Load values from SharedPreferences into variables final feedbackText = prefs.getString('feedbackText') ?? ''; + final savedIndex = prefs.getInt('selected_emoji_index'); + final easeOfUseRating = prefs.getDouble('ease_of_use_rating') ?? 0; + final qualityRating = prefs.getDouble('quality_rating') ?? 0; + final designRating = prefs.getDouble('design_rating') ?? 0; + final redundancyRating = prefs.getDouble('redundancy_rating') ?? 0; + + // Step 2: Populate fields with values without immediately clearing storage setState(() { _feedbackController.text = feedbackText; - }); - - final savedIndex = prefs.getInt('selected_emoji_index'); - if (savedIndex != null) { - setState(() { + if (savedIndex != null) { _selectedEmojiIndex = savedIndex; _isSmileySelected = true; // Indicating the user selected an emoji - }); - } + } + _easeOfUseRating = easeOfUseRating; + _qualityRating = qualityRating; + _designRating = designRating; + _redundancyRating = redundancyRating; + }); - setState(() { - _easeOfUseRating = prefs.getDouble('ease_of_use_rating') ?? 0; - _qualityRating = prefs.getDouble('quality_rating') ?? 0; - _designRating = prefs.getDouble('design_rating') ?? 0; - _redundancyRating = prefs.getDouble('redundancy_rating') ?? 0; + // Step 3: Clear storage after a slight delay + Future.delayed(Duration(milliseconds: 50), () async { + await prefs.remove('feedbackText'); + await prefs.remove('selected_emoji_index'); + await prefs.remove('ease_of_use_rating'); + await prefs.remove('quality_rating'); + await prefs.remove('design_rating'); + await prefs.remove('redundancy_rating'); }); } @@ -622,10 +653,6 @@ class _FeedbackFormState extends State { final DateFormat formatter = DateFormat("dd.MM.yyyy HH:mm 'UTC'"); String formattedDateTime = formatter.format(feedbackDateTime); - // Assuming the server timezone as UTC, you can append as needed - String submissionTime = "$formattedDateTime "; - - print("Formatted Date (UTC): $formattedDate"); String email = configEmail; // Direct assignment // Create the email message final message = Message() @@ -641,7 +668,7 @@ class _FeedbackFormState extends State { 1. Name: Guest 2. Date of Submission: $formattedDate - 3. Time of Submission: $submissionTime + 3. Time of Submission: $formattedDateTime Feedback: @@ -676,6 +703,7 @@ class _FeedbackFormState extends State { @override void dispose() { _feedbackController.dispose(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } }