import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import '../common/settings.dart'; import 'collect_money.dart'; TextStyle fts(double s, Color c, FontWeight w) => GoogleFonts.inter(fontSize: s, color: c, fontWeight: w); class UnloadingCompleteScreen extends StatefulWidget { var details; UnloadingCompleteScreen({ this.details, }); @override State createState() => _UnloadingCompleteScreenState(); } class _UnloadingCompleteScreenState extends State with TickerProviderStateMixin{ final _c1 = TextEditingController(); final _c2 = TextEditingController(); final _c3 = TextEditingController(); final _c4 = TextEditingController(); final _f1 = FocusNode(); final _f2 = FocusNode(); final _f3 = FocusNode(); final _f4 = FocusNode(); bool get _otpReady => _c1.text.isNotEmpty && _c2.text.isNotEmpty && _c3.text.isNotEmpty && _c4.text.isNotEmpty; late AnimationController _shakeController; late Animation _offsetAnimation; bool _isOtpComplete = false; void _checkOtpFilled() { setState(() { _isOtpComplete = _c1.text.isNotEmpty && _c2.text.isNotEmpty && _c3.text.isNotEmpty && _c4.text.isNotEmpty; }); } @override void initState() { super.initState(); _shakeController = AnimationController( duration: const Duration(milliseconds: 450), vsync: this, ); _offsetAnimation = Tween(begin: 0.0, end: 15.0) .chain(CurveTween(curve: Curves.elasticIn)) .animate(_shakeController); } @override void dispose() { _c1.dispose(); _c2.dispose(); _c3.dispose(); _c4.dispose(); _f1.dispose(); _f2.dispose(); _f3.dispose(); _f4.dispose(); _shakeController.dispose(); super.dispose(); } void _onDigit({ required String value, required FocusNode current, FocusNode? next, FocusNode? prev, }) { if (value.length == 1 && next != null) { next.requestFocus(); } else if (value.isEmpty && prev != null) { prev.requestFocus(); } setState(() {}); } InputDecoration _otpDecoration(bool focused) => InputDecoration( counterText: '', contentPadding: const EdgeInsets.symmetric(vertical: 14), filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Color(0xFFE0E0E0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Colors.black, width: 1.2), ), ); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppSettings.appBarWithNotificationIcon(widget.details.building_name, widget.details.type_of_water, widget.details.capacity, context), body: SafeArea( child: Align( alignment: const Alignment(0, -0.25), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( width: double.infinity, constraints: const BoxConstraints(maxWidth: 520), padding: const EdgeInsets.fromLTRB(16, 18, 16, 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 10, offset: Offset(0, 4))], border: Border.all(color: const Color(0xFFEDEDED)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Green check Container( width: 28, height: 28, decoration: const BoxDecoration(color: Color(0xFF2FAE22), shape: BoxShape.circle), child: const Icon(Icons.check, color: Colors.white, size: 18), ), const SizedBox(height: 12), Text('Unloading Complete', style: fts(20, const Color(0xFF101214), FontWeight.w700)), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Enter OTP to finish delivery', style: fts(12, const Color(0xFF7E7F80), FontWeight.w500)), const SizedBox(width: 6), Icon(Icons.help_outline, size: 16, color: Colors.grey.shade600), ], ), const SizedBox(height: 14), // OTP row AnimatedBuilder( animation: _shakeController, builder: (context, child) { return Transform.translate( offset: Offset(_offsetAnimation.value, 0), child: child, ); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _OtpBox( controller: _c1, focusNode: _f1, onChanged: (v){ _onDigit(value: v, current: _f1, next: _f2); _checkOtpFilled(); }, //onChanged: (v) => _onDigit(value: v, current: _f1, next: _f2), decoration: _otpDecoration(_f1.hasFocus), ), _OtpBox( controller: _c2, focusNode: _f2, onChanged: (v){ _onDigit(value: v, current: _f2, next: _f3, prev: _f1); _checkOtpFilled(); }, decoration: _otpDecoration(_f2.hasFocus), ), _OtpBox( controller: _c3, focusNode: _f3, onChanged: (v){ _onDigit(value: v, current: _f3, next: _f4, prev: _f2); _checkOtpFilled(); }, decoration: _otpDecoration(_f3.hasFocus), ), _OtpBox( controller: _c4, focusNode: _f4, onChanged: (v){ _onDigit(value: v, current: _f4, prev: _f3); _checkOtpFilled(); }, decoration: _otpDecoration(_f4.hasFocus), ), ], ), ), const SizedBox(height: 16), // Continue button -> go to CollectMoneyScreen SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: _isOtpComplete ? () async { if (!_otpReady) return; AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); if (!isOnline) { Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Please Check Internet"); return; } // Combine OTP final otp = "${_c1.text}${_c2.text}${_c3.text}${_c4.text}"; var payload = { "action": "stop", "percentage": "100", "otp": otp, }; // 🔥 Call API var response = await AppSettings.verifyUnloadStartOTP( widget.details.bookingid, payload ); Navigator.of(context, rootNavigator: true).pop(); // CLOSE LOADER if (response.isNotEmpty) { // Decode JSON var json = jsonDecode(response); if (json["status_code"] == 200) { // OTP VERIFIED → NAVIGATE FocusScope.of(context).unfocus(); await Future.delayed(const Duration(milliseconds: 100)); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => CollectMoney(details:widget.details)), ); } else { // OTP WRONG /*FocusScope.of(context).unfocus(); Navigator.push( context, MaterialPageRoute( builder: (context) => UnloadingInProgressScreen(details: widget.details), ), );*/ _shakeController.forward(from: 0); // 🔥 SHAKES OTP BOXES Future.delayed(const Duration(milliseconds: 300), () { _c1.clear(); _c2.clear(); _c3.clear(); _c4.clear(); _f1.requestFocus(); // Move focus back to first box _checkOtpFilled(); // Disable button again }); AppSettings.longFailedToast(json["msg"] ?? "Invalid OTP"); return; } } else { AppSettings.longFailedToast("Something went wrong"); } } : null, style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((_) => Colors.black), // always black foregroundColor: MaterialStateProperty.resolveWith((_) => Colors.white), shape: MaterialStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), ), ), child: Text('Continue', style: fts(14, Colors.white, FontWeight.w600)), ), ), ], ), ), ), ), ), ); } } class _OtpBox extends StatelessWidget { final TextEditingController controller; final FocusNode focusNode; final ValueChanged onChanged; final InputDecoration decoration; const _OtpBox({ required this.controller, required this.focusNode, required this.onChanged, required this.decoration, }); @override Widget build(BuildContext context) { return SizedBox( width: 54, child: TextField( controller: controller, focusNode: focusNode, textAlign: TextAlign.center, keyboardType: TextInputType.number, maxLength: 1, inputFormatters: [FilteringTextInputFormatter.digitsOnly], decoration: decoration, onChanged: onChanged, ), ); } }