import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../services/api_service.dart'; import '../services/otp_controller.dart'; import 'success_screen.dart'; class OtpWaitScreen extends StatefulWidget { final String phone; final String method; final int expiresIn; const OtpWaitScreen({ super.key, required this.phone, required this.method, this.expiresIn = 120, }); @override State createState() => _OtpWaitScreenState(); } class _OtpWaitScreenState extends State with TickerProviderStateMixin { final OtpController _otpController = OtpController(); final TextEditingController _manualOtpController = TextEditingController(); String _status = 'waiting'; // waiting, call_detected, verified, timeout, permission_denied int _countdown = 120; Timer? _countdownTimer; bool _showManualInput = false; bool _isVerifying = false; String? _errorMessage; // Animation controllers late AnimationController _ringAnimationController; late AnimationController _pulseAnimationController; late Animation _pulseAnimation; bool get _isFlashCall => widget.method == 'flash_call'; bool get _isIOS => Platform.isIOS; @override void initState() { super.initState(); _countdown = widget.expiresIn; // Ringing animation (rotation) _ringAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 400), )..repeat(reverse: true); // Pulse animation (scale) _pulseAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), )..repeat(reverse: true); _pulseAnimation = Tween(begin: 1.0, end: 1.15).animate( CurvedAnimation( parent: _pulseAnimationController, curve: Curves.easeInOut, ), ); // Start countdown _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (!mounted) return; setState(() { _countdown--; }); if (_countdown <= 0) { timer.cancel(); if (_status == 'waiting') { setState(() { _status = 'timeout'; }); _stopAnimations(); } } }); // Setup OTP controller callbacks _otpController.onStatusChanged = (status) { if (!mounted) return; setState(() { _status = status; }); if (status == 'call_detected' || status == 'verified') { _stopAnimations(); } if (status == 'permission_denied') { _stopAnimations(); setState(() { _showManualInput = true; }); } }; _otpController.onOtpDetected = (otp) { if (!mounted) return; _autoVerify(otp); }; _otpController.onTimeout = () { if (!mounted) return; setState(() { _status = 'timeout'; }); _stopAnimations(); }; // Start waiting (Android only for call log polling) if (_isIOS) { // On iOS, show manual input directly setState(() { _showManualInput = true; _status = 'waiting'; }); } else if (_isFlashCall) { _otpController.startWaiting(widget.phone); } else { // SMS method - show manual input setState(() { _showManualInput = true; }); } } void _stopAnimations() { _ringAnimationController.stop(); _pulseAnimationController.stop(); } Future _autoVerify(String otp) async { setState(() { _isVerifying = true; _status = 'call_detected'; }); try { final result = await ApiService.verifyOtp(widget.phone, otp); if (!mounted) return; if (result['success'] == true) { setState(() { _status = 'verified'; _isVerifying = false; }); _stopAnimations(); // Navigate to success screen after a brief delay await Future.delayed(const Duration(milliseconds: 800)); if (!mounted) return; Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => SuccessScreen(phone: widget.phone), ), ); } else { setState(() { _isVerifying = false; _errorMessage = result['message'] ?? 'فشل التحقق، أعد المحاولة'; _showManualInput = true; }); } } catch (e) { if (!mounted) return; setState(() { _isVerifying = false; _errorMessage = 'حدث خطأ في الاتصال بالخادم'; _showManualInput = true; }); } } Future _manualVerify() async { final otp = _manualOtpController.text.trim(); if (otp.length != 4) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Directionality( textDirection: TextDirection.rtl, child: Text('يرجى إدخال 4 أرقام'), ), backgroundColor: Colors.red, duration: Duration(seconds: 2), ), ); return; } setState(() { _isVerifying = true; _errorMessage = null; }); try { final result = await ApiService.verifyOtp(widget.phone, otp); if (!mounted) return; if (result['success'] == true) { setState(() { _status = 'verified'; _isVerifying = false; }); _stopAnimations(); await Future.delayed(const Duration(milliseconds: 800)); if (!mounted) return; Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => SuccessScreen(phone: widget.phone), ), ); } else { setState(() { _isVerifying = false; _errorMessage = result['message'] ?? 'رمز التحقق غير صحيح'; }); } } catch (e) { if (!mounted) return; setState(() { _isVerifying = false; _errorMessage = 'حدث خطأ في الاتصال بالخادم'; }); } } String _getStatusText() { switch (_status) { case 'waiting': return _isFlashCall ? '⏳ بانتظار المكالمة...' : '⏳ بانتظار الرسالة...'; case 'call_detected': return '📞 جاءت المكالمة! جارٍ التحقق...'; case 'verified': return '✅ تم التحقق بنجاح!'; case 'timeout': return '❌ انتهت المدة، أعد المحاولة'; case 'permission_denied': return '⚠️ يرجى إدخال الرمز يدوياً'; default: return '⏳ بانتظار المكالمة...'; } } Color _getStatusColor() { switch (_status) { case 'waiting': return Colors.orange; case 'call_detected': return Colors.blue; case 'verified': return Colors.green; case 'timeout': return Colors.red; case 'permission_denied': return Colors.orange; default: return Colors.orange; } } String _getTitleText() { if (_status == 'verified') { return '✅ تم التحقق بنجاح!'; } if (_status == 'timeout') { return 'انتهت المدة'; } return _isFlashCall ? 'جارٍ الاتصال بك...' : 'جارٍ إرسال الرمز...'; } @override void dispose() { _countdownTimer?.cancel(); _otpController.dispose(); _manualOtpController.dispose(); _ringAnimationController.dispose(); _pulseAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.rtl, child: PopScope( canPop: true, onPopInvokedWithResult: (didPop, result) { if (didPop) { _otpController.stopWaiting(); _countdownTimer?.cancel(); } }, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: const Text('التحقق من الرقم'), backgroundColor: Colors.green, foregroundColor: Colors.white, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { _otpController.stopWaiting(); _countdownTimer?.cancel(); Navigator.of(context).pop(); }, ), ), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), child: Column( children: [ const SizedBox(height: 20), // Title Text( _getTitleText(), style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: _getStatusColor(), ), textAlign: TextAlign.center, ), const SizedBox(height: 40), // Animated phone icon if (_status == 'waiting' || _status == 'call_detected') AnimatedBuilder( animation: Listenable.merge( [_ringAnimationController, _pulseAnimationController]), builder: (context, child) { final ringOffset = _ringAnimationController.value * 10 - 5; return Transform.translate( offset: Offset(ringOffset, 0), child: Transform.scale( scale: _pulseAnimation.value, child: child, ), ); }, child: Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.green.shade50, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.green.withValues(alpha: 0.3), blurRadius: 20, spreadRadius: 5, ), ], ), child: Icon( _isFlashCall ? Icons.phone_in_talk_rounded : Icons.sms_rounded, size: 50, color: Colors.green, ), ), ) else if (_status == 'verified') Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.green.shade50, shape: BoxShape.circle, ), child: const Icon( Icons.check_circle_rounded, size: 50, color: Colors.green, ), ) else if (_status == 'timeout') Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.red.shade50, shape: BoxShape.circle, ), child: Icon( Icons.timer_off_rounded, size: 50, color: Colors.red.shade400, ), ) else Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.orange.shade50, shape: BoxShape.circle, ), child: Icon( Icons.warning_amber_rounded, size: 50, color: Colors.orange.shade400, ), ), const SizedBox(height: 32), // Countdown timer if (_status == 'waiting' || _status == 'call_detected') Column( children: [ Text( 'الوقت المتبقي', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), const SizedBox(height: 8), Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: _countdown <= 30 ? Colors.red : Colors.green, width: 3, ), ), child: Center( child: Text( '$_countdown', style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: _countdown <= 30 ? Colors.red : Colors.green, ), ), ), ), ], ), const SizedBox(height: 24), // Status text Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: _getStatusColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: _getStatusColor().withValues(alpha: 0.3), ), ), child: Text( _getStatusText(), textAlign: TextAlign.center, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: _getStatusColor(), ), ), ), // Phone number display const SizedBox(height: 16), Text( widget.phone, textDirection: TextDirection.ltr, style: TextStyle( fontSize: 16, color: Colors.grey.shade600, letterSpacing: 1, ), ), const SizedBox(height: 32), // Loading indicator when verifying if (_isVerifying) const Padding( padding: EdgeInsets.only(bottom: 24), child: CircularProgressIndicator(color: Colors.green), ), // Error message if (_errorMessage != null) Container( width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade200), ), child: Text( _errorMessage!, textAlign: TextAlign.center, style: TextStyle( color: Colors.red.shade700, fontSize: 14, ), ), ), // Manual fallback link (Android flash call only) if (!_showManualInput && _isFlashCall && !_isIOS && _status == 'waiting') TextButton( onPressed: () { setState(() { _showManualInput = true; }); }, child: const Text( 'أدخل الكود يدوياً', style: TextStyle( fontSize: 16, color: Colors.green, decoration: TextDecoration.underline, ), ), ), // Manual OTP input if (_showManualInput) ...[ const SizedBox(height: 8), Text( 'أدخل رمز التحقق المكون من 4 أرقام', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), const SizedBox(height: 16), // 4-digit OTP input TextField( controller: _manualOtpController, keyboardType: TextInputType.number, textDirection: TextDirection.ltr, textAlign: TextAlign.center, maxLength: 4, autofocus: _isIOS, autofillHints: _isIOS ? const [AutofillHints.oneTimeCode] : null, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ], decoration: InputDecoration( hintText: '• • • •', hintStyle: TextStyle( color: Colors.grey.shade400, fontSize: 24, letterSpacing: 12, ), counterText: '', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.green, width: 2), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16), filled: true, fillColor: Colors.white, ), style: const TextStyle( fontSize: 28, letterSpacing: 16, fontWeight: FontWeight.bold, ), onSubmitted: (_) { if (!_isVerifying) { _manualVerify(); } }, ), const SizedBox(height: 20), // Verify button SizedBox( width: double.infinity, height: 52, child: ElevatedButton( onPressed: _isVerifying ? null : _manualVerify, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, disabledBackgroundColor: Colors.green.shade300, disabledForegroundColor: Colors.white70, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isVerifying ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2.5, ), ) : const Text( 'تحقق', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), ], // Retry button on timeout if (_status == 'timeout') ...[ const SizedBox(height: 24), SizedBox( width: double.infinity, height: 52, child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'إعادة المحاولة', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), ], ], ), ), ), ), ); } }