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.

906 lines
29 KiB

// ------------- FULL FILE STARTS HERE ---------------
import 'dart:convert';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:bookatanker/common/settings.dart';
import '../models/tanksview_model.dart';
class ArrivalScreen extends StatefulWidget {
var orderDetails;
ArrivalScreen({
required this.orderDetails,
});
@override
State<ArrivalScreen> createState() => _ArrivalScreenState();
}
class _ArrivalScreenState extends State<ArrivalScreen> {
bool isLoading = false;
List<GetTanksDetailsModel> tankLevelsList = [];
List<GetTanksDetailsModel> sumpTanks = [];
GetTanksDetailsModel? selectedTank;
String otp = '';
String unload_complete_otp = '';
String? selectedTankId;
bool _showOrderSummary = false;
int currentStep = 0;
bool allowTankChange = true;
// -------------------------------------------
// FETCH TANK DATA
// -------------------------------------------
Future<void> getAllTanksData() async {
isLoading = true;
var response1 = await AppSettings.getTankLevels();
setState(() {
tankLevelsList =
((jsonDecode(response1)['data']) as List).map((dynamic model) {
return GetTanksDetailsModel.fromJson(model);
}).toList();
sumpTanks = tankLevelsList
.where((product) =>
product.tank_location.toString().toUpperCase() == 'SUMP')
.toList();
isLoading = false;
});
}
@override
void initState() {
getAllTanksData();
super.initState();
otp = widget.orderDetails.tank_otp ?? '';
unload_complete_otp = widget.orderDetails.stop_otp ?? '';
_setChecklistStepFromStatus();
}
// -------------------------------------------
// DETERMINE CHECKLIST STEP + LOCK TANK CHANGE
// -------------------------------------------
void _setChecklistStepFromStatus() {
String status = (widget.orderDetails.orderStatus ?? "").toLowerCase().trim();
if (status == "unloading_started" || status == "in_progress") {
currentStep = 1; // Step 1 active
allowTankChange = false;
}
else if (status == "unloading_stopped" || status == "unloading_completed") {
currentStep = 2; // Step 2 active → View Bill ENABLED
allowTankChange = false;
}
else if (status == "payment_pending" || status == "payment_waiting") {
currentStep = 2; // Still show Step 2
allowTankChange = false;
}
else {
currentStep = 0; // Initial stage
allowTankChange = true; // Only here tank change is allowed
}
setState(() {});
}
// -------------------------------------------
// REFRESH ALL DATA
// -------------------------------------------
Future<void> _refreshAllData() async {
try {
var response =
await AppSettings.updateBookingDetailsById(widget.orderDetails.dbId);
if (response != '') {
final data = jsonDecode(response)['data'] ?? {};
widget.orderDetails.orderStatus =
data["orderStatus"] ?? widget.orderDetails.orderStatus;
widget.orderDetails.tank_otp =
data["tank_otp"] ?? widget.orderDetails.tank_otp;
widget.orderDetails.stop_otp =
data["stop_otp"] ?? widget.orderDetails.stop_otp;
widget.orderDetails.tankName =
data["tankName"] ?? widget.orderDetails.tankName;
otp = widget.orderDetails.tank_otp ?? '';
unload_complete_otp = widget.orderDetails.stop_otp ?? '';
selectedTankId = widget.orderDetails.tankName ?? '';
}
await getAllTanksData();
_setChecklistStepFromStatus();
} catch (e) {
print("Refresh failed: $e");
}
}
// -------------------------------------------
// UPDATE TANK NAME TO SERVER
// -------------------------------------------
Future<void> _updateTankName(String tankName) async {
var payload = {"tankName": tankName};
var updateTankStatus = await AppSettings.updateTankNameWhileDelivery(
widget.orderDetails.dbId, payload);
if (updateTankStatus != '') {
AppSettings.longSuccessToast('Updated successfully');
final updatedData = jsonDecode(updateTankStatus)['data'] ?? {};
final updatedTankName = updatedData["tankName"]?.toString() ?? tankName;
final updatedOtp = updatedData["tank_otp"]?.toString() ?? "";
setState(() {
selectedTankId = updatedTankName;
otp = updatedOtp;
try {
widget.orderDetails.tankName = updatedTankName;
widget.orderDetails.tank_otp = updatedOtp;
} catch (_) {}
});
/*Navigator.of(context).pop(true);*/
} else {
AppSettings.longFailedToast('Update failed');
}
}
// -------------------------------------------
// BUILD MAIN UI
// -------------------------------------------
@override
Widget build(BuildContext context) {
final bool hasTankAlready =
widget.orderDetails.tankName != null &&
widget.orderDetails.tankName!.isNotEmpty;
GetTanksDetailsModel? preSelectedTank;
if (hasTankAlready) {
try {
preSelectedTank = sumpTanks.firstWhere(
(t) => t.tank_name == widget.orderDetails.tankName);
} catch (e) {
preSelectedTank = null;
}
}
return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.supplierAppBarWithoutActions(
'Order#${widget.orderDetails.bookingid}', context),
body: WillPopScope(
onWillPop: () async {
Navigator.pop(context, true);
return false;
},
child: SafeArea(
child: RefreshIndicator(
onRefresh: _refreshAllData,
displacement: 60,
strokeWidth: 3,
color: Color(0xFF1D7AFC),
backgroundColor: Colors.white,
triggerMode: RefreshIndicatorTriggerMode.onEdge,
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: buildMainBody(
context, hasTankAlready, preSelectedTank),
),
],
),
),
),
),
);
}
// -------------------------------------------
// YOUR ENTIRE UI BODY
// -------------------------------------------
Widget buildMainBody(context, hasTankAlready, preSelectedTank) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------ BANNER -----------------
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFFFFF4D9),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
"Arrived at your location!",
style: fontTextStyle(20, Color(0xFF232527), FontWeight.w800),
),
SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: "HOME",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w500),
),
TextSpan(
text: " - ${AppSettings.userAddress}",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w400),
),
],
),
),
],
),
),
SizedBox(height: 16),
// ------------ PROFILE -----------------
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: Color(0XFFE8F2FF),
child: Image.asset('images/profile_user.png',
width: 50, height: 50),
),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.orderDetails.delivery_agent,
style: fontTextStyle(
12, Color(0xFF343637), FontWeight.w500),
),
SizedBox(height: 4),
Text(
"TS J8 8905",
style: fontTextStyle(
12, Color(0xFF646566), FontWeight.w500),
),
],
),
Spacer(),
Row(children: [
Image.asset('images/message.png', width: 24, height: 24),
SizedBox(width: 16),
Image.asset('images/phone.png', width: 24, height: 24),
])
],
),
SizedBox(height: 16),
// ------------ TANK DROPDOWN -----------------
buildTankDropdown(hasTankAlready, preSelectedTank),
SizedBox(height: 16),
// ------------ OTP Boxes -----------------
if (selectedTankId != null || hasTankAlready)
buildStartOtp(),
SizedBox(height: 16),
if (unload_complete_otp != '') buildStopOtp(),
SizedBox(height: 16),
// ------------ ORDER SUMMARY -----------------
buildOrderSummary(),
SizedBox(height: 16),
// ------------ CHECKLIST -----------------
buildChecklist(),
],
),
),
);
}
// -------------------------------------------
// DROPDOWN WITH BLOCKED LOGIC
// -------------------------------------------
Widget buildTankDropdown(hasTankAlready, preSelectedTank) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(8),
),
child: Column(children: [
Row(
children: [
Expanded(
child: Text(
hasTankAlready
? "Change tank to unload water to generate OTP"
: "Choose a tank to unload water to generate OTP",
style: fontTextStyle(12, Color(0xFF444444), FontWeight.w600),
),
),
SizedBox(width: 8),
Expanded(
child: isLoading
? Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 5,
),
)
: _buildDropdownField(
hint: hasTankAlready
? widget.orderDetails.tankName
: "Select Tank",
value: selectedTank ?? preSelectedTank,
items: sumpTanks,
// 🔥 OPTION B — TANK CHANGE BLOCKED HERE
onChanged: (GetTanksDetailsModel? tank) {
if (tank == null) return;
// ❌ BLOCK AFTER UNLOADING STARTED
if (!allowTankChange) {
AppSettings.longFailedToast(
"Tank cannot be changed after unloading has started.");
return;
}
// MOTOR CHECK
bool motorOn = false;
if (tank.inConnections.isNotEmpty) {
motorOn = tank.inConnections.any(
(conn) => conn['motor_status']?.toString() == '2');
}
if (!motorOn && tank.outConnections.isNotEmpty) {
motorOn = tank.outConnections.any(
(conn) => conn['motor_status']?.toString() == '2');
}
if (motorOn) {
showMotorOnAlert(tank);
return;
}
showTankConfirmDialog(tank);
},
),
),
],
)
]),
);
}
// -------------------------------------------
// ALERT WHEN MOTOR ON
// -------------------------------------------
void showMotorOnAlert(GetTanksDetailsModel tank) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
backgroundColor: Colors.white,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Center(
child: Text(
"The motor of \"${tank.tank_name}\" is turned on. Please turn it off to start unloading.",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
textAlign: TextAlign.center,
),
),
actions: [
Center(
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF1D7AFC)),
borderRadius: BorderRadius.circular(24),
),
child: Text("Cancel",
style: fontTextStyle(
12, Color(0xFF1D7AFC), FontWeight.w600)),
),
),
),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () {
Navigator.pop(context);
setState(() => selectedTank = tank);
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Color(0xFFC03D34),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Stop Motor",
style: fontTextStyle(
12, Colors.white, FontWeight.w600),
),
),
),
),
],
),
),
],
),
);
}
// -------------------------------------------
// CONFIRM TANK CHANGE
// -------------------------------------------
void showTankConfirmDialog(GetTanksDetailsModel tank) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Center(
child: Text(
"You have selected \"${tank.tank_name}\" for water unloading. Do you confirm?",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
textAlign: TextAlign.center,
),
),
actions: [
Center(
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF1D7AFC)),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Cancel",
style: fontTextStyle(
12, Color(0xFF1D7AFC), FontWeight.w600),
),
),
),
),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () async {
await _updateTankName(tank.tank_name);
setState(() {
selectedTank = tank;
selectedTankId = tank.tank_name;
});
Navigator.pop(context);
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Color(0xFF1D7AFC),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Confirm",
style: fontTextStyle(
12, Colors.white, FontWeight.w600),
),
),
),
),
],
),
),
],
),
);
}
// -------------------------------------------
// OTP BOXES
// -------------------------------------------
Widget buildStartOtp() {
return Container(
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text("Share the OTP to start unloading",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w600))),
Expanded(child: buildOtpBoxes(otp)),
],
),
);
}
Widget buildStopOtp() {
return Container(
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text("Share the OTP to stop unloading",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w600))),
Expanded(child: buildOtpBoxes(unload_complete_otp)),
],
),
);
}
Row buildOtpBoxes(String otp) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: (otp.isNotEmpty ? otp : "----")
.split('')
.map((digit) {
return Container(
width: 28,
height: 28,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 1),
decoration: BoxDecoration(
color: Color(0xFF4692FD),
borderRadius: BorderRadius.circular(4),
),
child: Text(
digit,
style: fontTextStyle(12, Colors.white, FontWeight.w500),
),
);
}).toList(),
);
}
// -------------------------------------------
// ORDER SUMMARY
// -------------------------------------------
Widget buildOrderSummary() {
return Column(
children: [
InkWell(
onTap: () => setState(() => _showOrderSummary = !_showOrderSummary),
child: Row(
children: [
Text("View Order Summary",
style: fontTextStyle(
14, Color(0xFF1D7AFC), FontWeight.w600)),
Spacer(),
Image.asset(
_showOrderSummary
? 'images/arrow-up.png'
: 'images/arrow-down.png',
width: 24,
height: 24,
color: Color(0xFF1D7AFC),
)
],
),
),
if (_showOrderSummary)
AnimatedContainer(
duration: Duration(milliseconds: 300),
child: Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("ITEMS",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w700)),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("10,000 L Drinking water x 1",
style: fontTextStyle(
12, Color(0xFF515253), FontWeight.w400)),
Text("₹2,500",
style: fontTextStyle(
12, Color(0xFF515253), FontWeight.w400)),
],
),
Divider(),
_priceRow("Item Total", "₹2,346.00"),
_priceRow("Delivery Charges", "₹150.00"),
_priceRow("Platform Fee", "₹6.00"),
_priceRow("Taxes", "₹12.49"),
Divider(),
_priceRow("Total Bill", "₹2,514", isBold: true),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Mode of Payment",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w500)),
Row(
children: [
Image.asset('images/success-toast.png',
width: 12, height: 12),
SizedBox(width: 4),
Text("Cash on delivery",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w500)),
],
)
],
)
],
),
),
),
)
],
);
}
// -------------------------------------------
// CHECKLIST
// -------------------------------------------
Widget buildChecklist() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Arrival Checklist",
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
_buildChecklistItem(
icon: Icons.play_circle_fill,
title: "Start unloading water",
subtitle:
"The status will be updated once the delivery agent starts unloading water.",
stepIndex: 0,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
_buildChecklistItem(
icon: Icons.check_circle,
title: "Complete unloading",
subtitle:
"The status will be updated after you confirm that unloading is complete.",
stepIndex: 1,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
_buildChecklistItem(
icon: Icons.receipt_long,
title: "View your bill",
subtitle:
"The bill will be calculated after unloading is complete.",
stepIndex: 2,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
],
);
}
// -------------------------------------------
// HELPERS
// -------------------------------------------
Widget _buildDropdownField({
required String hint,
required GetTanksDetailsModel? value,
required List<GetTanksDetailsModel> items,
String? path,
required ValueChanged<GetTanksDetailsModel?> onChanged,
}) {
return DropdownButtonHideUnderline(
child: DropdownButton2<GetTanksDetailsModel>(
isExpanded: true,
hint: Row(
children: [
if (path != null && path.isNotEmpty)
Image.asset(path, width: 16, height: 16),
SizedBox(width: 6),
Text(hint,
style: fontTextStyle(
14, Color(0XFF939495), FontWeight.w400)),
],
),
value: value,
items: items.map((tank) {
return DropdownMenuItem(
value: tank,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Text(tank.tank_name,
style: fontTextStyle(
14, Color(0xFF2D2E30), FontWeight.w400)),
),
);
}).toList(),
onChanged: onChanged,
buttonStyleData: ButtonStyleData(
height: 48,
padding: EdgeInsets.only(left: 0, right: 12),
decoration: BoxDecoration(
border: Border.all(color: Color(0XFF939495)),
borderRadius: BorderRadius.circular(12),
),
),
iconStyleData: IconStyleData(
icon: Padding(
padding: EdgeInsets.only(right: 4),
child: Image.asset('images/arrow_down.png',
width: 16, height: 16, color: Color(0XFF939495)),
),
),
),
);
}
}
Widget _buildChecklistItem({
required IconData icon,
required String title,
required String subtitle,
required int stepIndex,
required int currentStep,
required VoidCallback onTap,
required String? selectedTankId,
}) {
bool isCompleted = currentStep > stepIndex;
bool isActive = currentStep == stepIndex;
// 🔥 Only allow clicking when this is the ACTIVE step AND it is step 2
bool isClickable = (stepIndex == 2 && isActive);
return GestureDetector(
onTap: isClickable ? onTap : null,
child: Opacity(
opacity: 1,
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Icon(
(isActive || isCompleted)
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: (isActive || isCompleted) ? Colors.blue : Colors.grey,
),
if (stepIndex < 2)
Container(
width: 2,
height: 50,
color: isCompleted ? Colors.blue : Colors.grey[300],
),
],
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF5F7FA),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
icon,
color: isCompleted ? Colors.blue : Colors.grey,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: isCompleted ? Colors.black : Colors.grey,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(fontSize: 12, color: Colors.black54),
),
],
),
),
],
),
),
)
],
),
),
),
);
}
Widget _priceRow(String label, String amount, {bool isBold = false}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400)),
Text(amount,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400)),
],
),
);
}
// ------------- FULL FILE ENDS HERE ---------------