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