import 'dart:convert'; import 'dart:math'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:localstore/localstore.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; import '../widgets/encabezado_pais.dart'; class VerificacionPage extends StatefulWidget { final String correo; final String numTel; final String pais; final String verificacion; final String idUsuario; const VerificacionPage({ Key? key, required this.correo, required this.numTel, required this.pais, required this.verificacion, required this.idUsuario, }) : super(key: key); @override State createState() => _VerificacionPageState(); } class _VerificacionPageState extends State { late String _phone; late String _email; late String _pais; late String _keyPhone; late String _keyEmail; final _url = "smart.solerpalau.mx"; final db = Localstore.instance; bool _exedioPhone = false; bool _exedioEmail = false; bool _validoEmail = false; bool _validoPhone = false; bool _errorPhone = false; bool _errorEmail = false; bool _isLoading = true; int _intentosPhone = 1; int _intentosEmail = 1; final String _code = ""; TextEditingController _codigoController = TextEditingController(); TextEditingController _codigoTelController = TextEditingController(); @override void initState() { super.initState(); _phone = widget.numTel; _email = widget.correo; _pais = widget.pais; _keyPhone = _generateKey(); _keyEmail = _generateKey(); _enviosprimero(widget.verificacion); } String _generateKey(){ final rand = Random(); String num = rand.nextInt(999999).toString(); for(int i = num.length; i < 6; i++) { num = "0$num"; } return num; } Future _enviosprimero(String verificacion) async{ if(verificacion == "1"){ final correoResp = await _sendCorreo(_email, _keyEmail); final movilResp = await _sendPhone(_phone, _keyPhone); _errorEmail = !correoResp; _errorPhone = !movilResp; }else if(verificacion == "2"){ final movilResp = await _sendPhone(_phone, _keyPhone); _errorEmail = false; _validoEmail = true; _errorPhone = !movilResp; }else if(verificacion == "3"){ final correoResp = await _sendCorreo(_email, _keyEmail); _errorEmail = !correoResp; _validoPhone = true; _errorPhone = false; }else{ _errorEmail = false; _errorPhone = false; _validoEmail = true; _validoPhone = true; } /*print("===EMAIL: $_keyEmail==="); print("===PHONE: $_keyPhone===");*/ setState(() => _isLoading = false); } Future _sendCorreo(String correo, String otp) async{ final url = Uri.https(_url, "PR/api/v1/matrizMemoriasCalculo/sendOTPemail"); final res = await http.post(url, body: { "CORREO": correo, "OTP" : otp, "DATE" : _orderDate("${DateTime.now()}".substring(0, 16)), "UNTIL" : "el ${_orderDate("${DateTime.now().add(const Duration(minutes: 5))}".substring(0, 16))}", "YEAR" : "${DateTime.now().year}" } ); final decoded = jsonDecode(res.body); return decoded['response']; } Future _sendPhone(String movil, String otp) async{ final url = Uri.https("jlbn.plataforma-educativa.com.mx", "sms/"); final res = await http.post(url, body: { "MOVIL" : movil, "OTP" : otp, "DATE" : _orderDate('${DateTime.now()}'.substring(0, 16)).toUpperCase(), "UNTIL" : _orderDate("${DateTime.now().add(const Duration(minutes: 5))}".substring(0, 16)), "YEAR" : "${DateTime.now().year}", "PAIS" : _pais } ); final dec = jsonDecode(res.body); return !dec['error']; } String _orderDate(String dateTime){ final months = { "01": "enero", "02": "febrero", "03": "marzo", "04": "abril", "05": "mayo", "06": "junio", "07": "julio", "08": "agosto", "09": "septiembre", "10": "octubre", "11": "noviembre", "12": "diciembre" }; final dateTimeArr = dateTime.split(" "); final date = dateTimeArr.first; final dateArr = date.split("-"); return "${dateArr[2]} de ${months[dateArr[1]]} del ${dateArr[0]} a las ${dateTimeArr.last} hrs"; } @override Widget build(BuildContext context) { final _screenSize = MediaQuery.of(context).size; return Scaffold( body: _isLoading ? const Center( child: CircularProgressIndicator(), ) : _errorPhone || _errorEmail ? AlertDialog( title: Text( "¡ERROR!", style: GoogleFonts.robotoSlab( fontSize: 24.0, fontWeight: FontWeight.bold, color: const Color.fromRGBO(180, 4, 4, 1) ), ), content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( "error.png", width: 72.0, ), const SizedBox(height: 8.0), Text( "Se presentaron los siguientes errores al tratar de comunicarnos con usted:", style: GoogleFonts.roboto(), ), if(_errorEmail) const SizedBox(height: 8.0), if(_errorEmail) Text( "-Error al enviar el correo electrónico", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ), if(_errorPhone) const SizedBox(height: 8.0), if(_errorPhone) Text( "-Error al enviar el SMS", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ), const SizedBox(height: 8.0), Text( "Por favor contacte al departamento de TI de S&P México si alguno de los errores persiste.", style: GoogleFonts.roboto(), ), ], ), actions: [ TextButton( child: const Text("Cerrar"), onPressed: () => Navigator.pop(context), ) ], ) : SafeArea( child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Container( margin: const EdgeInsets.fromLTRB(32.0, 0.0, 16.0, 0.0), padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0), width: double.infinity, child: Row( children: [ Expanded( child: Text( "Portal de memorias de cálculo", style: GoogleFonts.roboto( fontSize: 16.0, color: Colors.black54, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ), ), const SizedBox(width: 8.0), IconButton( icon: const Icon(Icons.logout, color: Color(0xFFB40404)), onPressed: () => Navigator.pop(context, 'logout'), ), ], ), ), const SizedBox(height: 8.0), EncabezadoPais( paisCode: _code, paisName: _pais, ), const SizedBox( height: 32.0, ), Image.asset( "assets/matriz.png", width: 88.0, fit: BoxFit.cover, ), const SizedBox( height: 16.0, ), Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 32.0), child: const Text( "PORTAL DE MEMORIAS DE CÁLCULO", textAlign: TextAlign.center, style: TextStyle( fontFamily: "Helvetica Neue Black Cond", fontSize: 28.0, ), ), ), const SizedBox( height: 8.0, ), Container( padding: const EdgeInsets.symmetric(horizontal: 32.0), width: double.infinity, child: const Text( "Antes de continuar vamos a verificar tu correo y número de teléfono.", textAlign: TextAlign.center, style: TextStyle( fontFamily: "Helvetica Neue Light", fontSize: 22.0, fontWeight: FontWeight.w600, color: Color.fromRGBO(104, 104, 104, 1) ), ), ), const SizedBox( height: 32.0, ), if(widget.verificacion == "1" || widget.verificacion == "3") Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 32.0), decoration: widget.verificacion == "1" ? const BoxDecoration( border: Border( bottom: BorderSide( color: Colors.black12, ), ), ) : null, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Hemos enviado un código a tu correo electrónico. Por favor ingresalo debajo.", textAlign: TextAlign.center, style: GoogleFonts.roboto( color: Colors.grey, fontSize: 18.0, ), ), const SizedBox(height: 16.0), TextButton( child: Text( "Mi correo electrónico no es $_email, quiero cambiarlo.", style: GoogleFonts.roboto( fontSize: 12.0, ), ), onPressed: () async{ final newEmailController = TextEditingController(); final shouldRefresh = await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Cambiar correo electrónico."), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( "Ingresa el nuevo correo electrónico:", style: GoogleFonts.roboto(), ), TextField( controller: newEmailController, decoration: InputDecoration( hintText: "ejemplo@ejemplo.com", hintStyle: GoogleFonts.roboto( fontStyle: FontStyle.italic, color: Colors.black54, ), ), ), ], ), actions: [ TextButton( child: Text( "Cancelar", style: GoogleFonts.roboto(), ), onPressed: () => Navigator.pop(context, false), ), TextButton( child: Text( "Aceptar", style: GoogleFonts.roboto(), ), onPressed: (){ const pattern = r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'; final regExp = RegExp(pattern); if(!regExp.hasMatch(newEmailController.text)){ Fluttertoast.showToast( msg: "El correo no tiene un formato válido.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); }else{ Navigator.pop(context, true); } }, ) ], ) ); if(shouldRefresh && newEmailController.text != _email){ final url = Uri.https(_url, "PR/api/v1/matrizMemoriasCalculo/actualizarCuenta"); final res = await http.post(url, body: { "IDUSUARIO" : widget.idUsuario, "MOVIL" : "", "CORREO" : newEmailController.text }, ); final decoded = jsonDecode(res.body); if(decoded['response']){ setState(() { _email = newEmailController.text; _exedioEmail = false; _intentosEmail = 1; _isLoading = true; _keyEmail = _generateKey(); }); await _sendCorreo(_email, _keyEmail); setState((){ _codigoController = TextEditingController(); _codigoTelController = TextEditingController(); _isLoading = false; }); }else{ Fluttertoast.showToast( msg: "No se pudo enviar el correo.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }else{ Fluttertoast.showToast( msg: "Ambos correos son iguales, por favor revisa tu bandeja de entrada.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }, ), const SizedBox(height: 16.0), Text( "Código:", textAlign: TextAlign.center, style: GoogleFonts.roboto( fontSize: 18.0, ), ), const SizedBox(height: 16.0), SizedBox( width: 256.0, child: _exedioEmail ? ElevatedButton( child: Text( "Generar nuevo código", style: GoogleFonts.roboto(), ), onPressed: () async{ final newPin = _generateKey(); setState(() => _keyEmail = newPin); await _sendCorreo(_email, newPin); setState(() { _exedioEmail = false; _intentosEmail = 1; _codigoController = TextEditingController(); }); }, ) : AnimatedCrossFade( firstChild: Row( children: [ Expanded( child: Text( _keyEmail, textAlign: TextAlign.center, style: GoogleFonts.roboto( fontSize: 32.0, fontWeight: FontWeight.bold, color: Colors.green, letterSpacing: 16.0 ), ), ), const SizedBox(width: 8.0), const SizedBox( width: 32.0, height: 32.0, child: Icon( Icons.check_circle, color: Colors.green, ), ) ], ), secondChild: Row( children: [ const SizedBox(height: 12.0), Expanded(child: PinCodeTextField( appContext: context, length: 6, obscureText: false, animationType: AnimationType.fade, pinTheme: PinTheme( shape: PinCodeFieldShape.underline, fieldHeight: 32.0, fieldWidth: 32.0, activeFillColor: const Color(0xFFB40404), activeColor: const Color(0xFFB40404), selectedColor: const Color(0xFFFF0000), selectedFillColor: Colors.black26, inactiveColor: Colors.transparent, disabledColor: Colors.transparent, inactiveFillColor: Colors.black12, ), animationDuration: const Duration(milliseconds: 250), textStyle: GoogleFonts.roboto( color: Colors.white, fontWeight: FontWeight.bold, ), enableActiveFill: true, controller: _codigoController, enabled: !_validoEmail, onCompleted: (value) async{ if(_codigoController.text != _keyEmail && _intentosEmail < 5){ Fluttertoast.showToast( msg: "El código que ingresó es incorrecto. Intento $_intentosEmail de 5.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); _codigoController.clear(); setState(() => _intentosEmail++ ); }else if(_codigoController.text == _keyEmail){ setState(() => _validoEmail = true); final url = Uri.https(_url, "PR/api/v1/matrizMemoriasCalculo/verificarCuenta"); final res = await http.post(url, body: { "IDUSUARIO" : widget.idUsuario, "VERIFICACION" : "correo" }, ); final decoded = jsonDecode(res.body); if(!decoded['response']){ final items = await db.collection("login").get(); final lastID = items?.keys.last; final info = items![lastID]; info['verificacion'] = "2"; db.collection('login').doc(lastID).set(info); Fluttertoast.showToast( msg: "No se pudo actualizar su verificación en el servidor.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }else{ setState(() { _intentosEmail = 1; _exedioEmail = true; }); } }, onChanged: (val) => false, )), const SizedBox(height: 12.0), ], ), crossFadeState: _validoEmail ? CrossFadeState.showFirst : CrossFadeState.showSecond, duration: const Duration(milliseconds: 250), ), ), const SizedBox(height: 16.0), ], ), ), if(widget.verificacion == "1" || widget.verificacion == "2") const SizedBox(height: 32.0), if(widget.verificacion == "1" || widget.verificacion == "2") Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Hemos enviado un código a tu número telefónico. Por favor ingresalo debajo.", textAlign: TextAlign.center, style: GoogleFonts.roboto( color: Colors.grey, fontSize: 18.0, ), ), const SizedBox(height: 16.0), TextButton( child: Text( "Mi número telefónico no es $_phone, quiero cambiarlo.", style: GoogleFonts.roboto( fontSize: 12.0, ), ), onPressed: () async{ final newPhoneController = TextEditingController(); final shouldRefresh = await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Cambiar número telefónico."), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( "Ingresa el nuevo número de teléfono:", style: GoogleFonts.roboto(), ), TextField( controller: newPhoneController, decoration: InputDecoration( hintText: "7771234567", hintStyle: GoogleFonts.roboto( fontStyle: FontStyle.italic, color: Colors.black54, ), ), ) ], ), actions: [ TextButton( child: Text( "Cancelar", style: GoogleFonts.roboto(), ), onPressed: () => Navigator.pop(context, false), ), TextButton( child: Text( "Aceptar", style: GoogleFonts.roboto(), ), onPressed: (){ Navigator.pop(context, true); }, ), ], ) ); if(shouldRefresh && newPhoneController.text != _phone){ final url = Uri.https(_url, "PR/api/v1/matrizMemoriasCalculo/actualizarCuenta"); final res = await http.post(url, body: { "IDUSUARIO" : widget.idUsuario, "MOVIL" : newPhoneController.text, "CORREO" : "", }, ); final decoded = jsonDecode(res.body); if(decoded['response']){ setState(() { _phone = newPhoneController.text; _exedioPhone = false; _intentosPhone = 1; _isLoading = true; _keyPhone = _generateKey(); }); await _sendPhone(_phone, _keyPhone); setState((){ _codigoController = TextEditingController(); _codigoTelController = TextEditingController(); _isLoading = false; }); }else{ Fluttertoast.showToast( msg: "No se pudo enviar el SMS.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }else{ Fluttertoast.showToast( msg: "Ambos teléfonos son iguales, por favor revisa tus mensajes nuevos.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }, ), const SizedBox(height: 16.0), Text( "Código:", textAlign: TextAlign.center, style: GoogleFonts.roboto( fontSize: 18.0, ), ), const SizedBox(height: 16.0), SizedBox( width: 256, child: _exedioPhone ? ElevatedButton( child: Text( "Generar nuevo código", style: GoogleFonts.roboto(), ), onPressed: () async{ final newPin = _generateKey(); setState(() => _keyPhone = newPin); await _sendPhone(_phone, newPin); setState((){ _exedioPhone = false; _intentosPhone = 1; _codigoTelController = TextEditingController(); }); }, ) : AnimatedCrossFade( firstChild: Row( children: [ Expanded( child: Text( _keyPhone, textAlign: TextAlign.center, style: GoogleFonts.roboto( fontSize: 32.0, fontWeight: FontWeight.bold, color: Colors.green, letterSpacing: 16.0, ), ), ), const SizedBox(width: 8.0), const SizedBox( width: 32.0, height: 32.0, child: Icon( Icons.check_circle, color: Colors.green, ), ) ], ), secondChild: Row( children: [ const SizedBox(width: 12.0), Expanded(child: PinCodeTextField( appContext: context, length: 6, obscureText: false, animationType: AnimationType.fade, pinTheme: PinTheme( shape: PinCodeFieldShape.underline, fieldHeight: 32.0, fieldWidth: 32.0, activeFillColor: const Color(0xFFB40404), activeColor: const Color(0xFFB40404), selectedColor: const Color(0xFFFF0000), selectedFillColor: Colors.black26, inactiveColor: Colors.transparent, disabledColor: Colors.transparent, inactiveFillColor: Colors.black12, ), animationDuration: const Duration(milliseconds: 250), textStyle: GoogleFonts.roboto( color: Colors.white, fontWeight: FontWeight.bold, ), enableActiveFill: true, controller: _codigoTelController, onCompleted: (value) async{ if(_codigoTelController.text != _keyPhone && _intentosPhone < 5){ Fluttertoast.showToast( msg: "El código que ingresaste es incorrecto. Intento $_intentosPhone de 5.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); _codigoTelController.clear(); setState(() { _intentosPhone++; }); }else if(_codigoTelController.text == _keyPhone){ setState(() { _validoPhone = true; }); final url = Uri.https(_url, "PR/api/v1/matrizMemoriasCalculo/verificarCuenta"); final res = await http.post(url, body: { "IDUSUARIO" : widget.idUsuario, "VERIFICACION" : "movil" }, ); final decoded = jsonDecode(res.body); if(!decoded['response']){ final items = await db.collection("login").get(); final lastID = items?.keys.last; final info = items![lastID]; info['verificacion'] = "3"; db.collection('login').doc(lastID).set(info); Fluttertoast.showToast( msg: "No se pudo actualizar su verificación en el servidor.", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }else{ setState(() { _intentosPhone = 1; _exedioPhone = true; }); } }, onChanged: (val) => false, )), const SizedBox(width: 12.0), ], ), crossFadeState: _validoPhone ? CrossFadeState.showFirst : CrossFadeState.showSecond, duration: const Duration(milliseconds: 250), ), ), const SizedBox(height: 16.0), ], ), ), const SizedBox(height: 32.0), Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Column( children: [ MaterialButton( elevation: 4.0, padding: const EdgeInsets.symmetric(vertical: 8.0), minWidth: _screenSize.width-64.0, color: const Color(0xFFFF0000), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon( Icons.arrow_back, color: Colors.white, size: 32.0, ), SizedBox(width: 8.0), Text( "Regresar", style: TextStyle( fontFamily: "Helvetica Neue Black Cond", fontSize: 28.0, color: Colors.white, ), ), ], ), onPressed: () => Navigator.pop(context), ), const SizedBox(height: 16.0), MaterialButton( elevation: 4.0, padding: const EdgeInsets.symmetric(vertical: 8.0), minWidth: _screenSize.width-64, color: const Color(0xFFFF0000), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( "assets/continuar.png", width: 32.0, ), const SizedBox(width: 8.0), const Text( "Continuar", style: TextStyle( fontFamily: "Helvetica Neue Black Cond", fontSize: 28.0, color: Colors.white, ), ), ], ), disabledColor: Colors.grey, onPressed: _validoEmail && _validoPhone ? () async{ final action = await Navigator.pushNamed(context, "indexMC"); if(action == "logout"){ final items = await db.collection('login').get(); items!.forEach((key, value) { db.collection('login').doc(key).delete(); }); Navigator.pop(context); } } : null, ), ], ), ), const SizedBox(height: 32.0), ], ), ), ), ); } }