You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

382 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:supplier_new/orders/unloading_inprogress.dart';
import '../common/settings.dart';
import 'all_orders.dart';
class UnloadArrivalScreen extends StatefulWidget {
var details;
UnloadArrivalScreen({
this.details
});
@override
State<UnloadArrivalScreen> createState() => _UnloadArrivalScreenState();
}
class _UnloadArrivalScreenState extends State<UnloadArrivalScreen> 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<double> _offsetAnimation;
bool _isOtpComplete = false;
void _checkOtpFilled() {
setState(() {
_isOtpComplete =
_c1.text.isNotEmpty &&
_c2.text.isNotEmpty &&
_c3.text.isNotEmpty &&
_c4.text.isNotEmpty;
});
}
@override
void dispose() {
_c1.dispose();
_c2.dispose();
_c3.dispose();
_c4.dispose();
_f1.dispose();
_f2.dispose();
_f3.dispose();
_f4.dispose();
_shakeController.dispose();
super.dispose();
}
@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);
}
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
WillPopScope(
onWillPop: () async {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => AllOrders(navigationFrom:"")),
(route) => false,
);
return false;},
child:Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.appBarWithNotificationIcon(widget.details.building_name, widget.details.type_of_water, widget.details.capacity, context),
body: Stack(
children: [
// Map background
Positioned.fill(
child: Image.asset(
'images/google_maps.png', // make sure this exists & is declared in pubspec.yaml
fit: BoxFit.cover,
),
),
// Success text
Positioned(
left: 16,
right: 16,
top: 8,
child: Padding(
padding: const EdgeInsets.only(top: 24, left: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('You have reached!',
style: fontTextStyle(
20, const Color(0xFF101214), FontWeight.w600)),
const SizedBox(height: 4),
Text('Hurray! You are on-time.',
style: fontTextStyle(
16, const Color(0xFF0A9E04), FontWeight.w500)),
],
),
),
),
// Center card (nudged up)
Align(
alignment: const Alignment(0, -0.6),
child: Container(
width: MediaQuery.of(context).size.width * 0.86,
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))
],
),
child: widget.details.tank_name!=''?Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.details.tank_name!=''?"Unload Water in":'The user hasnt selected a tank. Please ask the user to select one.',
textAlign: TextAlign.center,
style: fontTextStyle(
16, const Color(0xFF939495), FontWeight.w500)),
const SizedBox(height: 4),
Visibility(
visible: widget.details.tank_name!='',
child: Text(widget.details.tank_name,
style: fontTextStyle(
20, const Color(0xFF101214), FontWeight.w600)),),
const SizedBox(height: 14),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Enter OTP to Start Unloading',
style: fontTextStyle(14, const Color(0xFF646566),
FontWeight.w500)),
const SizedBox(width: 6),
Icon(Icons.help_outline,
size: 16, color: Colors.grey.shade600),
],
),
const SizedBox(height: 14),
// OTP boxes
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 -> navigate to UnloadingInProgressScreen
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": "start",
"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.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => UnloadingInProgressScreen(details: widget.details)),
(route) => false,
);
} else {
_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),
foregroundColor:
MaterialStateProperty.resolveWith((_) => Colors.white),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28)),
),
),
child: Text('Continue',
style: fontTextStyle(
16, const Color(0xFFFFFFFF), FontWeight.w400)),
),
),
],
):
Text(widget.details.tank_name!=''?"Unload Water in":'The user hasnt selected a tank. Please ask the user to select one.',
textAlign: TextAlign.center,
style: fontTextStyle(
16, const Color(0xFF939495), FontWeight.w500)),
),
),
],
),
));
}
}
class _OtpBox extends StatelessWidget {
final TextEditingController controller;
final FocusNode focusNode;
final ValueChanged<String> 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,
),
);
}
}