master
Sneha 1 day ago
parent fb8777b7f8
commit 92e704a1b4

@ -649,6 +649,21 @@ class _OrderCardState extends State<OrderCard>{
return "completed";
}
/// delivery flow statuses
if(
s=='in_progress' ||
s=='pickup_started' ||
s=='start_loading' ||
s=='loading_completed' ||
s=='out_for_delivery' ||
s=='arrived' ||
s=='unloading_started' ||
s=='unloading_stopped' ||
s=='payment_pending'
){
return s.replaceAll('_',' ');
}
return s;
}
@ -656,13 +671,17 @@ class _OrderCardState extends State<OrderCard>{
/// ACTION RULES
bool get canAssign{
if(isExpired){
if(orderDay == null){
return false;
}
String s =
widget.order.status.toLowerCase();
if(orderDay!.isBefore(today)){
return false;
}
String s = widget.order.status.toLowerCase();
/// only before delivery starts
return s=='advance_paid' ||
s=='accepted';
@ -670,25 +689,49 @@ class _OrderCardState extends State<OrderCard>{
bool get canCancel{
if(isExpired){
if(orderDay == null){
return false;
}
String s =
widget.order.status.toLowerCase();
if(orderDay!.isBefore(today)){
return false;
}
String s = widget.order.status.toLowerCase();
return s=='advance_paid' ||
s=='accepted';
s=='accepted' ||
s=='in_progress' ||
s=='pickup_started' ||
s=='start_loading' ||
s=='loading_completed' ||
s=='out_for_delivery' ||
s=='arrived' ||
s=='payment_pending';
}
bool get canTrack{
if(isExpired){
if(orderDay == null){
return false;
}
return normalizedStatus=='in_progress';
if(orderDay!.isBefore(today)){
return false;
}
String s = widget.order.status.toLowerCase();
return s=='in_progress' ||
s=='pickup_started' ||
s=='start_loading' ||
s=='loading_completed' ||
s=='out_for_delivery' ||
s=='arrived' ||
s=='unloading_started' ||
s=='unloading_stopped' ||
s=='payment_pending';
}
@ -746,7 +789,15 @@ class _OrderCardState extends State<OrderCard>{
case "completed":
return Color(0XFFC4E8C3);
case "in_progress":
case "in progress":
case "pickup started":
case "start loading":
case "loading completed":
case "out for delivery":
case "arrived":
case "unloading started":
case "unloading stopped":
case "payment pending":
return Color(0XFFC9DFFE);
case "cancelled":
@ -759,7 +810,7 @@ class _OrderCardState extends State<OrderCard>{
return Color(0XFFFDF3D3);
default:
return Colors.grey;
return Colors.grey.shade200;
}
@ -776,7 +827,15 @@ class _OrderCardState extends State<OrderCard>{
case "completed":
return Color(0XFF0A9E04);
case "in_progress":
case "in progress":
case "pickup started":
case "start loading":
case "loading completed":
case "out for delivery":
case "arrived":
case "unloading started":
case "unloading stopped":
case "payment pending":
return Color(0XFF1D7AFC);
case "cancelled":
@ -789,7 +848,7 @@ class _OrderCardState extends State<OrderCard>{
return Color(0XFFD0AE3C);
default:
return Colors.grey;
return Colors.black87;
}
@ -1043,7 +1102,7 @@ class _OrderCardState extends State<OrderCard>{
/// ASSIGN CANCEL
Visibility(
visible:canAssign || canCancel,
visible:canAssign,
child:Row(
@ -1205,8 +1264,9 @@ class _OrderCardState extends State<OrderCard>{
MaterialPageRoute(
builder:(context)=>
AssignDriverScreen(
order:widget.order
DeliveryUpdatesPage(
orderId:widget.order.dbId,
initialStatus:widget.order.status
)
)

@ -5,6 +5,8 @@ import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/resources/source_loctaions_model.dart';
import '../resources/drivers_model.dart';
import '../resources/tankers_model.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class AssignDriverScreen extends StatefulWidget {
var order;
@ -1018,6 +1020,42 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
);
}
void openDistanceMap() {
double supplierLat =
double.tryParse(AppSettings.supplierLatitude.toString()) ?? 0;
double supplierLng =
double.tryParse(AppSettings.supplierLongitude.toString()) ?? 0;
double userLat =
double.tryParse(widget.order.lat.toString()) ?? 0;
double userLng =
double.tryParse(widget.order.lng.toString()) ?? 0;
String googleUrl =
"https://www.google.com/maps/dir/?api=1"
"&origin=$supplierLat,$supplierLng"
"&destination=$userLat,$userLng"
"&travelmode=driving";
launchUrl(Uri.parse(googleUrl));
}
void callDriver(String phone){
if(phone.isEmpty){
return;
}
launchUrl(
Uri.parse("tel:$phone")
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -1145,11 +1183,78 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
],
),
const Spacer(),
Text(
/* Text(
widget.order.distanceInKm.toString() + 'Km',
style: fontTextStyle(
12, const Color(0XFF939495), FontWeight.w400),
),
),*/
Row(
children:[
Text(
"${widget.order.distanceInKm} Km",
style: fontTextStyle(
12,
Color(0XFF939495),
FontWeight.w400
),
),
/*SizedBox(width:4),
Text(
"(${((widget.order.distanceInKm)*1000).toInt()} m)",
style: fontTextStyle(
10,
Color(0XFF8270DB),
FontWeight.w500
),
),
*/
SizedBox(width:6),
GestureDetector(
onTap:openDistanceMap,
child:Image.asset(
'images/maps.png',
height:18,
width:18,
color:Color(0XFF8270DB),
),
),
SizedBox(width:6),
GestureDetector(
onTap:(){
callDriver(widget.order.phone);
},
child:Image.asset(
'images/phone_icon.png',
height:20,
width:20,
color:Color(0XFF8270DB),
),
),
],
)
],
),
),

@ -1,131 +1,877 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
import 'package:supplier_new/common/settings.dart';
class DeliveryUpdatesPage extends StatefulWidget {
final String orderId;
final String initialStatus;
const DeliveryUpdatesPage({super.key, required this.orderId, required this.initialStatus});
const DeliveryUpdatesPage({
super.key,
required this.orderId,
required this.initialStatus,
});
@override
State<DeliveryUpdatesPage> createState() => _DeliveryUpdatesPageState();
}
class _DeliveryUpdatesPageState extends State<DeliveryUpdatesPage> {
List<String> statuses = [
"Tanker reached source",
"Water filling started",
"Water filling completed",
"Tanker started to customer location",
"Offloading water started",
"Offloading water completed",
"Payment completed",
"Delivery completed"
];
int currentStep = 0;
GoogleMapController? _mapController;
Timer? _timer;
final PolylinePoints _polylinePoints = PolylinePoints();
/// ---------------- CHANGE THIS ----------------
/// Put your Google Maps API key here
/// Need Directions API + Distance Matrix API enabled
static const String googleApiKey = 'AIzaSyDJpK9RVhlBejtJu9xSGfneuTN6HOfJgSM';
/// --------------------------------------------
bool isLoading = true;
bool mapReady = false;
String currentStatus = '';
String etaText = '--';
String distanceText = '--';
String durationText = '--';
String driverName = '';
String driverPhone = '';
String tankerName = '';
String orderAddress = '';
double? driverLat;
double? driverLng;
double? destinationLat;
double? destinationLng;
Set<Marker> markers = {};
Set<Polyline> polylines = {};
List<LatLng> routePoints = [];
@override
void initState() {
super.initState();
// Example: Get live updates from backend (MQTT, WebSocket, Firestore, etc.)
_simulateStatusUpdates();
currentStatus = _beautifyStatus(widget.initialStatus);
_loadTracking(showLoader: true);
_timer = Timer.periodic(const Duration(seconds: 5), (_) {
_loadTracking(showLoader: false);
});
}
void _simulateStatusUpdates() async {
// This is just simulation replace with your listener
for (int i = 0; i < statuses.length; i++) {
await Future.delayed(const Duration(seconds: 3));
@override
void dispose() {
_timer?.cancel();
_mapController?.dispose();
super.dispose();
}
Future<void> _loadTracking({bool showLoader = false}) async {
if (showLoader && mounted) {
setState(() {
currentStep = i;
isLoading = true;
});
}
try {
/// ===========================
/// IMPORTANT:
/// Change this endpoint path to your real backend route
/// Example:
/// ${AppSettings.host}trackOrder/${widget.orderId}
/// ${AppSettings.host}getOrderTracking/${widget.orderId}
/// ===========================
final uri = Uri.parse(
'${AppSettings.host}trackOrder/${widget.orderId}',
);
final response = await http.get(uri);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw Exception('Tracking API failed: ${response.statusCode}');
}
final decoded = jsonDecode(response.body);
/// Supports either:
/// { status:true, data:{...} }
/// OR direct flat response
final data = decoded is Map && decoded['data'] is Map
? decoded['data'] as Map<String, dynamic>
: decoded as Map<String, dynamic>;
driverLat = _toDouble(
data['driverLat'] ??
data['driver_lat'] ??
data['deliveryBoyLat'] ??
data['deliveryboyLat'] ??
data['lat'],
);
driverLng = _toDouble(
data['driverLng'] ??
data['driver_lng'] ??
data['deliveryBoyLng'] ??
data['deliveryboyLng'] ??
data['lng'] ??
data['long'],
);
destinationLat = _toDouble(
data['destinationLat'] ??
data['destination_lat'] ??
data['orderLat'] ??
data['customerLat'] ??
data['dropLat'],
);
destinationLng = _toDouble(
data['destinationLng'] ??
data['destination_lng'] ??
data['orderLng'] ??
data['customerLng'] ??
data['dropLng'],
);
driverName = (data['driverName'] ??
data['delivery_agent_name'] ??
data['deliveryBoyName'] ??
'')
.toString();
driverPhone = (data['driverPhone'] ??
data['delivery_agent_mobile'] ??
data['deliveryBoyPhone'] ??
'')
.toString();
tankerName = (data['tankerName'] ?? data['tanker_name'] ?? '').toString();
orderAddress = (data['destinationAddress'] ??
data['displayAddress'] ??
data['address'] ??
data['customerAddress'] ??
'')
.toString();
final apiStatus =
(data['status'] ?? data['orderStatus'] ?? widget.initialStatus)
.toString();
currentStatus = _beautifyStatus(apiStatus);
final apiEta =
(data['eta'] ?? data['etaText'] ?? data['estimatedTime'] ?? '')
.toString()
.trim();
final apiDistance =
(data['distance'] ?? data['distanceText'] ?? '').toString().trim();
if (apiEta.isNotEmpty) etaText = apiEta;
if (apiDistance.isNotEmpty) distanceText = apiDistance;
if (driverLat != null &&
driverLng != null &&
destinationLat != null &&
destinationLng != null) {
await _buildMarkers();
await _fetchGoogleRouteAndEta();
await _moveCameraToBounds();
}
if (mounted) {
setState(() {
isLoading = false;
});
}
} catch (e) {
debugPrint('Tracking error: $e');
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Track Order"),
backgroundColor: const Color(0XFF0A9E04),
Future<void> _buildMarkers() async {
if (driverLat == null ||
driverLng == null ||
destinationLat == null ||
destinationLng == null) {
return;
}
markers = {
Marker(
markerId: const MarkerId('driver'),
position: LatLng(driverLat!, driverLng!),
infoWindow: InfoWindow(
title: driverName.isNotEmpty ? driverName : 'Driver',
snippet: tankerName.isNotEmpty ? tankerName : 'Live location',
),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure,
),
),
Marker(
markerId: const MarkerId('destination'),
position: LatLng(destinationLat!, destinationLng!),
infoWindow: InfoWindow(
title: 'Destination',
snippet: orderAddress.isNotEmpty ? orderAddress : 'Order location',
),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueRed,
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
};
}
Future<void> _fetchGoogleRouteAndEta() async {
if (driverLat == null ||
driverLng == null ||
destinationLat == null ||
destinationLng == null) {
return;
}
try {
final origin = '${driverLat!},${driverLng!}';
final destination = '${destinationLat!},${destinationLng!}';
final directionsUrl = Uri.parse(
'https://maps.googleapis.com/maps/api/directions/json'
'?origin=$origin'
'&destination=$destination'
'&departure_time=now'
'&traffic_model=best_guess'
'&mode=driving'
'&key=$googleApiKey',
);
final directionsResponse = await http.get(directionsUrl);
final directionsData = jsonDecode(directionsResponse.body);
if (directionsData['routes'] != null &&
(directionsData['routes'] as List).isNotEmpty) {
final route = directionsData['routes'][0];
final overviewPolyline =
route['overview_polyline']?['points']?.toString() ?? '';
final legs = route['legs'] as List?;
if (legs != null && legs.isNotEmpty) {
final leg = legs[0];
final distanceObj = leg['distance'];
final durationObj = leg['duration'];
final durationTrafficObj = leg['duration_in_traffic'];
if (distanceObj != null && (distanceText == '--' || distanceText.isEmpty)) {
distanceText = (distanceObj['text'] ?? '--').toString();
}
if (durationObj != null) {
durationText = (durationObj['text'] ?? '--').toString();
}
if (durationTrafficObj != null) {
etaText = (durationTrafficObj['text'] ?? '--').toString();
} else if (durationObj != null && (etaText == '--' || etaText.isEmpty)) {
etaText = (durationObj['text'] ?? '--').toString();
}
}
final result = _polylinePoints.decodePolyline(overviewPolyline);
routePoints = result
.map((p) => LatLng(p.latitude, p.longitude))
.toList();
polylines = {
Polyline(
polylineId: const PolylineId('delivery_route'),
points: routePoints,
width: 5,
color: const Color(0XFF8270DB),
),
};
} else {
_setStraightLinePolyline();
_setApproxEtaIfNeeded();
}
} catch (e) {
debugPrint('Directions error: $e');
_setStraightLinePolyline();
_setApproxEtaIfNeeded();
}
}
void _setStraightLinePolyline() {
if (driverLat == null ||
driverLng == null ||
destinationLat == null ||
destinationLng == null) {
return;
}
polylines = {
Polyline(
polylineId: const PolylineId('delivery_route_fallback'),
points: [
LatLng(driverLat!, driverLng!),
LatLng(destinationLat!, destinationLng!),
],
width: 5,
color: const Color(0XFF8270DB),
),
};
}
void _setApproxEtaIfNeeded() {
if (driverLat == null ||
driverLng == null ||
destinationLat == null ||
destinationLng == null) {
return;
}
final km = _calculateDistanceKm(
driverLat!,
driverLng!,
destinationLat!,
destinationLng!,
);
if (distanceText == '--' || distanceText.isEmpty) {
distanceText = '${km.toStringAsFixed(1)} km';
}
/// Rough city driving estimate at 25 km/h
final mins = ((km / 25) * 60).ceil().clamp(1, 999);
if (etaText == '--' || etaText.isEmpty) {
etaText = '$mins mins';
}
if (durationText == '--' || durationText.isEmpty) {
durationText = '$mins mins';
}
}
Future<void> _moveCameraToBounds() async {
if (!mapReady || _mapController == null) return;
if (driverLat == null ||
driverLng == null ||
destinationLat == null ||
destinationLng == null) {
return;
}
final southwest = LatLng(
min(driverLat!, destinationLat!),
min(driverLng!, destinationLng!),
);
final northeast = LatLng(
max(driverLat!, destinationLat!),
max(driverLng!, destinationLng!),
);
final bounds = LatLngBounds(
southwest: southwest,
northeast: northeast,
);
try {
await _mapController!.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 70),
);
} catch (_) {
await Future.delayed(const Duration(milliseconds: 300));
try {
await _mapController!.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 70),
);
} catch (_) {}
}
}
double _calculateDistanceKm(
double startLat,
double startLng,
double endLat,
double endLng,
) {
const double earthRadius = 6371;
final dLat = _degToRad(endLat - startLat);
final dLng = _degToRad(endLng - startLng);
final a = sin(dLat / 2) * sin(dLat / 2) +
cos(_degToRad(startLat)) *
cos(_degToRad(endLat)) *
sin(dLng / 2) *
sin(dLng / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c;
}
double _degToRad(double deg) => deg * pi / 180;
double? _toDouble(dynamic val) {
if (val == null) return null;
if (val is double) return val;
if (val is int) return val.toDouble();
return double.tryParse(val.toString());
}
String _beautifyStatus(String s) {
final value = s.trim().toLowerCase();
if (value == 'advance_paid' || value == 'accepted') {
return 'Pending';
}
if (value == 'deliveryboy_assigned' || value == 'tanker_assigned') {
return 'Assigned';
}
if (value == 'delivered') {
return 'Completed';
}
return value
.replaceAll('_', ' ')
.split(' ')
.map((e) => e.isEmpty
? e
: '${e[0].toUpperCase()}${e.substring(1)}')
.join(' ');
}
int _statusStepIndex() {
final raw = widget.initialStatus.trim().toLowerCase();
final live = currentStatus.trim().toLowerCase().replaceAll(' ', '_');
final s = live.isNotEmpty ? live : raw;
switch (s) {
case 'accepted':
case 'advance_paid':
return 0;
case 'deliveryboy_assigned':
case 'tanker_assigned':
return 1;
case 'pickup_started':
return 2;
case 'start_loading':
case 'loading_completed':
return 3;
case 'out_for_delivery':
case 'arrived':
return 4;
case 'unloading_started':
case 'unloading_stopped':
case 'payment_pending':
return 5;
case 'delivered':
return 6;
default:
return 0;
}
}
Future<void> _callDriver() async {
if (driverPhone.trim().isEmpty) return;
final uri = Uri.parse('tel:$driverPhone');
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
Widget _buildTopMapCard() {
final initialTarget = LatLng(
driverLat ?? 17.385,
driverLng ?? 78.486,
);
return Expanded(
child: Stack(
children: [
GoogleMap(
initialCameraPosition: CameraPosition(
target: initialTarget,
zoom: 14,
),
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
compassEnabled: true,
trafficEnabled: true,
markers: markers,
polylines: polylines,
onMapCreated: (controller) async {
_mapController = controller;
mapReady = true;
await _moveCameraToBounds();
},
),
Positioned(
top: 14,
left: 14,
right: 14,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 10,
offset: const Offset(0, 3),
)
],
),
child: Row(
children: [
_miniMetric('ETA', etaText),
const SizedBox(width: 10),
_miniMetric('Distance', distanceText),
const SizedBox(width: 10),
_miniMetric('Status', currentStatus),
],
),
),
),
Positioned(
right: 14,
bottom: 14,
child: FloatingActionButton(
heroTag: 'fit_map_btn',
backgroundColor: const Color(0XFF8270DB),
onPressed: _moveCameraToBounds,
child: const Icon(Icons.center_focus_strong, color: Colors.white),
),
),
],
),
);
}
Widget _miniMetric(String title, String value) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
color: const Color(0XFFF7F7FB),
borderRadius: BorderRadius.circular(14),
),
child: Column(
children: [
Text(
title,
style: const TextStyle(
fontSize: 11,
color: Color(0XFF777777),
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 6),
Text(
value.isEmpty ? '--' : value,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 12,
color: Color(0XFF2D2E30),
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
Widget _buildBottomPanel() {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 18),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(26)),
),
child: SingleChildScrollView(
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: statuses.length,
itemBuilder: (context, index) {
final isCompleted = index <= currentStep;
Container(
width: 44,
height: 5,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
),
),
const SizedBox(height: 16),
return Row(
Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: const Color(0XFFEEE9FF),
child: const Icon(
Icons.person,
color: Color(0XFF8270DB),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
// Circle with tanker icon or checkmark
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: isCompleted ? Colors.green : Colors.grey[300],
shape: BoxShape.circle,
),
child: Center(
child: index == currentStep
? const Icon(Icons.local_shipping)
: Icon(
isCompleted
? Icons.check
: Icons.circle_outlined,
size: 16,
color: isCompleted ? Colors.white : Colors.grey,
),
),
),
if (index != statuses.length - 1)
Container(
width: 4,
height: 50,
color: index < currentStep ? Colors.green : Colors.grey[300],
),
],
Text(
driverName.isNotEmpty ? driverName : 'Driver not assigned',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Color(0XFF2D2E30),
),
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
statuses[index],
style: TextStyle(
fontSize: 14,
fontWeight: isCompleted ? FontWeight.w600 : FontWeight.w400,
color: isCompleted ? Colors.black : Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
tankerName.isNotEmpty
? tankerName
: 'Delivery partner',
style: const TextStyle(
fontSize: 13,
color: Color(0XFF6A6B6D),
fontWeight: FontWeight.w500,
),
)
),
],
);
},
),
),
),
InkWell(
onTap: driverPhone.trim().isEmpty ? null : _callDriver,
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: driverPhone.trim().isEmpty
? Colors.grey.shade300
: const Color(0XFF8270DB),
borderRadius: BorderRadius.circular(16),
),
child: const Row(
children: [
Icon(Icons.call, color: Colors.white, size: 16),
SizedBox(width: 6),
Text(
'Call',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
],
),
),
)
],
),
// 🔸 Current status shown at bottom
const SizedBox(height: 16),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
color: const Color(0XFFF7F7FB),
borderRadius: BorderRadius.circular(18),
),
child: Text(
"Current Status: ${statuses[currentStep]}",
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
child: Column(
children: [
_infoRow('Order ID', widget.orderId),
const SizedBox(height: 10),
_infoRow('Current Status', currentStatus),
const SizedBox(height: 10),
_infoRow('ETA to Destination', etaText),
const SizedBox(height: 10),
_infoRow('Remaining Distance', distanceText),
if (orderAddress.trim().isNotEmpty) ...[
const SizedBox(height: 10),
_infoRow('Destination', orderAddress),
],
],
),
),
const SizedBox(height: 18),
_buildTimeline(),
],
),
),
);
}
}
Widget _infoRow(String title, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 130,
child: Text(
title,
style: const TextStyle(
fontSize: 13,
color: Color(0XFF6B6C6E),
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value.isEmpty ? '--' : value,
style: const TextStyle(
fontSize: 13,
color: Color(0XFF222222),
fontWeight: FontWeight.w700,
),
),
),
],
);
}
Widget _buildTimeline() {
final currentIndex = _statusStepIndex();
final steps = [
'Order Confirmed',
'Driver Assigned',
'Pickup Started',
'Loading',
'Out for Delivery',
'Unloading',
'Completed',
];
return Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0XFFF7F7FB),
borderRadius: BorderRadius.circular(18),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Delivery Progress',
style: TextStyle(
fontSize: 15,
color: Color(0XFF2D2E30),
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 14),
...List.generate(steps.length, (index) {
final done = index <= currentIndex;
final isLast = index == steps.length - 1;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: done
? const Color(0XFF8270DB)
: Colors.grey.shade300,
shape: BoxShape.circle,
),
child: done
? const Icon(Icons.check,
size: 12, color: Colors.white)
: null,
),
if (!isLast)
Container(
width: 2,
height: 28,
color: done
? const Color(0XFF8270DB)
: Colors.grey.shade300,
),
],
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 1),
child: Text(
steps[index],
style: TextStyle(
fontSize: 14,
fontWeight: done ? FontWeight.w700 : FontWeight.w500,
color: done
? const Color(0XFF2D2E30)
: const Color(0XFF8A8B8D),
),
),
),
),
],
);
}),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0XFFF2F2F2),
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
iconTheme: const IconThemeData(color: Color(0XFF2D2E30)),
title: const Text(
'Track Delivery',
style: TextStyle(
color: Color(0XFF2D2E30),
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
),
body: isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
_buildTopMapCard(),
SizedBox(
height: MediaQuery.of(context).size.height * 0.40,
child: _buildBottomPanel(),
),
],
),
);
}
}

@ -5,6 +5,7 @@ import 'package:geolocator/geolocator.dart';
class OrdersModel {
String building_name = '';
String phone = '';
String address = '';
String type_of_water = '';
String capacity = '';
@ -32,6 +33,7 @@ class OrdersModel {
OrdersModel rtvm = new OrdersModel();
rtvm.building_name = json['buildingName'] ?? '';
rtvm.phone = json['customerPhone'] ?? '';
rtvm.dbId = json['_id']?? '';
rtvm.address = json['address'] ?? '';
rtvm.type_of_water = json['typeofwater'] ?? '';

Loading…
Cancel
Save