· 5 months ago · May 07, 2025, 11:35 AM
1import 'dart:io';
2
3import 'package:firebase_auth/firebase_auth.dart' as fAuth;
4import 'package:flutter/gestures.dart';
5import 'package:flutter/material.dart';
6import 'package:flutter/services.dart';
7import 'package:flutter_svg/svg.dart';
8import 'package:google_fonts/google_fonts.dart';
9import 'package:investorv2/generated/assets.dart';
10import 'package:investorv2/singleton/shared_pref.dart';
11import 'package:investorv2/utility/apple_signin.dart';
12import 'package:investorv2/utility/baseFunctions.dart';
13import 'package:investorv2/utility/colors.dart';
14import 'package:investorv2/utility/customStrings.dart';
15import 'package:investorv2/utility/google_signin.dart';
16import 'package:investorv2/utility/routes.dart';
17import 'package:investorv2/utility/valueStrings.dart';
18import 'package:investorv2/view/common/progressBar.dart';
19import 'package:local_auth/local_auth.dart';
20import 'package:provider/provider.dart';
21
22import '../../provider/auth_provider.dart';
23import '../../singleton/logger.dart';
24
25class LoginPage extends StatefulWidget {
26 const LoginPage({super.key});
27
28 @override
29 LoginPageState createState() => LoginPageState();
30}
31
32class LoginPageState extends State<LoginPage> {
33 final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
34 final emailController = TextEditingController();
35 final passwordController = TextEditingController();
36 bool passwordVisible = true;
37 late AuthProvider provider;
38 bool isAppleSignInAvailable = false;
39
40 final LocalAuthentication auth = LocalAuthentication();
41 _SupportState _supportState = _SupportState.unknown;
42 bool _canCheckBiometrics = false;
43 List<BiometricType>? _availableBiometrics;
44 String _authorized = 'Not Authorized';
45 bool _isAuthenticating = false;
46
47 @override
48 void initState() {
49 provider = Provider.of<AuthProvider>(context, listen: false);
50 if (Platform.isIOS) {
51 appleSigninAvailability();
52 }
53 super.initState();
54 auth.isDeviceSupported().then(
55 (bool isSupported) => setState(() {
56 _supportState =
57 isSupported ? _SupportState.supported : _SupportState.unsupported;
58 }),
59 );
60 }
61
62 @override
63 Widget build(BuildContext context) {
64 return SafeArea(
65 child: Center(
66 child: SingleChildScrollView(
67 physics: ScrollPhysics(),
68 child: Padding(
69 padding: const EdgeInsets.all(20.0),
70 child: Column(
71 children: <Widget>[
72 SizedBox(
73 child: SvgPicture.asset(
74 "assets/images/ic_ifarmer.svg",
75 height: 40,
76 width: 100,
77 ),
78 ),
79 SizedBox(height: 30),
80 Form(
81 key: _formKey,
82 child: Column(
83 children: <Widget>[
84 Align(
85 alignment: AlignmentDirectional.centerStart,
86 child: Text(
87 CustomStrings.email,
88 style: GoogleFonts.poppins(
89 fontSize: 14,
90 fontWeight: FontWeight.w400,
91 color: ProjectColors.black4,
92 ),
93 maxLines: 1,
94 overflow: TextOverflow.ellipsis,
95 softWrap: true,
96 textAlign: TextAlign.start,
97 ),
98 ),
99 SizedBox(height: 10),
100 TextFormField(
101 style: GoogleFonts.poppins(
102 color: ProjectColors.black4,
103 fontSize: 14,
104 fontWeight: FontWeight.w500,
105 ),
106 maxLines: 1,
107 controller: emailController,
108 validator: (value) {
109 if (value == null || value.isEmpty) {
110 return CustomStrings.required;
111 } else if (!RegExp(
112 r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
113 ).hasMatch(value)) {
114 return CustomStrings.invalidEmail;
115 }
116 return null; // Valid input
117 },
118 keyboardType: TextInputType.emailAddress,
119 decoration: InputDecoration(
120 fillColor: ProjectColors.gray,
121 filled: true,
122 hintStyle: GoogleFonts.poppins(
123 color: ProjectColors.black4.withValues(alpha: 90),
124 fontSize: 14,
125 fontWeight: FontWeight.w500,
126 ),
127 errorBorder: OutlineInputBorder(
128 borderRadius: BorderRadius.circular(10.0),
129 borderSide: BorderSide(
130 color: Colors.red.shade800,
131 width: 0.5,
132 ),
133 ),
134 focusedBorder: OutlineInputBorder(
135 borderRadius: BorderRadius.circular(10.0),
136 borderSide: BorderSide(
137 color: ProjectColors.primaryColor,
138 ),
139 ),
140 focusedErrorBorder: OutlineInputBorder(
141 borderRadius: BorderRadius.circular(10.0),
142 borderSide: BorderSide(
143 color: Colors.red.shade800,
144 width: 0.5,
145 ),
146 ),
147 contentPadding: EdgeInsets.fromLTRB(
148 20.0,
149 15.0,
150 20.0,
151 15.0,
152 ),
153 hintText: CustomStrings.emailHint,
154 border: OutlineInputBorder(
155 borderSide: BorderSide.none,
156 borderRadius: BorderRadius.circular(10.0),
157 ),
158 ),
159 ),
160 SizedBox(height: 20.0),
161 Align(
162 alignment: AlignmentDirectional.centerStart,
163 child: Text(
164 CustomStrings.pin,
165 style: GoogleFonts.poppins(
166 fontSize: 14,
167 fontWeight: FontWeight.w400,
168 color: ProjectColors.black4,
169 ),
170 maxLines: 1,
171 overflow: TextOverflow.ellipsis,
172 softWrap: true,
173 textAlign: TextAlign.start,
174 ),
175 ),
176 SizedBox(height: 10),
177 TextFormField(
178 style: GoogleFonts.poppins(
179 color: ProjectColors.black4,
180 fontSize: 14,
181 fontWeight: FontWeight.w500,
182 ),
183 maxLines: 1,
184 controller: passwordController,
185 inputFormatters: [
186 FilteringTextInputFormatter.digitsOnly,
187 ],
188 validator: (value) {
189 if (value!.trim().isEmpty) {
190 return CustomStrings.required;
191 }
192 if (value.trim().length < 6) {
193 return CustomStrings.min6;
194 }
195 return null;
196 },
197 obscureText: passwordVisible,
198 keyboardType: TextInputType.number,
199 decoration: InputDecoration(
200 fillColor: ProjectColors.gray,
201 filled: true,
202 hintStyle: GoogleFonts.poppins(
203 color: ProjectColors.black4.withValues(alpha: 90),
204 fontSize: 14,
205 fontWeight: FontWeight.w500,
206 ),
207 errorBorder: OutlineInputBorder(
208 borderRadius: BorderRadius.circular(10.0),
209 borderSide: BorderSide(
210 color: Colors.red.shade800,
211 width: 0.5,
212 ),
213 ),
214 focusedBorder: OutlineInputBorder(
215 borderRadius: BorderRadius.circular(10.0),
216 borderSide: BorderSide(
217 color: ProjectColors.primaryColor,
218 ),
219 ),
220 focusedErrorBorder: OutlineInputBorder(
221 borderRadius: BorderRadius.circular(10.0),
222 borderSide: BorderSide(
223 color: Colors.red.shade800,
224 width: 0.5,
225 ),
226 ),
227 contentPadding: EdgeInsets.fromLTRB(
228 20.0,
229 15.0,
230 20.0,
231 15.0,
232 ),
233 hintText: CustomStrings.pinHint,
234 border: OutlineInputBorder(
235 borderSide: BorderSide.none,
236 borderRadius: BorderRadius.circular(10.0),
237 ),
238 suffixIcon: IconButton(
239 icon: Icon(
240 passwordVisible
241 ? Icons.visibility
242 : Icons.visibility_off,
243 color: ProjectColors.black4,
244 ),
245 onPressed: () {
246 setState(() {
247 passwordVisible = !passwordVisible;
248 });
249 },
250 ),
251 ),
252 ),
253 ],
254 ),
255 ),
256 Align(
257 alignment: AlignmentDirectional.centerStart,
258 child: TextButton(
259 onPressed: () {
260 Navigator.pushNamed(context, forgetPasswordPage);
261 },
262 child: Text(
263 CustomStrings.forgotPassword,
264 style: GoogleFonts.poppins(
265 fontSize: 12,
266 fontWeight: FontWeight.w400,
267 color: ProjectColors.primaryColor,
268 ),
269 maxLines: 1,
270 overflow: TextOverflow.ellipsis,
271 softWrap: true,
272 textAlign: TextAlign.start,
273 ),
274 ),
275 ),
276 Row(
277 mainAxisAlignment: MainAxisAlignment.start,
278 crossAxisAlignment: CrossAxisAlignment.center,
279 children: [
280 Expanded(
281 child: ElevatedButton(
282 style: ButtonStyle(
283 backgroundColor: WidgetStateProperty.all(
284 ProjectColors.primaryColor,
285 ),
286 padding: WidgetStateProperty.all(
287 EdgeInsets.fromLTRB(8, 16, 8, 16),
288 ),
289 shape: WidgetStatePropertyAll(
290 RoundedRectangleBorder(
291 borderRadius: BorderRadius.circular(10),
292 ),
293 ),
294 ),
295 onPressed: () {
296 if (_formKey.currentState!.validate()) {
297 provider
298 .loginCall(
299 email: emailController.text.trim(),
300 password: passwordController.text,
301 )
302 .then((statusCode) {
303 if (statusCode == 200) {
304 SharedPref.setModel(
305 loginModel,
306 context
307 .read<AuthProvider>()
308 .logInResponse,
309 );
310 SharedPref.setString(
311 token,
312 context
313 .read<AuthProvider>()
314 .logInResponse
315 .authToken!,
316 );
317 if (!context
318 .read<AuthProvider>()
319 .logInResponse
320 .privacyFlag!) {
321 Navigator.pushReplacementNamed(
322 context,
323 privacyPolicyPage,
324 arguments: false,
325 );
326 } else {
327 Navigator.pushReplacementNamed(
328 context,
329 loggedHomePage,
330 );
331 }
332 }
333 });
334 }
335 },
336 child: Row(
337 mainAxisAlignment: MainAxisAlignment.center,
338 spacing: 6,
339 children: [
340 SvgPicture.asset(
341 "assets/images/ic_login.svg",
342 height: 24,
343 width: 24,
344 colorFilter: ColorFilter.mode(
345 ProjectColors.white,
346 BlendMode.srcIn,
347 ),
348 ),
349 Text(
350 CustomStrings.login,
351 style: GoogleFonts.poppins(
352 fontWeight: FontWeight.w500,
353 fontSize: 16,
354 color: ProjectColors.white,
355 ),
356 ),
357 ],
358 ),
359 ),
360 ),
361 Visibility(
362 visible: _supportState == _SupportState.supported,
363 child: IconButton(
364 onPressed: () {
365 _checkBiometrics().then((value) {
366 if (_canCheckBiometrics) {
367 _getAvailableBiometrics().then((value) {
368 _authenticateWithBiometrics().then((value) {
369 Log().showMessageToast(message: _authorized);
370 });
371 });
372 }
373 });
374 },
375 icon: Image(
376 image: AssetImage(Assets.imagesIcBiometric),
377 width: 60,
378 height: 60,
379 ),
380 ),
381 ),
382 ],
383 ),
384 SizedBox(height: 10),
385 SizedBox(
386 height: 1,
387 child: Container(
388 color: ProjectColors.black6,
389 padding: EdgeInsets.only(top: 10, bottom: 10),
390 ),
391 ),
392 SizedBox(height: 10),
393 Align(
394 alignment: AlignmentDirectional.centerStart,
395 child: RichText(
396 textAlign: TextAlign.start,
397 text: TextSpan(
398 style: GoogleFonts.poppins(
399 fontSize: 12.0,
400 color: ProjectColors.black4,
401 fontWeight: FontWeight.w400,
402 ),
403 children: <TextSpan>[
404 TextSpan(
405 text: CustomStrings.doNotAccount,
406 style: GoogleFonts.poppins(
407 fontSize: 12.0,
408 color: ProjectColors.black4,
409 fontWeight: FontWeight.w400,
410 ),
411 ),
412 TextSpan(
413 text: CustomStrings.signUp,
414 recognizer:
415 TapGestureRecognizer()
416 ..onTap = () {
417 Navigator.pushNamed(
418 context,
419 registrationPage,
420 );
421 },
422 style: GoogleFonts.poppins(
423 fontSize: 12.0,
424 color: ProjectColors.primaryColor,
425 fontWeight: FontWeight.w400,
426 ),
427 ),
428 ],
429 ),
430 ),
431 ),
432 Container(
433 margin: EdgeInsets.only(top: 20.0),
434 child: Column(
435 mainAxisAlignment: MainAxisAlignment.spaceBetween,
436 mainAxisSize: MainAxisSize.max,
437 children: <Widget>[
438 Card(
439 margin: EdgeInsets.all(0.0),
440 elevation: 2,
441 shape: RoundedRectangleBorder(
442 side: BorderSide(
443 color: ProjectColors.primaryColor,
444 width: 0.5,
445 ),
446 borderRadius: BorderRadius.circular(4.0),
447 ),
448 child: GestureDetector(
449 onTap: () async {
450 goggleSignIn();
451 },
452 child: Container(
453 color: ProjectColors.white,
454 padding: EdgeInsets.symmetric(vertical: 10.0),
455 child: Row(
456 mainAxisAlignment: MainAxisAlignment.center,
457 crossAxisAlignment: CrossAxisAlignment.center,
458 children: <Widget>[
459 SvgPicture.asset(
460 "assets/images/google.svg",
461 height: 25.0,
462 width: 18.0,
463 ),
464 SizedBox(width: 10),
465 Text(
466 CustomStrings.google,
467 style: TextStyle(
468 fontSize: 14,
469 fontWeight: FontWeight.w500,
470 ),
471 ),
472 ],
473 ),
474 ),
475 ),
476 ),
477 SizedBox(height: 15),
478 Visibility(
479 visible: isAppleSignInAvailable,
480 child: Card(
481 margin: EdgeInsets.all(0.0),
482 elevation: 2,
483 color: Colors.black,
484 shape: RoundedRectangleBorder(
485 borderRadius: BorderRadius.circular(4.0),
486 ),
487 child: GestureDetector(
488 onTap: () {
489 appleSignIn();
490 },
491 child: Container(
492 padding: EdgeInsets.symmetric(
493 vertical: 10.0,
494 horizontal: 10.0,
495 ),
496 child: Row(
497 mainAxisAlignment: MainAxisAlignment.center,
498 crossAxisAlignment: CrossAxisAlignment.center,
499 children: <Widget>[
500 SvgPicture.asset(
501 "assets/images/apple_logo.svg",
502 height: 25.0,
503 width: 18.0,
504 ),
505 SizedBox(width: 10),
506 Text(
507 CustomStrings.apple,
508 style: TextStyle(
509 fontSize: 14,
510 color: Colors.white,
511 fontWeight: FontWeight.w500,
512 ),
513 ),
514 ],
515 ),
516 ),
517 ),
518 ),
519 ),
520 ],
521 ),
522 ),
523 SizedBox(height: 10),
524 ],
525 ),
526 ),
527 ),
528 ),
529 );
530 }
531
532 appleSigninAvailability() async {
533 var value = await AppleAuthService.isAppleSignInAvailable();
534 setState(() {
535 isAppleSignInAvailable = value;
536 });
537 }
538
539 appleSignIn() async {
540 fAuth.UserCredential? userCredential;
541 userCredential = await AppleAuthService().signInWithApple();
542 String name = SharedPref.getString("appleDisplayName");
543 Log().printInfo("Saved Name:$name");
544 if (userCredential != null) {
545 CustomProgressDialog.show(message: "Signing In...", isDismissible: false);
546 fAuth.User? user = userCredential.user;
547 Map<String, dynamic> map = {};
548 if (user != null) {
549 map["uid"] = user.uid;
550 map['provider'] = "apple";
551 if (user.email != null) {
552 map['email'] = user.email;
553 BaseFunctions().setEmail(user.email!);
554 }
555 map['name'] = name;
556 if (user.photoURL != null) {
557 map['image'] = user.photoURL ?? "";
558 }
559
560 provider.socialLogin(map: map).then((statusCode) {
561 Log().printInfo(statusCode.toString());
562 if (statusCode == 200) {
563 if (statusCode == 200) {
564 SharedPref.setModel(
565 loginModel,
566 context.read<AuthProvider>().logInResponse,
567 );
568 SharedPref.setString(
569 token,
570 context.read<AuthProvider>().logInResponse.authToken!,
571 );
572 if (!context.read<AuthProvider>().logInResponse.privacyFlag!) {
573 Navigator.pushReplacementNamed(
574 context,
575 privacyPolicyPage,
576 arguments: false,
577 );
578 } else {
579 Navigator.pushReplacementNamed(context, loggedHomePage);
580 }
581 }
582 }
583 });
584 } else {
585 CustomProgressDialog.hide();
586 }
587 } else {
588 CustomProgressDialog.hide();
589 }
590 }
591
592 goggleSignIn() async {
593 fAuth.UserCredential? userCredential;
594
595 userCredential = await GoogleAuthService().signInWithGoogle();
596
597 // Access user details
598 if (userCredential != null) {
599 CustomProgressDialog.show(message: "Signing In...", isDismissible: false);
600 fAuth.User? user = userCredential.user;
601 if (user != null) {
602 Log().printInfo("User UID: ${user.uid}");
603 Log().printInfo("User Display Name: ${user.displayName}");
604 Log().printInfo("User Email: ${user.email}");
605 Log().printInfo("User Photo URL: ${user.photoURL}");
606
607 Map<String, dynamic> map = {};
608 map['uid'] = user.uid;
609 map['provider'] = "google";
610 if (user.email != null) {
611 map['email'] = user.email ?? "";
612 BaseFunctions().setEmail(user.email ?? "");
613 }
614 if (user.displayName != null) {
615 map['name'] = user.displayName ?? "";
616 BaseFunctions().setEmail(user.displayName ?? "");
617 }
618
619 map['image'] = user.photoURL ?? "";
620
621 provider.socialLogin(map: map).then((statusCode) {
622 Log().printInfo(statusCode.toString());
623 if (statusCode == 200) {
624 if (statusCode == 200) {
625 SharedPref.setModel(
626 loginModel,
627 context.read<AuthProvider>().logInResponse,
628 );
629 SharedPref.setString(
630 token,
631 context.read<AuthProvider>().logInResponse.authToken!,
632 );
633 if (!context.read<AuthProvider>().logInResponse.privacyFlag!) {
634 Navigator.pushReplacementNamed(
635 context,
636 privacyPolicyPage,
637 arguments: false,
638 );
639 } else {
640 Navigator.pushReplacementNamed(context, loggedHomePage);
641 }
642 }
643 }
644 });
645 } else {
646 CustomProgressDialog.hide();
647 }
648 } else {
649 CustomProgressDialog.hide();
650 }
651 }
652
653 Future<void> _checkBiometrics() async {
654 late bool canCheckBiometrics;
655 try {
656 canCheckBiometrics = await auth.canCheckBiometrics;
657 } on PlatformException catch (e) {
658 canCheckBiometrics = false;
659 print(e);
660 }
661 if (!mounted) {
662 return;
663 }
664 setState(() {
665 _canCheckBiometrics = canCheckBiometrics;
666 });
667 }
668
669 Future<void> _getAvailableBiometrics() async {
670 late List<BiometricType> availableBiometrics;
671 try {
672 availableBiometrics = await auth.getAvailableBiometrics();
673 } on PlatformException catch (e) {
674 availableBiometrics = <BiometricType>[];
675 print(e);
676 }
677 if (!mounted) {
678 return;
679 }
680 setState(() {
681 _availableBiometrics = availableBiometrics;
682 });
683 }
684
685 Future<void> _authenticate() async {
686 bool authenticated = false;
687 try {
688 setState(() {
689 _isAuthenticating = true;
690 _authorized = 'Authenticating';
691 });
692 authenticated = await auth.authenticate(
693 localizedReason: 'Let OS determine authentication method',
694 options: const AuthenticationOptions(stickyAuth: true),
695 );
696 setState(() {
697 _isAuthenticating = false;
698 });
699 } on PlatformException catch (e) {
700 setState(() {
701 _isAuthenticating = false;
702 _authorized = 'Error - ${e.message}';
703 });
704 return;
705 }
706 if (!mounted) {
707 return;
708 }
709 setState(
710 () => _authorized = authenticated ? 'Authorized' : 'Not Authorized',
711 );
712 }
713
714 Future<void> _authenticateWithBiometrics() async {
715 bool authenticated = false;
716 try {
717 setState(() {
718 _isAuthenticating = true;
719 _authorized = 'Authenticating';
720 });
721 authenticated = await auth.authenticate(
722 localizedReason:
723 'Scan your fingerprint (or face or whatever) to authenticate',
724 options: const AuthenticationOptions(
725 stickyAuth: true,
726 biometricOnly: true,
727 ),
728 );
729 setState(() {
730 _isAuthenticating = false;
731 _authorized = 'Authenticating';
732 });
733 } on PlatformException catch (e) {
734 setState(() {
735 _isAuthenticating = false;
736 _authorized = 'Error - ${e.message}';
737 });
738 return;
739 }
740 if (!mounted) {
741 return;
742 }
743 final String message = authenticated ? 'Authorized' : 'Not Authorized';
744 setState(() {
745 _authorized = message;
746 });
747 }
748
749 Future<void> _cancelAuthentication() async {
750 await auth.stopAuthentication();
751 setState(() => _isAuthenticating = false);
752 }
753}
754
755enum _SupportState { unknown, supported, unsupported }
756