211 lines
6.6 KiB
Dart
211 lines
6.6 KiB
Dart
|
|
import 'dart:ui';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
||
|
|
import '../services/otp_service.dart';
|
||
|
|
|
||
|
|
class FeaVerificationModal extends StatefulWidget {
|
||
|
|
final String phoneNumber;
|
||
|
|
final String conducenteName;
|
||
|
|
final VoidCallback onVerificationSuccess;
|
||
|
|
final VoidCallback onCancel;
|
||
|
|
|
||
|
|
const FeaVerificationModal({
|
||
|
|
super.key,
|
||
|
|
required this.phoneNumber,
|
||
|
|
required this.conducenteName,
|
||
|
|
required this.onVerificationSuccess,
|
||
|
|
required this.onCancel,
|
||
|
|
});
|
||
|
|
|
||
|
|
static Future<void> show({
|
||
|
|
required BuildContext context,
|
||
|
|
required String phoneNumber,
|
||
|
|
required String conducenteName,
|
||
|
|
required VoidCallback onVerificationSuccess,
|
||
|
|
required VoidCallback onCancel,
|
||
|
|
}) {
|
||
|
|
return showDialog(
|
||
|
|
context: context,
|
||
|
|
barrierDismissible: false,
|
||
|
|
builder: (context) => FeaVerificationModal(
|
||
|
|
phoneNumber: phoneNumber,
|
||
|
|
conducenteName: conducenteName,
|
||
|
|
onVerificationSuccess: onVerificationSuccess,
|
||
|
|
onCancel: onCancel,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<FeaVerificationModal> createState() => _FeaVerificationModalState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _FeaVerificationModalState extends State<FeaVerificationModal> {
|
||
|
|
final OtpService _otpService = OtpService();
|
||
|
|
final TextEditingController _otpController = TextEditingController();
|
||
|
|
|
||
|
|
bool _isLoading = false;
|
||
|
|
bool _codeSent = false;
|
||
|
|
String? _verificationId;
|
||
|
|
String? _errorMessage;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_inviaSms();
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _inviaSms() async {
|
||
|
|
setState(() {
|
||
|
|
_isLoading = true;
|
||
|
|
_errorMessage = null;
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
await _otpService.sendOtp(
|
||
|
|
phoneNumber: widget.phoneNumber,
|
||
|
|
onCodeSent: (verId) {
|
||
|
|
if (mounted) {
|
||
|
|
setState(() {
|
||
|
|
_verificationId = verId;
|
||
|
|
_codeSent = true;
|
||
|
|
_isLoading = false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onVerificationFailed: (e) {
|
||
|
|
if (mounted) {
|
||
|
|
setState(() {
|
||
|
|
_errorMessage = e.message ?? "Errore nell'invio dell'SMS. Controlla il numero.";
|
||
|
|
_isLoading = false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onVerificationCompleted: (credential) async {
|
||
|
|
// Auto-resolution (su alcuni Android)
|
||
|
|
if (mounted) {
|
||
|
|
setState(() => _isLoading = true);
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
await FirebaseAuth.instance.signInWithCredential(credential);
|
||
|
|
widget.onVerificationSuccess();
|
||
|
|
} catch (e) {
|
||
|
|
if (mounted) {
|
||
|
|
setState(() {
|
||
|
|
_errorMessage = "Errore auto-verifica.";
|
||
|
|
_isLoading = false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onCodeAutoRetrievalTimeout: (verId) {
|
||
|
|
_verificationId = verId;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
} catch (e) {
|
||
|
|
if (mounted) {
|
||
|
|
setState(() {
|
||
|
|
_errorMessage = "Errore generico: $e";
|
||
|
|
_isLoading = false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _verificaPin() async {
|
||
|
|
final code = _otpController.text.trim();
|
||
|
|
if (code.length != 6 || _verificationId == null) {
|
||
|
|
setState(() => _errorMessage = "Inserisci il codice a 6 cifre");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setState(() {
|
||
|
|
_isLoading = true;
|
||
|
|
_errorMessage = null;
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
await _otpService.verifyCode(verificationId: _verificationId!, smsCode: code);
|
||
|
|
widget.onVerificationSuccess();
|
||
|
|
} catch (e) {
|
||
|
|
if (mounted) {
|
||
|
|
setState(() {
|
||
|
|
_errorMessage = "Codice errato o scaduto. Riprova.";
|
||
|
|
_isLoading = false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return BackdropFilter(
|
||
|
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||
|
|
child: AlertDialog(
|
||
|
|
backgroundColor: Colors.white.withValues(alpha: 0.95),
|
||
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||
|
|
title: Column(
|
||
|
|
children: [
|
||
|
|
const Icon(Icons.verified_user, color: Colors.green, size: 50),
|
||
|
|
const SizedBox(height: 10),
|
||
|
|
const Text("Firma Elettronica", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), textAlign: TextAlign.center),
|
||
|
|
const SizedBox(height: 5),
|
||
|
|
Text(widget.conducenteName, style: const TextStyle(fontSize: 14, color: Colors.black87), textAlign: TextAlign.center),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
content: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
if (_errorMessage != null)
|
||
|
|
Padding(
|
||
|
|
padding: const EdgeInsets.only(bottom: 10),
|
||
|
|
child: Text(_errorMessage!, style: const TextStyle(color: Colors.red, fontSize: 13), textAlign: TextAlign.center),
|
||
|
|
),
|
||
|
|
if (!_codeSent) ...[
|
||
|
|
const Text("Invio SMS in corso...", textAlign: TextAlign.center),
|
||
|
|
const SizedBox(height: 15),
|
||
|
|
if (_isLoading) const CircularProgressIndicator(),
|
||
|
|
] else ...[
|
||
|
|
Text("Abbiamo inviato un codice a:\n${widget.phoneNumber}", textAlign: TextAlign.center),
|
||
|
|
const SizedBox(height: 15),
|
||
|
|
TextField(
|
||
|
|
controller: _otpController,
|
||
|
|
keyboardType: TextInputType.number,
|
||
|
|
maxLength: 6,
|
||
|
|
textAlign: TextAlign.center,
|
||
|
|
style: const TextStyle(fontSize: 24, letterSpacing: 8, fontWeight: FontWeight.bold),
|
||
|
|
decoration: InputDecoration(
|
||
|
|
counterText: "",
|
||
|
|
hintText: "000000",
|
||
|
|
filled: true,
|
||
|
|
fillColor: Colors.grey.shade100,
|
||
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(height: 10),
|
||
|
|
if (_isLoading) const CircularProgressIndicator()
|
||
|
|
else ElevatedButton(
|
||
|
|
style: ElevatedButton.styleFrom(
|
||
|
|
backgroundColor: Colors.blue.shade800,
|
||
|
|
foregroundColor: Colors.white,
|
||
|
|
minimumSize: const Size(double.infinity, 50),
|
||
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
|
|
elevation: 2,
|
||
|
|
),
|
||
|
|
onPressed: _verificaPin,
|
||
|
|
child: const Text("VERIFICA", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||
|
|
),
|
||
|
|
]
|
||
|
|
],
|
||
|
|
),
|
||
|
|
actions: [
|
||
|
|
TextButton(
|
||
|
|
onPressed: widget.onCancel,
|
||
|
|
child: const Text("ANNULLA", style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|