parent
92e704a1b4
commit
ce55bf125b
@ -0,0 +1,521 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:supplier_new/common/settings.dart';
|
||||
import '../resources/drivers_model.dart';
|
||||
import '../resources/tankers_model.dart';
|
||||
import '../resources/source_loctaions_model.dart';
|
||||
|
||||
class AssignTankerScreen extends StatefulWidget {
|
||||
|
||||
final order;
|
||||
final List<DriversModel> driversList;
|
||||
final List<TankersModel> tankersList;
|
||||
final List<SourceLocationsModel> sourceLocationsList;
|
||||
|
||||
AssignTankerScreen({
|
||||
this.order,
|
||||
required this.driversList,
|
||||
required this.tankersList,
|
||||
required this.sourceLocationsList,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AssignTankerScreen> createState() =>
|
||||
_AssignTankerScreenState();
|
||||
}
|
||||
|
||||
class _AssignTankerScreenState
|
||||
extends State<AssignTankerScreen> {
|
||||
|
||||
int? selectedTankerIndex;
|
||||
int? selectedDriverIndex;
|
||||
int? selectedSourceIndex;
|
||||
|
||||
int _capToLiters(dynamic cap){
|
||||
|
||||
if(cap==null)return -1;
|
||||
|
||||
if(cap is num){
|
||||
return cap.round();
|
||||
}
|
||||
|
||||
final s=cap
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replaceAll(',','')
|
||||
.trim();
|
||||
|
||||
final match=
|
||||
RegExp(r'(\d+(\.\d+)?)')
|
||||
.firstMatch(s);
|
||||
|
||||
if(match==null)return -1;
|
||||
|
||||
final n=
|
||||
double.tryParse(match.group(1)!)??-1;
|
||||
|
||||
if(n<0)return -1;
|
||||
|
||||
if(s.contains('kl')){
|
||||
return (n*1000).round();
|
||||
}
|
||||
|
||||
return n.round();
|
||||
|
||||
}
|
||||
|
||||
DateTime parseOrderDateTime(){
|
||||
|
||||
DateTime d=
|
||||
DateFormat("dd-MMM-yyyy")
|
||||
.parse(widget.order.date);
|
||||
|
||||
DateTime t=
|
||||
DateFormat("hh:mm a")
|
||||
.parse(widget.order.time);
|
||||
|
||||
return DateTime(
|
||||
d.year,
|
||||
d.month,
|
||||
d.day,
|
||||
t.hour,
|
||||
t.minute
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
bool isTankerBlocked(
|
||||
TankersModel tanker){
|
||||
|
||||
if(tanker.blocked_dates==null ||
|
||||
tanker.blocked_dates.isEmpty){
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime orderDT=
|
||||
parseOrderDateTime();
|
||||
|
||||
for(var slot
|
||||
in tanker.blocked_dates){
|
||||
|
||||
if(slot['date']!=
|
||||
widget.order.date){
|
||||
continue;
|
||||
}
|
||||
|
||||
String timeRange=
|
||||
slot['time'];
|
||||
|
||||
List parts=
|
||||
timeRange.split("to");
|
||||
|
||||
if(parts.length!=2){
|
||||
continue;
|
||||
}
|
||||
|
||||
DateTime start=
|
||||
DateFormat("hh:mm a")
|
||||
.parse(parts[0].trim());
|
||||
|
||||
DateTime end=
|
||||
DateFormat("hh:mm a")
|
||||
.parse(parts[1].trim());
|
||||
|
||||
DateTime startDT=
|
||||
DateTime(
|
||||
orderDT.year,
|
||||
orderDT.month,
|
||||
orderDT.day,
|
||||
start.hour,
|
||||
start.minute
|
||||
);
|
||||
|
||||
DateTime endDT=
|
||||
DateTime(
|
||||
orderDT.year,
|
||||
orderDT.month,
|
||||
orderDT.day,
|
||||
end.hour,
|
||||
end.minute
|
||||
);
|
||||
|
||||
if(orderDT.isAfter(startDT)
|
||||
&&
|
||||
orderDT.isBefore(endDT)){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(orderDT==startDT ||
|
||||
orderDT==endDT){
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
String? getDriverBlockedTime(
|
||||
DriversModel driver){
|
||||
|
||||
if(driver.blocked_dates==null ||
|
||||
driver.blocked_dates.isEmpty){
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTime orderDT=
|
||||
parseOrderDateTime();
|
||||
|
||||
for(var slot
|
||||
in driver.blocked_dates){
|
||||
|
||||
if(slot['date']!=
|
||||
widget.order.date){
|
||||
continue;
|
||||
}
|
||||
|
||||
String timeRange=
|
||||
slot['time'];
|
||||
|
||||
List parts=
|
||||
timeRange.split("to");
|
||||
|
||||
if(parts.length!=2){
|
||||
continue;
|
||||
}
|
||||
|
||||
DateTime start=
|
||||
DateFormat("hh:mm a")
|
||||
.parse(parts[0].trim());
|
||||
|
||||
DateTime end=
|
||||
DateFormat("hh:mm a")
|
||||
.parse(parts[1].trim());
|
||||
|
||||
DateTime startDT=
|
||||
DateTime(
|
||||
orderDT.year,
|
||||
orderDT.month,
|
||||
orderDT.day,
|
||||
start.hour,
|
||||
start.minute
|
||||
);
|
||||
|
||||
DateTime endDT=
|
||||
DateTime(
|
||||
orderDT.year,
|
||||
orderDT.month,
|
||||
orderDT.day,
|
||||
end.hour,
|
||||
end.minute
|
||||
);
|
||||
|
||||
if(orderDT.isAfter(startDT)
|
||||
&&
|
||||
orderDT.isBefore(endDT)){
|
||||
return timeRange;
|
||||
}
|
||||
|
||||
if(orderDT==startDT ||
|
||||
orderDT==endDT){
|
||||
return timeRange;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context){
|
||||
|
||||
return Scaffold(
|
||||
|
||||
backgroundColor: Colors.white,
|
||||
|
||||
appBar: AppBar(
|
||||
|
||||
title:
|
||||
Text("Assign Tanker"),
|
||||
|
||||
),
|
||||
|
||||
body:
|
||||
|
||||
Column(
|
||||
|
||||
children:[
|
||||
|
||||
Expanded(
|
||||
|
||||
child:
|
||||
|
||||
SingleChildScrollView(
|
||||
|
||||
padding:
|
||||
EdgeInsets.all(16),
|
||||
|
||||
child:
|
||||
|
||||
Column(
|
||||
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
|
||||
children:[
|
||||
|
||||
/// ORDER CARD
|
||||
|
||||
Container(
|
||||
|
||||
padding:
|
||||
EdgeInsets.all(12),
|
||||
|
||||
decoration:
|
||||
BoxDecoration(
|
||||
|
||||
borderRadius:
|
||||
BorderRadius.circular(12),
|
||||
|
||||
border:
|
||||
Border.all(
|
||||
color:
|
||||
Color(0XFFC9C2F0)
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
child:
|
||||
|
||||
Row(
|
||||
|
||||
children:[
|
||||
|
||||
Column(
|
||||
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
|
||||
children:[
|
||||
|
||||
Text(
|
||||
widget.order.building_name
|
||||
),
|
||||
|
||||
Text(
|
||||
widget.order.displayAddress
|
||||
),
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
Spacer(),
|
||||
|
||||
Text(
|
||||
"${widget.order.distanceInKm} Km"
|
||||
),
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
SizedBox(height:20),
|
||||
|
||||
/// TANKERS
|
||||
|
||||
Text("SELECT TANKER"),
|
||||
|
||||
SizedBox(height:10),
|
||||
|
||||
...widget.tankersList
|
||||
.where((t)=>
|
||||
|
||||
_capToLiters(t.capacity)==
|
||||
_capToLiters(widget.order.capacity)
|
||||
|
||||
).toList()
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry){
|
||||
|
||||
int idx=
|
||||
entry.key;
|
||||
|
||||
var d=
|
||||
entry.value;
|
||||
|
||||
bool blocked=
|
||||
isTankerBlocked(d);
|
||||
|
||||
return GestureDetector(
|
||||
|
||||
onTap:blocked?null:(){
|
||||
|
||||
setState(() {
|
||||
|
||||
selectedTankerIndex=
|
||||
idx;
|
||||
|
||||
selectedDriverIndex=
|
||||
null;
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
child:
|
||||
|
||||
Card(
|
||||
|
||||
child:
|
||||
|
||||
Padding(
|
||||
|
||||
padding:
|
||||
EdgeInsets.all(12),
|
||||
|
||||
child:
|
||||
|
||||
Text(
|
||||
d.tanker_name
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}).toList(),
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
/// ASSIGN BUTTON
|
||||
|
||||
Padding(
|
||||
|
||||
padding:
|
||||
EdgeInsets.all(16),
|
||||
|
||||
child:
|
||||
|
||||
SizedBox(
|
||||
|
||||
width:double.infinity,
|
||||
|
||||
child:
|
||||
|
||||
ElevatedButton(
|
||||
|
||||
style:
|
||||
ElevatedButton.styleFrom(
|
||||
|
||||
backgroundColor:
|
||||
Color(0XFF8270DB)
|
||||
|
||||
),
|
||||
|
||||
onPressed:()async{
|
||||
|
||||
if(selectedTankerIndex==null){
|
||||
|
||||
AppSettings
|
||||
.longFailedToast(
|
||||
"Select tanker"
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final selectedTanker=
|
||||
widget.tankersList[
|
||||
selectedTankerIndex!
|
||||
];
|
||||
|
||||
final selectedDriver=
|
||||
selectedDriverIndex!=null
|
||||
?
|
||||
widget.driversList[
|
||||
selectedDriverIndex!
|
||||
]
|
||||
:
|
||||
null;
|
||||
|
||||
final selectedSource=
|
||||
selectedSourceIndex!=null
|
||||
?
|
||||
widget.sourceLocationsList[
|
||||
selectedSourceIndex!
|
||||
]
|
||||
:
|
||||
null;
|
||||
|
||||
var payload={};
|
||||
|
||||
payload["tankerName"]=
|
||||
selectedTanker.tanker_name;
|
||||
|
||||
payload["delivery_agent"]=
|
||||
selectedDriver?.driver_name;
|
||||
|
||||
payload["delivery_agent_mobile"]=
|
||||
selectedDriver?.phone_number;
|
||||
|
||||
payload["water_source_location"]=
|
||||
selectedSource?.source_name;
|
||||
|
||||
bool status=
|
||||
await AppSettings
|
||||
.assignTanker(
|
||||
payload,
|
||||
widget.order.dbId
|
||||
);
|
||||
|
||||
if(status){
|
||||
|
||||
AppSettings
|
||||
.longSuccessToast(
|
||||
"Assigned"
|
||||
);
|
||||
|
||||
Navigator.pop(
|
||||
context,
|
||||
true
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
child:
|
||||
|
||||
Text("Assign"),
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import '../common/settings.dart';
|
||||
import 'all_orders.dart';
|
||||
|
||||
class CollectMoney extends StatefulWidget {
|
||||
var details;
|
||||
|
||||
CollectMoney({this.details});
|
||||
|
||||
@override
|
||||
State<CollectMoney> createState() => _CollectMoneyState();
|
||||
}
|
||||
|
||||
class _CollectMoneyState extends State<CollectMoney> {
|
||||
String collectAmountInRupees = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Safe parsing of amount_due (avoid NaN)
|
||||
String raw = widget.details.amount_due?.toString() ?? "";
|
||||
double? parsed = double.tryParse(raw);
|
||||
|
||||
if (parsed == null || parsed.isNaN) {
|
||||
collectAmountInRupees = "0";
|
||||
} else {
|
||||
collectAmountInRupees = parsed.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
|
||||
String formatDeliveredDate() {
|
||||
final now = DateTime.now();
|
||||
|
||||
// Month names
|
||||
const months = [
|
||||
"Jan","Feb","Mar","Apr","May","Jun",
|
||||
"Jul","Aug","Sep","Oct","Nov","Dec"
|
||||
];
|
||||
|
||||
String day = now.day.toString().padLeft(2, '0');
|
||||
String month = months[now.month - 1];
|
||||
String year = now.year.toString();
|
||||
|
||||
int hour12 = now.hour > 12 ? now.hour - 12 : (now.hour == 0 ? 12 : now.hour);
|
||||
String hour = hour12.toString();
|
||||
String minute = now.minute.toString().padLeft(2, '0');
|
||||
String ampm = now.hour >= 12 ? "PM" : "AM";
|
||||
|
||||
return "$day-$month-$year $hour:$minute $ampm";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// --------------------------------------------
|
||||
// 🔥 UPI QR GENERATION
|
||||
// --------------------------------------------
|
||||
String upiId = widget.details.supplier_upi_id ?? "";
|
||||
String supplierName =
|
||||
Uri.encodeComponent(widget.details.supplier_name ?? "Supplier");
|
||||
|
||||
String qrData =
|
||||
"upi://pay?pa=$upiId&pn=$supplierName&am=$collectAmountInRupees&cu=INR&mam=1";
|
||||
|
||||
// --------------------------------------------
|
||||
|
||||
return
|
||||
WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => AllOrders(navigationFrom:"")),
|
||||
(route) => false,
|
||||
);
|
||||
return false;
|
||||
},
|
||||
|
||||
child: Scaffold(
|
||||
backgroundColor: Color(0XFFFFFFFF),
|
||||
appBar: AppSettings.appBarWithNotificationIcon(
|
||||
widget.details.building_name,
|
||||
widget.details.type_of_water,
|
||||
widget.details.capacity,
|
||||
context,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
|
||||
child: Align(
|
||||
alignment: const Alignment(0, -0.25),
|
||||
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),
|
||||
border: Border.all(color: const Color(0xFFEDEDED)),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Collect money',
|
||||
style: fontTextStyle(
|
||||
16, const Color(0xFF7E7F80), FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// AMOUNT
|
||||
Text(
|
||||
'₹$collectAmountInRupees',
|
||||
style: fontTextStyle(
|
||||
24, const Color(0xFF343637), FontWeight.w600),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --------------------------------------------
|
||||
// 🔥 QR Code Box
|
||||
// --------------------------------------------
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFE0E0E0)),
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: QrImageView(
|
||||
data: qrData,
|
||||
size: 200,
|
||||
gapless: true,
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// CONTINUE BUTTON
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
AppSettings.preLoaderDialog(context);
|
||||
|
||||
bool isOnline =
|
||||
await AppSettings.internetConnectivity();
|
||||
if (!isOnline) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
AppSettings.longFailedToast(
|
||||
"Please Check Internet");
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = {
|
||||
"amount_paid": widget.details.amount_paid,
|
||||
"payment_mode": widget.details.payment_mode,
|
||||
"orderStatus": "completed",
|
||||
"deliveredDate": formatDeliveredDate(),
|
||||
};
|
||||
|
||||
// 🔥 Call API
|
||||
var response = await AppSettings.amountpaidByCustomer(
|
||||
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) {
|
||||
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => AllOrders(navigationFrom:"")),
|
||||
(route) => false,
|
||||
);
|
||||
|
||||
return;
|
||||
} else {}
|
||||
AppSettings.longFailedToast(
|
||||
json["msg"] ?? "Invalid OTP");
|
||||
return;
|
||||
}
|
||||
// TODO: Navigate home
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Continue to Home',
|
||||
style: fontTextStyle(14, Colors.white, FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,328 @@
|
||||
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<UnloadingCompleteScreen> createState() => _UnloadingCompleteScreenState();
|
||||
}
|
||||
|
||||
class _UnloadingCompleteScreenState extends State<UnloadingCompleteScreen> 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 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<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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:supplier_new/orders/unloading_completed_otp.dart';
|
||||
|
||||
import '../common/settings.dart';
|
||||
import 'all_orders.dart';
|
||||
|
||||
TextStyle fts(double s, Color c, FontWeight w) =>
|
||||
GoogleFonts.inter(fontSize: s, color: c, fontWeight: w);
|
||||
|
||||
class UnloadingInProgressScreen extends StatefulWidget {
|
||||
final Duration? startFrom;
|
||||
var details;
|
||||
|
||||
UnloadingInProgressScreen({
|
||||
this.details,
|
||||
this.startFrom
|
||||
});
|
||||
/// If you want to start from a preset time (e.g., 12 minutes), pass Duration(minutes: 12)
|
||||
|
||||
|
||||
@override
|
||||
State<UnloadingInProgressScreen> createState() => _UnloadingInProgressScreenState();
|
||||
}
|
||||
|
||||
class _UnloadingInProgressScreenState extends State<UnloadingInProgressScreen> {
|
||||
late Duration _elapsed;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_elapsed = widget.startFrom ?? Duration.zero;
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
setState(() => _elapsed += const Duration(seconds: 1));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String _fmt(Duration d) {
|
||||
final h = d.inHours.toString().padLeft(2, '0');
|
||||
final m = (d.inMinutes % 60).toString().padLeft(2, '0');
|
||||
final s = (d.inSeconds % 60).toString().padLeft(2, '0');
|
||||
return '$h:$m:$s';
|
||||
}
|
||||
|
||||
void _onComplete() {
|
||||
_timer?.cancel();
|
||||
// Optionally show a toast/snackbar, then navigate
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(content: Text('Unloading marked complete at ${_fmt(_elapsed)}')),
|
||||
// );
|
||||
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => UnloadingCompleteScreen(details: widget.details,)),
|
||||
);
|
||||
}
|
||||
|
||||
@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: SafeArea(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Unloading in-progress',
|
||||
style: fontTextStyle(24, const Color(0xFF000000), FontWeight.w500)),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_fmt(_elapsed),
|
||||
style: fontTextStyle(40, const Color(0xFF000000), FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed: _onComplete,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Text('Unloading Complete',
|
||||
style: fontTextStyle(14, const Color(0xFFFFFFFF), FontWeight.w400)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue