951 lines
30 KiB
Dart
951 lines
30 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:get/get_core/src/get_main.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../../../Controller/map_controller.dart';
|
|
import '../../../Globalwidgets/textwidget.dart';
|
|
import '../../../Helper/Constants/Colorconstants.dart';
|
|
import '../../../Helper/utility.dart';
|
|
import '../../../Model/Response/Summary/Getsummarysresponse.dart';
|
|
|
|
|
|
|
|
class MapWithBottomSheetPage extends StatelessWidget {
|
|
|
|
final DeliveriesDetails data;
|
|
|
|
MapWithBottomSheetPage({super.key, required this.data});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
MapWithPolylines(
|
|
endLatLng: LatLng(
|
|
double.tryParse(data.deliverylat ?? '') ?? 0.0,
|
|
double.tryParse(data.deliverylong ?? '') ?? 0.0,
|
|
),
|
|
startLatLng: LatLng(
|
|
double.tryParse(data.pickuplat ?? '') ?? 0.0,
|
|
double.tryParse(data.pickuplon ?? '') ?? 0.0,
|
|
),
|
|
),
|
|
DraggableScrollableSheet(
|
|
initialChildSize: 0.22,
|
|
minChildSize: 0.12,
|
|
maxChildSize: 0.65,
|
|
builder: (context, scrollController) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
spreadRadius: 2,
|
|
),
|
|
],
|
|
),
|
|
child: SingleChildScrollView(
|
|
controller: scrollController,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 5,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[300],
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
CircleAvatar(
|
|
backgroundColor: ColorConstants.primaryColor,
|
|
radius: 30,
|
|
child: TextWidget(
|
|
text: data.ridername?[0] ?? '',
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 20,
|
|
color: ColorConstants.secondaryColor,
|
|
),
|
|
),
|
|
SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
data.ridername ?? '',
|
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
|
),
|
|
Text("Order ID: ${data.orderid}"),
|
|
Text("Status: ${data.orderstatus}", style: TextStyle(color: Colors.green)),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.phone, color: ColorConstants.primaryColor),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 20),
|
|
Divider(),
|
|
ListTile(
|
|
title: Text("Pickup Location"),
|
|
subtitle: Text(data.pickupaddress ?? ''),
|
|
leading: Icon(Icons.location_on, color: Colors.red),
|
|
),
|
|
SizedBox(height: 10,),
|
|
ListTile(
|
|
title: Text("Drop-off Location"),
|
|
subtitle: Text(data.deliveryaddress ?? ''),
|
|
leading: Icon(Icons.flag, color: Colors.green),
|
|
),
|
|
SizedBox(height: 18),
|
|
Divider(
|
|
height: 1,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(height: 18),
|
|
TextWidget(
|
|
text: 'Order Details',
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
),
|
|
SizedBox(height: 10,),
|
|
Row(
|
|
children: [
|
|
TextWidget(
|
|
text: 'Delivery Charges',
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
Spacer(),
|
|
TextWidget(
|
|
text: data.deliverycharges.toString(),
|
|
fontWeight: FontWeight.w700,
|
|
)
|
|
],
|
|
),
|
|
SizedBox(height: 10,),
|
|
Row(
|
|
children: [
|
|
TextWidget(
|
|
text: 'Total Amount',
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
Spacer(),
|
|
TextWidget(
|
|
text: data.deliveryamt.toString(),
|
|
fontWeight: FontWeight.w700,
|
|
)
|
|
],
|
|
),
|
|
SizedBox(height: 18),
|
|
Divider(
|
|
height: 1,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(height: 18),
|
|
TextWidget(
|
|
text: 'Payment Details',
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
),
|
|
SizedBox(height: 10,),
|
|
Row(
|
|
children: [
|
|
TextWidget(
|
|
text: data.deliverycustomer ?? '',
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
Spacer(),
|
|
TextWidget(
|
|
text: data.deliverycontactno.toString(),
|
|
fontWeight: FontWeight.w700,
|
|
)
|
|
],
|
|
),
|
|
SizedBox(height: 18),
|
|
Divider(
|
|
height: 1,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(height: 18),
|
|
TextWidget(
|
|
text: 'Payment method',
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
),
|
|
SizedBox(height: 10,),
|
|
TextWidget(
|
|
text: 'Cash on delivery',
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// class SummaryDetailsView extends StatelessWidget {
|
|
// final DeliveriesDetails data;
|
|
//
|
|
// const SummaryDetailsView({super.key, required this.data});
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// return Scaffold(
|
|
// appBar: _buildAppBar(),
|
|
// backgroundColor: Colors.grey[100],
|
|
// body: _buildBody(context),
|
|
// );
|
|
// }
|
|
//
|
|
// /// Builds the app bar with a back button and title.
|
|
// AppBar _buildAppBar() {
|
|
// return AppBar(
|
|
// leading: InkWell(
|
|
// onTap: Get.back,
|
|
// child: Icon(
|
|
// Icons.arrow_back,
|
|
// color: ColorConstants.blackColor,
|
|
// size: 28,
|
|
// ),
|
|
// ),
|
|
// title: TextWidget(
|
|
// text: 'Delivery Details',
|
|
// fontSize: 20,
|
|
// fontWeight: FontWeight.w700,
|
|
// ),
|
|
// backgroundColor: ColorConstants.secondaryColor,
|
|
// );
|
|
// }
|
|
//
|
|
// /// Builds the scrollable body with delivery details and map.
|
|
// Widget _buildBody(BuildContext context) {
|
|
// return SingleChildScrollView(
|
|
// padding: const EdgeInsets.all(0.0),
|
|
// child: Column(
|
|
// children: [
|
|
// Padding(
|
|
// padding: const EdgeInsets.only(left: 0,right: 0),
|
|
// child: SizedBox(
|
|
// height: Get.height * 0.60,
|
|
// child: MapWithPolylines(
|
|
// endLatLng: LatLng(
|
|
// double.tryParse(data.deliverylat ?? '') ?? 0.0,
|
|
// double.tryParse(data.deliverylong ?? '') ?? 0.0,
|
|
// ),
|
|
// startLatLng: LatLng(
|
|
// double.tryParse(data.pickuplat ?? '') ?? 0.0,
|
|
// double.tryParse(data.pickuplon ?? '') ?? 0.0,
|
|
// ),
|
|
// )
|
|
// ),
|
|
// ),
|
|
// const SizedBox(height: 10),
|
|
// DeliveryCard(data: data, isFromSummary: true,),
|
|
// const SizedBox(height: 20,)
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
/// A reusable card widget displaying delivery header information (date, tenant, order).
|
|
class DeliveryHeaderCard extends StatelessWidget {
|
|
final DeliveriesDetails data;
|
|
|
|
const DeliveryHeaderCard({super.key, required this.data});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
color: ColorConstants.secondaryColor,
|
|
margin: const EdgeInsets.symmetric(horizontal: 5),
|
|
height: 95,
|
|
child: Row(
|
|
children: [
|
|
_buildDateSection(),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
flex: 3,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
IconTextRow(
|
|
icon: Icons.person,
|
|
text: data.tenantname ?? '',
|
|
textStyle: _textStyle(15, Colors.grey[500]),
|
|
),
|
|
IconTextRow(
|
|
icon: Icons.receipt,
|
|
text: data.orderid ?? '',
|
|
textStyle: _textStyle(13, Colors.black54),
|
|
),
|
|
IconTextRow(
|
|
icon: Icons.phone,
|
|
text: data.tenantcontactno ?? '',
|
|
textStyle: _textStyle(13, Colors.black54),
|
|
onTap: () => _launchPhone(data.tenantcontactno),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Builds the date section with day, month, and time.
|
|
Widget _buildDateSection() {
|
|
final date = DateFormat("yyyy-MM-dd", "en_US").parse(data.deliverydate ?? DateTime.now().toString());
|
|
return Container(
|
|
width: 75,
|
|
decoration: BoxDecoration(
|
|
color: ColorConstants.primaryColor1,
|
|
shape: BoxShape.rectangle,
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white70,
|
|
borderRadius: BorderRadius.circular(5),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
DateFormat("dd").format(date),
|
|
style: _textStyle(14, Colors.grey[700]),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
DateFormat("MMM").format(date),
|
|
style: _textStyle(14, Colors.grey[700], height: 1),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
DateFormat("hh.mm a").format(date),
|
|
style: _textStyle(11, Colors.grey[700]),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Helper method to create consistent text styles.
|
|
TextStyle _textStyle(double fontSize, Color? color, {double? height}) {
|
|
return TextStyle(
|
|
fontSize: fontSize,
|
|
color: color,
|
|
fontWeight: FontWeight.w600,
|
|
height: height,
|
|
);
|
|
}
|
|
|
|
/// Launches phone dialer with the provided number.
|
|
void _launchPhone(String? number) {
|
|
if (number != null && number.isNotEmpty) {
|
|
launch('tel://$number');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A reusable card widget for displaying pickup or delivery location details.
|
|
class DeliveryLocationCard extends StatelessWidget {
|
|
final String title;
|
|
final String address;
|
|
final String contact;
|
|
final double lat;
|
|
final double lon;
|
|
final Color iconColor;
|
|
final bool isPickup;
|
|
|
|
const DeliveryLocationCard({
|
|
super.key,
|
|
required this.title,
|
|
required this.address,
|
|
required this.contact,
|
|
required this.lat,
|
|
required this.lon,
|
|
required this.iconColor,
|
|
required this.isPickup,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
color: Colors.red[100]!.withAlpha(100),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(13)),
|
|
child: SizedBox(
|
|
height: 150,
|
|
width: MediaQuery.of(context).size.width,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
const SizedBox(height: 15),
|
|
_buildMarkerIcon(),
|
|
const Spacer(),
|
|
_buildNavigationIcon(),
|
|
const SizedBox(height: 10),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 14,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(5.0),
|
|
child: Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 10),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 15),
|
|
IconTextRow(
|
|
icon: isPickup ? Icons.business : Icons.person,
|
|
text: title,
|
|
textStyle: const TextStyle(color: Colors.black87, fontSize: 15),
|
|
),
|
|
const SizedBox(height: 10),
|
|
IconTextRow(
|
|
icon: Icons.location_on_rounded,
|
|
text: address,
|
|
textStyle: const TextStyle(
|
|
color: Colors.black87,
|
|
fontSize: 13,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
maxLines: 2,
|
|
),
|
|
const SizedBox(height: 15),
|
|
IconTextRow(
|
|
icon: Icons.phone,
|
|
text: contact,
|
|
textStyle: const TextStyle(color: Colors.black87, fontSize: 13),
|
|
onTap: () => Utility.openPhoneCallApp(contact),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Builds the marker icon for the location.
|
|
Widget _buildMarkerIcon() {
|
|
return Container(
|
|
height: 40,
|
|
width: 40,
|
|
decoration: const BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Colors.white,
|
|
),
|
|
child: FaIcon(
|
|
FontAwesomeIcons.mapMarkerAlt,
|
|
size: 22,
|
|
color: iconColor,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Builds the navigation icon with a tap action to open the map.
|
|
Widget _buildNavigationIcon() {
|
|
return InkWell(
|
|
onTap: () => Utility.openMap(lat, lon),
|
|
child: Container(
|
|
height: 40,
|
|
width: 40,
|
|
decoration: const BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Colors.white,
|
|
),
|
|
child: const Icon(
|
|
Icons.assistant_direction_rounded,
|
|
size: 30,
|
|
color: ColorConstants.primaryColor,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A reusable widget for displaying an icon and text with an optional tap action.
|
|
class IconTextRow extends StatelessWidget {
|
|
final IconData icon;
|
|
final String text;
|
|
final TextStyle textStyle;
|
|
final int? maxLines;
|
|
final VoidCallback? onTap;
|
|
|
|
const IconTextRow({
|
|
super.key,
|
|
required this.icon,
|
|
required this.text,
|
|
required this.textStyle,
|
|
this.maxLines,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 14, color: Colors.black38),
|
|
const SizedBox(width: 4),
|
|
Flexible(
|
|
child: Text(
|
|
text,
|
|
style: textStyle,
|
|
maxLines: maxLines,
|
|
overflow: maxLines != null ? TextOverflow.ellipsis : null,
|
|
),
|
|
),
|
|
if (maxLines != null) const SizedBox(width: 5),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Map with polyLines
|
|
class MapWithPolylines extends StatelessWidget {
|
|
final LatLng startLatLng;
|
|
final LatLng endLatLng;
|
|
|
|
MapWithPolylines({
|
|
required this.startLatLng,
|
|
required this.endLatLng,
|
|
Key? key,
|
|
}) : super(key: key);
|
|
|
|
final mapController = Get.put(MapController());
|
|
|
|
Set<Polyline> _createPolylines() {
|
|
return {
|
|
Polyline(
|
|
polylineId: const PolylineId('route1'),
|
|
visible: true,
|
|
points: [startLatLng, endLatLng],
|
|
color: Colors.blue,
|
|
width: 5,
|
|
),
|
|
};
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Stack(
|
|
children: [
|
|
Obx(() => GoogleMap(
|
|
initialCameraPosition: CameraPosition(
|
|
target: startLatLng,
|
|
zoom: mapController.zoom.value,
|
|
),
|
|
polylines: _createPolylines(),
|
|
markers: {
|
|
Marker(markerId: const MarkerId("start"), position: startLatLng),
|
|
Marker(markerId: const MarkerId("end"), position: endLatLng),
|
|
},
|
|
onMapCreated: (controller) {
|
|
mapController.setController(controller);
|
|
},
|
|
)),
|
|
Positioned(
|
|
top: 20,
|
|
right: 10,
|
|
child: Column(
|
|
children: [
|
|
FloatingActionButton(
|
|
mini: true,
|
|
backgroundColor: Colors.white,
|
|
onPressed: mapController.zoomIn,
|
|
child: const Icon(Icons.add, color: Colors.black),
|
|
),
|
|
const SizedBox(height: 8),
|
|
FloatingActionButton(
|
|
mini: true,
|
|
backgroundColor: Colors.white,
|
|
onPressed: mapController.zoomOut,
|
|
child: const Icon(Icons.remove, color: Colors.black),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 20,
|
|
left: 10,
|
|
child: FloatingActionButton(
|
|
mini: true,
|
|
backgroundColor: Colors.white,
|
|
child: Icon(Icons.arrow_back, color: Colors.black),
|
|
onPressed: () {
|
|
Get.back();
|
|
}
|
|
)
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class DeliveryInfoCard extends StatelessWidget {
|
|
final String deliveryDate;
|
|
final String tenantName;
|
|
final String orderId;
|
|
final String contactNumber;
|
|
final VoidCallback onPhoneTap;
|
|
final Color primaryColor;
|
|
|
|
const DeliveryInfoCard({
|
|
super.key,
|
|
required this.deliveryDate,
|
|
required this.tenantName,
|
|
required this.orderId,
|
|
required this.contactNumber,
|
|
required this.onPhoneTap,
|
|
required this.primaryColor,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final date = DateFormat("yyyy-MM-dd").parse(deliveryDate);
|
|
final dateTime = DateFormat("yyyy-MM-ddTHH:mm:ss").parse(deliveryDate);
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 6,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: SizedBox(
|
|
height: 100,
|
|
child: Row(
|
|
children: [
|
|
/// LEFT SIDE: DATE
|
|
Container(
|
|
width: 80,
|
|
decoration: BoxDecoration(
|
|
color: primaryColor,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(12),
|
|
bottomLeft: Radius.circular(12),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
DateFormat("dd").format(date),
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
DateFormat("MMM").format(date),
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[600],
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
DateFormat("hh:mm a").format(dateTime),
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
/// RIGHT SIDE: DETAILS
|
|
Expanded(
|
|
flex: 3,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildIconText(Icons.person, tenantName, 15),
|
|
_buildIconText(Icons.receipt, orderId, 13),
|
|
InkWell(
|
|
onTap: onPhoneTap,
|
|
child: _buildIconText(Icons.phone, contactNumber, 13, isLink: true),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildIconText(IconData icon, String text, double fontSize, {bool isLink = false}) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, color: Colors.black45, size: 18),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Text(
|
|
text,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: fontSize,
|
|
color: isLink ? Colors.blue : Colors.black87,
|
|
fontWeight: FontWeight.w500,
|
|
decoration: isLink ? TextDecoration.underline : TextDecoration.none,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class DeliveryCard extends StatelessWidget {
|
|
final dynamic data;
|
|
final bool isFromSummary;
|
|
|
|
const DeliveryCard({super.key, required this.data, this.isFromSummary = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
margin: const EdgeInsets.all(12),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
elevation: 0,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
/// Timeline: Pickup icon, dotted line, Drop icon
|
|
SizedBox(
|
|
height: 190, // <-- match the combined height of your content
|
|
child: Column(
|
|
children: [
|
|
_circleIcon(FontAwesomeIcons.mapMarkerAlt as IconData, isFromSummary ? Colors.red : Colors.green),
|
|
Expanded(child: _verticalDottedLine()),
|
|
_circleIcon((isFromSummary ? FontAwesomeIcons.checkCircle : FontAwesomeIcons.mapMarkerAlt) as IconData, isFromSummary ? Colors.green : Colors.red),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
|
|
/// Content Block
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildInfoBlock(
|
|
label: "Pickup",
|
|
name: data?.pickupcustomer ?? '',
|
|
address: data?.pickupaddress ?? '',
|
|
contact: data?.pickupcontactno ?? '',
|
|
lat: data?.pickuplat,
|
|
lon: data?.pickuplon,
|
|
isPickup: true,
|
|
),
|
|
const SizedBox(height: 20),
|
|
_buildInfoBlock(
|
|
label: isFromSummary ? "Delivered" : "Drop",
|
|
name: data?.deliverycustomer ?? '',
|
|
address: data?.deliveryaddress ?? '',
|
|
contact: data?.deliverycontactno ?? '',
|
|
lat: data?.droplat,
|
|
lon: data?.droplon,
|
|
isPickup: false,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
Widget _circleIcon(IconData icon, Color color) {
|
|
return Container(
|
|
height: 38,
|
|
width: 38,
|
|
decoration: const BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Colors.white,
|
|
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 3)],
|
|
),
|
|
child: FaIcon(icon as FaIconData?, color: color, size: 20),
|
|
);
|
|
}
|
|
|
|
Widget _verticalDottedLine({double height = 60}) {
|
|
return SizedBox(
|
|
height: height,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: List.generate(
|
|
(height ~/ 6),
|
|
(index) => Container(
|
|
width: 1,
|
|
height: 4,
|
|
color: Colors.grey[400],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoBlock({
|
|
required String label,
|
|
required String name,
|
|
required String address,
|
|
required String contact,
|
|
required String? lat,
|
|
required String? lon,
|
|
required bool isPickup,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
_infoRow(Icons.person, name),
|
|
const SizedBox(height: 4),
|
|
_infoRow(Icons.location_on, address, maxLines: 2),
|
|
const SizedBox(height: 4),
|
|
InkWell(
|
|
onTap: () => Utility.openPhoneCallApp(contact),
|
|
child: _infoRow(Icons.phone, contact),
|
|
),
|
|
const SizedBox(height: 6),
|
|
InkWell(
|
|
onTap: () {
|
|
Utility.openMap(
|
|
double.tryParse(lat ?? '0') ?? 0,
|
|
double.tryParse(lon ?? '0') ?? 0,
|
|
);
|
|
},
|
|
child: const Row(
|
|
children: [
|
|
Icon(Icons.assistant_direction_rounded,
|
|
color: ColorConstants.primaryColor, size: 20),
|
|
SizedBox(width: 6),
|
|
Text(
|
|
"Navigate",
|
|
style: TextStyle(
|
|
color: ColorConstants.primaryColor,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _infoRow(IconData icon, String text, {int maxLines = 1}) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, size: 16, color: Colors.black45),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Text(
|
|
text,
|
|
maxLines: maxLines,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
color: Colors.black87,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |