import 'dart:convert'; import 'dart:io'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhance_app_pwa/pages/service/TokenService.dart'; import 'package:pinput/pinput.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/environment.dart'; import '../../customAppBar/responsive.dart'; import '../../customAppBar/toastHelper.dart'; import '../postEnrollment/service/svg_service.dart'; import '../service/SessionManager.dart'; import 'authenticationService.dart'; class pinSettingPage extends StatefulWidget { const pinSettingPage({Key? key}) : super(key: key); @override State createState() => _pinSettingPageState(); } class _pinSettingPageState extends State { bool _enableBiometric = false; final _pinController = TextEditingController(); final _confirmPinController = TextEditingController(); final AuthService _authService = AuthService(); late final _formKey = GlobalKey(); late final FocusNode focusNode; dynamic empMobileNo; dynamic empEmailid; dynamic enrollToken; dynamic _postToken; dynamic emp_status; final session = SessionManager(); bool _isSavingPin = false; bool _isPinSaved = false; // hard success lock @override void initState() { super.initState(); focusNode = FocusNode(); } @override void dispose() { focusNode.dispose(); super.dispose(); } Future setLoginPin(skipOrNot) async { if (_isSavingPin || _isPinSaved) return; setState(() { _isSavingPin = true; }); try { print('setLoginPin'); // ✅ Only validate if not skipping if (skipOrNot != 1 && !_formKey.currentState!.validate()) { return; } // if (_formKey.currentState!.validate()) { final SharedPreferences prefs = await SharedPreferences.getInstance(); empMobileNo = prefs.getString('empMobileNo'); empEmailid = prefs.getString('empEmailid'); print('setLoginPin112233 $skipOrNot'); prefs.setString('is_mpin_skipped', skipOrNot.toString()); print('setLoginPin112233'); // emp_status = session.emp_status; enrollToken = await TokenService.getPreToken(); _postToken = await TokenService.getPostToken(); String pin = _pinController.text; await _authService.savePin(pin); // await _authService.saveSkipStatus(0); if(skipOrNot == 1){ setState(() { _pinController.clear(); _enableBiometric = false; }); } String token; if (enrollToken != null && enrollToken.isNotEmpty) { token = enrollToken; } else { token = _postToken; } int is_biometric_enabled; if (_enableBiometric == true) { is_biometric_enabled = 1; } else { is_biometric_enabled = 0; } var params = {}; if (empMobileNo != null && empMobileNo.isNotEmpty) { params = { 'mobile_number': empMobileNo, 'mpin': _pinController.text, 'is_biometric_enabled': is_biometric_enabled, 'is_mpin_skipped':skipOrNot }; } else if(empEmailid != null && empEmailid.isNotEmpty) { params = { 'email_id': empEmailid, 'mpin': _pinController.text, 'is_biometric_enabled': is_biometric_enabled, 'is_mpin_skipped':skipOrNot }; } print('SAVE MPIN : $params'); // return; final response = await http.post( Uri.parse(Environment.apiUrlEnrollment + 'saveMpin'), body: json.encode(params), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'Authorization': 'Bearer $token', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { setState(() { _isPinSaved = true; // 🔒 HARD LOCK }); if (_enableBiometric == true) { await _authService.saveBiometricEnableKey(_enableBiometric); bool biometricEnabled = await _authService.authenticateWithBiometrics(); if (biometricEnabled) { if (_postToken != null && _postToken.isNotEmpty) { SessionManager().unlockMobileAppSession(); context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); } else { SessionManager().unlockMobileAppSession(); context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); } } else { setState(() { _isPinSaved = false; // ✅ unlock again }); ToastHelper.showErrorToast( context, 'Biometric authentication failed'); } } else { if (_postToken != null && _postToken.isNotEmpty) { SessionManager().unlockMobileAppSession(); context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); } else { SessionManager().unlockMobileAppSession(); context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); } } } else { ToastHelper.showErrorToast(context, message); print('Invalid Pin Number'); } } else if (response.statusCode == 401) { await SessionManager().clear(); ToastHelper.showErrorToast(context, 'Session Out'); if (!context.mounted) return; context.go('/login'); } else if (response.statusCode == 403) { await SessionManager().clear(); ToastHelper.showErrorToast(context, 'Session Out'); if (!context.mounted) return; context.go('/login'); } else if (response.statusCode == 451) { final body = jsonDecode(response.body); final message = body['message']; ToastHelper.showWarningToast(context, message); } else if (response.statusCode == 429) { final body = jsonDecode(response.body); final message = body['message']; ToastHelper.showWarningToast(context, message); } else { throw Exception('Failed to load data'); } // } } catch (e) { ToastHelper.showErrorToast(context, 'Unable to process. Please try again later'); print('Error: $e'); } finally { if (mounted) { setState(() { _isSavingPin = false; }); } } } String? _validatePin(String? value) { if (value == null || value.isEmpty) { return 'Please enter a PIN'; } if (value.length != 4) { return 'PIN must be 4 digits'; } return null; } String? _validateConfirmPin(String? value) { if (value == null || value.isEmpty) { return 'Please re-enter your PIN'; } if (value != _pinController.text) { return 'PINs do not match'; } return null; } Future setSkipStatus() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); empMobileNo = prefs.getString('empMobileNo'); empEmailid = prefs.getString('empEmailid'); emp_status = prefs.getString('emp_status'); enrollToken = prefs.getString('enrollToken'); _postToken = prefs.getString('_postToken'); await _authService.saveSkipStatus(0); if (_postToken != null && _postToken.isNotEmpty) { SessionManager().unlockMobileAppSession(); context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); } else { SessionManager().unlockMobileAppSession(); context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); } } @override Widget build(BuildContext context) { const focusedBorderColor = Color.fromRGBO(23, 171, 144, 1); const fillColor = Color.fromRGBO(243, 246, 249, 0); const borderColor = Color.fromRGBO(23, 171, 144, 0.4); Size _size = MediaQuery.of(context).size; EdgeInsets marginInsets = EdgeInsets.zero; if (Responsive.isDesktop(context)) { marginInsets = const EdgeInsets.only( left: 0, right: 0, bottom: 0, top: 0, ); } else if (Responsive.isMobile(context)) { marginInsets = const EdgeInsets.only( left: 25, // Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } else if (Responsive.isTablet(context)) { marginInsets = const EdgeInsets.only( left: 25, // Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } final defaultPinTheme = PinTheme( width: 56, height: 56, textStyle: const TextStyle( fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(19), border: Border.all(color: borderColor), ), ); return WillPopScope( onWillPop: () async { return false; }, child: Scaffold( resizeToAvoidBottomInset: true, // IMPORTANT bottomNavigationBar: SafeArea( child: Container( color: Colors.white, // 👈 set your color here padding: const EdgeInsets.only(bottom: 8, top: 4), child: RichText( textAlign: TextAlign.center, text: TextSpan( text: 'By continuing, you agree with our ', style: GoogleFonts.poppins( color: Colors.black, fontSize: 9, ), children: const [ TextSpan( text: 'privacy policy ', style: TextStyle(color: Color(0xFF00989E)), ), TextSpan(text: 'and '), TextSpan( text: 'terms of use', style: TextStyle(color: Color(0xFF00989E)), ), ], ), ), ), ), body: Container( color: Colors.white, // Set the color for the body child: SizedBox.expand( child: Stack(children: [ SingleChildScrollView( child: Container( padding: EdgeInsets.all(0), color: Color(0xFFFFFCE5), child: Column(children: [ Container( decoration: BoxDecoration( color: Color( 0xFFFFFCE5), // Set background color for the container borderRadius: BorderRadius.circular( 10), // Set border radius for the container ), child: Column( // crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( flex: 12, child: Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.only( left: 16.0), // Add left margin child: Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 100, ), ), ), ), ], ), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( flex: 12, child: Align( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.only( left: 16.0), // Add left margin child: SvgPicture.string( SvgService.getSvg('mpin'), width: 150, height: 150, ), ), ), ), ], ), SizedBox(height: 30), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( flex: 12, child: Text( 'Set PIN To enter', textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 16, color: Color(0xFF404040), fontWeight: FontWeight.w500, ), ), ), ], ), SizedBox(height: 10), ], ), ), Container( decoration: BoxDecoration( color: Colors .white, // Set background color for the container borderRadius: BorderRadius.only( topLeft: Radius.circular(150.0), topRight: Radius.circular(0), ), // Set border radius for the container ), padding: EdgeInsets.only( top: 20, bottom: 10, left: 10, right: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( margin: marginInsets, alignment: Alignment.bottomCenter, child: SingleChildScrollView( child: Form( key: _formKey, child: Column( children: [ Row( children: [ Expanded( flex: 12, child: Column( children: [ SizedBox(height: 40), Container( margin: EdgeInsets.symmetric( horizontal: 30), child: Row( mainAxisAlignment: MainAxisAlignment .start, // Align text to the right children: [ Align( // alignment: Alignment.centerLeft, child: Text( 'Enter the 4 Digit pin', style: GoogleFonts .poppins( fontSize: 16, color: Color( 0xFF909090), fontWeight: FontWeight .w400, ), ), ), ]), ), SizedBox(height: 5), Container( margin: EdgeInsets.symmetric( horizontal: 0), child: Pinput( enabled: !_isSavingPin && !_isPinSaved, length: 4, defaultPinTheme: defaultPinTheme, separatorBuilder: (index) => const SizedBox( width: 8), showCursor: true, controller: _pinController, validator: _validatePin, errorPinTheme: defaultPinTheme .copyBorderWith( border: Border.all( color: Colors .redAccent), ), hapticFeedbackType: HapticFeedbackType .lightImpact, onCompleted: (pin) { debugPrint( 'onCompleted: $pin'); }, onChanged: (value) { debugPrint( 'onChanged: $value'); }, cursor: Column( mainAxisAlignment: MainAxisAlignment .end, children: [ Container( margin: const EdgeInsets .only( bottom: 9), width: 22, height: 1, color: focusedBorderColor, ), ], ), focusedPinTheme: defaultPinTheme .copyWith( decoration: defaultPinTheme .decoration! .copyWith( borderRadius: BorderRadius .circular(8), border: Border.all( color: focusedBorderColor), ), ), submittedPinTheme: defaultPinTheme .copyWith( decoration: defaultPinTheme .decoration! .copyWith( color: fillColor, borderRadius: BorderRadius .circular(19), border: Border.all( color: focusedBorderColor), ), ), ), ), SizedBox(height: 15), Container( margin: EdgeInsets.symmetric( horizontal: 30), child: Row( mainAxisAlignment: MainAxisAlignment .start, // Align text to the right children: [ Align( // alignment: Alignment.centerLeft, child: Text( 'Reenter the 4 Digit pin', style: GoogleFonts .poppins( fontSize: 16, color: Color( 0xFF909090), fontWeight: FontWeight .w400, ), ), ), ]), ), SizedBox(height: 5), Container( margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Pinput( enabled: !_isSavingPin && !_isPinSaved, length: 4, defaultPinTheme: defaultPinTheme, separatorBuilder: (index) => const SizedBox( width: 8), showCursor: true, controller: _confirmPinController, validator: _validateConfirmPin, errorPinTheme: defaultPinTheme .copyBorderWith( border: Border.all( color: Colors .redAccent), ), hapticFeedbackType: HapticFeedbackType .lightImpact, onCompleted: (pin) { debugPrint( 'onCompleted: $pin'); }, onChanged: (value) { debugPrint( 'onChanged: $value'); }, cursor: Column( mainAxisAlignment: MainAxisAlignment .end, children: [ Container( margin: const EdgeInsets .only( bottom: 9), width: 22, height: 1, color: focusedBorderColor, ), ], ), focusedPinTheme: defaultPinTheme .copyWith( decoration: defaultPinTheme .decoration! .copyWith( borderRadius: BorderRadius .circular(8), border: Border.all( color: focusedBorderColor), ), ), submittedPinTheme: defaultPinTheme .copyWith( decoration: defaultPinTheme .decoration! .copyWith( color: fillColor, borderRadius: BorderRadius .circular(19), border: Border.all( color: focusedBorderColor), ), ), ), ), SizedBox(height: 15), Container( margin: EdgeInsets.symmetric( horizontal: 30), child: Row( mainAxisAlignment: MainAxisAlignment .end, // Align text to the right children: [ Text( 'Enable Biometric', style: GoogleFonts .poppins( fontSize: 12, color: Color( 0xFF000000), fontWeight: FontWeight .w400, ), ), SizedBox( width: 10), // Optional: Add some spacing between the text and switch Transform.scale( scale: 0.8, // Adjust the scale factor to change the size child: Switch( value: _enableBiometric, onChanged: (value) { setState(() { _enableBiometric = value; }); }, activeColor: Color( 0xFFE26728), // Color when the switch is ON inactiveTrackColor: Color( 0xFFD8D8D8), // Color of the switch track when OFF ), ), ]), ), SizedBox(height: 15), Container( margin: EdgeInsets.symmetric( horizontal: 30), child: SizedBox( width: double.infinity, height: 45, child: ElevatedButton( style: ElevatedButton .styleFrom( backgroundColor: Color(0xFF00989E), shape: RoundedRectangleBorder( borderRadius: BorderRadius .circular( 10), ), ), onPressed: (_isSavingPin || _isPinSaved) ? null : () { setLoginPin(0); }, child: _isSavingPin ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text( "Save", style: GoogleFonts.poppins(color: Colors.white), ), ), ), ), SizedBox(height: 15), Container( child: Row( mainAxisAlignment: MainAxisAlignment .center, children: [ GestureDetector( onTap: (_isSavingPin || _isPinSaved) ? null : () { setLoginPin(1); }, child: Text( 'Skip ?', style: GoogleFonts.poppins( fontSize: 12, color: (_isSavingPin || _isPinSaved) ? Colors.grey : const Color(0xFF929292), ), ), ), ], ), ), ], ), ), ], ), ], ), ), ), ), SizedBox(height: 40), ], ), ), ]), )), ]), )))); } }