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.

1237 lines
47 KiB

import 'dart:convert';
2 months ago
import 'package:flutter/material.dart';
2 months ago
import 'package:flutter/services.dart';
import 'package:supplier_new/resources/tanker_trips_model.dart';
2 months ago
import '../common/settings.dart';
class FirstCharUppercaseFormatter extends TextInputFormatter {
const FirstCharUppercaseFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text;
if (text.isEmpty) return newValue;
// Find first non-space char
final i = text.indexOf(RegExp(r'\S'));
if (i == -1) return newValue;
final first = text[i];
final upper = first.toUpperCase();
if (first == upper) return newValue;
final newText = text.replaceRange(i, i + 1, upper);
return newValue.copyWith(
text: newText,
selection: newValue.selection,
composing: TextRange.empty,
);
}
}
2 months ago
class TankerDetailsPage extends StatefulWidget {
var tankerDetails;
var status;
TankerDetailsPage({this.tankerDetails, this.status});
@override
State<TankerDetailsPage> createState() => _TankerDetailsPageState();
}
class _TankerDetailsPageState extends State<TankerDetailsPage> {
2 months ago
final _formKey = GlobalKey<FormState>();
final _nameCtrl = TextEditingController();
final _capacityCtrl = TextEditingController();
final _plateCtrl = TextEditingController();
final _mfgYearCtrl = TextEditingController();
final _insExpiryCtrl = TextEditingController();
// Dropdown selections (sheet)
String? selectedType;
String? selectedTypeOfWater;
// Dropdown options (adjust to your backend)
final List<String> tankerTypes = const [
"Standard Tanker",
"High-Capacity Tanker",
"Small Tanker",
];
final List<String> typeOfWater = const [
"Drinking water",
"Bore water",
];
String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required";
return null;
}
int selectedTab = 0;
String search = '';
bool isLoading = false;
bool isTankerTripsLoading=false;
2 months ago
bool isStatusLoading = false;
String? currentAvailability;
List<TankerTripsModel> tankerTripsList = [];
@override
void initState() {
super.initState();
_nameCtrl.text=widget.tankerDetails.tanker_name ?? '';
// ✅ If the tanker already has availability from backend, prepare it
if (widget.tankerDetails.availability != null &&
widget.tankerDetails.availability is List &&
widget.tankerDetails.availability.length == 2) {
final a = widget.tankerDetails.availability;
currentAvailability = "${a[0]}|${a[1]}";
_fetchTankerTrips();
// ✅ Automatically show dropdown bottom sheet after short delay
/*WidgetsBinding.instance.addPostFrameCallback((_) {
_showDropdownBottomSheet(defaultValue: currentAvailability);
});*/
}
}
Future<void> _fetchTankerTrips() async {
setState(() => isTankerTripsLoading = true);
try {
var payload = new Map<String, dynamic>();
payload["customerId"] = widget.tankerDetails.tanker_name;
payload["tankerName"] = widget.tankerDetails.tanker_name;
final response = await AppSettings.getTankerTrips(payload);
final data = (jsonDecode(response)['data'] as List)
.map((e) => TankerTripsModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
tankerTripsList = data;
isTankerTripsLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
setState(() => isTankerTripsLoading = false);
}
}
// Dropdown options: fill + availability combined
final List<String> options = [
"empty|available",
"empty|blocked",
"empty|undermaintanence",
"filled|available",
"filled|blocked",
"filled|undermaintanence",
];
String _getLabel(String value) {
final parts = value.split('|');
return "${parts[0].toUpperCase()}${parts[1].toUpperCase()}";
}
Future<void> _updateAvailability(String selectedValue) async {
// 🔹 Split the combined dropdown value like "filled|available"
final parts = selectedValue.split('|');
final availability = [parts[0], parts[1]];
// 🔹 Build payload for updateTanker API
final payload = <String, dynamic>{
"tankerName": widget.tankerDetails.tanker_name,
"availability": availability, // ✅ send array as backend expects
};
try {
setState(() => isStatusLoading = true);
// 🔹 Reuse your existing update API
final bool tankStatus = await AppSettings.updateTankerAvailability(payload);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker status updated successfully");
// Close sheet and refresh details
Navigator.pop(context, true);
await _refreshTankerDetails();
} else {
Navigator.pop(context, true);
AppSettings.longFailedToast("Tanker status update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong: $e");
} finally {
if (mounted) setState(() => isStatusLoading = false);
}
}
void _showDropdownBottomSheet({String? defaultValue}) {
String? selectedValue = defaultValue; // preselect from backend
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent, // nice rounded corners
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.6, // ⬅️ increase/decrease height here (e.g., 0.7 / 0.8)
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: EdgeInsets.fromLTRB(20, 16, 20, 16 + viewInsets),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// drag handle
Center(
child: Container(
width: 60,
height: 4,
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
const Text(
"Change Tanker Status",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
const SizedBox(height: 20),
// Make central content scrollable within fixed height
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
DropdownButtonFormField<String>(
value: selectedValue,
decoration: InputDecoration(
labelText: "Select Availability",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 12,
),
),
items: options
.map((opt) => DropdownMenuItem(
value: opt,
child: Text(_getLabel(opt)),
))
.toList(),
onChanged: (val) => selectedValue = val,
),
const SizedBox(height: 20),
],
),
),
),
// Sticky submit at bottom
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF515253),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
if (selectedValue != null) {
_updateAvailability(selectedValue!);
} else {
AppSettings.longFailedToast("Please select an option");
}
},
child: isLoading
? const SizedBox(
width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: const Text("Submit"),
),
),
],
),
),
);
},
);
}
showDeleteTankerDialog(BuildContext context) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return AlertDialog(
backgroundColor: Color(0XFFFFFFFF),// Set your desired background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // Optional: Rounded corners
),
title: Center(
child: Text('Delete Tanker?' ,style: fontTextStyle(16,Color(0XFF3B3B3B),FontWeight.w600),),
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Container(
child: Text('Do u want to delete "${widget.tankerDetails.tanker_name}"',style: fontTextStyle(14,Color(0XFF101214),FontWeight.w600),),
),
],
),
),
actions: <Widget>[
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: GestureDetector(
onTap: (){
Navigator.pop(context);
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFFFFFFF),
border: Border.all(
width: 1,
color: Color(0XFF1D7AFC)),
borderRadius: BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Visibility(
visible: true,
child: Padding(
padding: EdgeInsets.fromLTRB(16,12,16,12),
child: Text('Cancel', style: fontTextStyle(12, Color(0XFF1D7AFC), FontWeight.w600)),
),
),
),
),),
SizedBox(width:MediaQuery.of(context).size.width * .016,),
Expanded(child: GestureDetector(
onTap: ()async{
bool status = await AppSettings.deleteTanker(widget.tankerDetails.tanker_name,);
if(status){
AppSettings.longSuccessToast('Tanker deleted successfully');
Navigator.of(context).pop(true);
Navigator.of(context).pop(true);
}
else{
Navigator.of(context).pop(true);
AppSettings.longFailedToast('Failed to delete tanker');
}
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFE2483D),
border: Border.all(
width: 1,
color: Color(0XFFE2483D)),
borderRadius: BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Visibility(
visible: true,
child: Padding(
padding: EdgeInsets.fromLTRB(16,12,16,12),
child: Text(
'Delete',
style: fontTextStyle(
12,
Color(0XFFFFFFFF),
FontWeight.w600)),
),
),
)
),)
],
),
),
],
);
});
},
);
}
2 months ago
Color _chipColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFFFFFFFF);
case 'available':
return const Color(0xFFE8F0FF);
case 'empty':
return const Color(0xFFFFEEEE);
case 'in-use':
return const Color(0xFFFFF0E6);
case 'maintenance':
return const Color(0xFFFFF4E6);
default:
return const Color(0xFFECECEC);
}
}
Color _chipTextColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFF1D7AFC);
case 'available':
return const Color(0xFF0A9E04);
case 'empty':
return const Color(0xFFE2483D);
case 'in-use':
return const Color(0xFFEA843B);
case 'maintenance':
return const Color(0xFFD0AE3C);
default:
return Colors.black87;
}
}
Future<void> _pickInsuranceDate() async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
dialogBackgroundColor: Colors.white,
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: const Color(0xFF8270DB),
surface: Colors.white,
onSurface: const Color(0xFF101214),
),
2 months ago
),
2 months ago
child: child!,
);
},
);
if (picked != null) {
final dd = picked.day.toString().padLeft(2, '0');
final mm = picked.month.toString().padLeft(2, '0');
final yyyy = picked.year.toString();
_insExpiryCtrl.text = "$dd-$mm-$yyyy";
setState(() {});
}
}
void _resetForm() {
_formKey.currentState?.reset();
_nameCtrl.clear();
_capacityCtrl.clear();
_plateCtrl.clear();
_mfgYearCtrl.clear();
_insExpiryCtrl.clear();
selectedType = null;
selectedTypeOfWater = null;
}
Future<void> _refreshTankerDetails() async {
try {
setState(() => isLoading = true);
final updatedDetails = await AppSettings.getTankerDetailsByName(
_nameCtrl.text.trim(),
);
if (updatedDetails != null) {
setState(() {
widget.tankerDetails = updatedDetails;
});
} else {
AppSettings.longFailedToast("Failed to fetch updated tanker details");
}
} catch (e) {
debugPrint("⚠️ Error refreshing tanker details: $e");
AppSettings.longFailedToast("Error refreshing tanker details");
} finally {
setState(() => isLoading = false);
}
}
2 months ago
String? fitToOption(String? incoming, List<String> options) {
if (incoming == null) return null;
final inc = incoming.trim();
if (inc.isEmpty) return null;
final match = options.firstWhere(
(o) => o.toLowerCase() == inc.toLowerCase(),
orElse: () => '',
);
return match.isEmpty ? null : match; // return the exact option string
}
Future<void> _updateTanker() async {
// Validate
final ok = _formKey.currentState?.validate() ?? false;
if (!ok) {
setState(() {}); // force rebuild to show errors
return;
}
// Build payload keys to match your backend
final payload = <String, dynamic>{
"tankerName": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"typeofwater": selectedTypeOfWater ?? "",
"supplier_address": AppSettings.userAddress,
"supplier_name": AppSettings.userName,
"phoneNumber": AppSettings.phoneNumber,
"tanker_type": selectedType ?? "",
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_exp_date": _insExpiryCtrl.text.trim(),
"delivery_fee": widget.tankerDetails.delivery_fee,
"pumping_fee":widget.tankerDetails.pumping_fee,
"price": widget.tankerDetails.price,
};
try {
final bool tankStatus = await AppSettings.updateTanker(payload,widget.tankerDetails.tanker_name);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker Updated Successfully");
Navigator.pop(context, true);
_refreshTankerDetails();// close sheet
2 months ago
_resetForm();
// refresh from server
} else {
AppSettings.longFailedToast("Tanker update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong");
}
}
Future<void> openTankerSimpleSheet(BuildContext context) async {
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
_capacityCtrl.text = widget.tankerDetails.capacity ?? '';
_plateCtrl.text = widget.tankerDetails.license_plate ?? '';
_mfgYearCtrl.text = widget.tankerDetails.manufacturing_year ?? '';
_insExpiryCtrl.text = widget.tankerDetails.insurance_expiry ?? '';
selectedType = fitToOption(widget.tankerDetails.tanker_type, tankerTypes);
selectedTypeOfWater = fitToOption(widget.tankerDetails.type_of_water, typeOfWater);
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.75,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Padding(
padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + viewInsets),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(
child: Center(
child: Container(
width: 86,
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
),
],
),
_LabeledField(
label: "Tanker Name *",
child: TextFormField(
controller: _nameCtrl,
validator: (v) => _required(v, field: "Tanker Name"),
textCapitalization: TextCapitalization.none,
inputFormatters: const [
FirstCharUppercaseFormatter(), // << live first-letter caps
],
decoration: InputDecoration(
hintText: "Enter Tanker Name",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Capacity (in L) *",
child: TextFormField(
controller: _capacityCtrl,
validator: (v) => _required(v, field: "Tanker Capacity"),
decoration: InputDecoration(
hintText: "10,000",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9,]')),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Type *",
child: DropdownButtonFormField<String>(
value: selectedType,
items: tankerTypes
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedType = v),
validator: (v) => v == null || v.isEmpty ? "Tanker Type is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select Type",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "Type of water *",
child: DropdownButtonFormField<String>(
value: (selectedTypeOfWater != null && typeOfWater.contains(selectedTypeOfWater))
? selectedTypeOfWater
: null, // <- ensure null instead of "" or a non-member
items: typeOfWater
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedTypeOfWater = v),
validator: (v) => v == null || v.isEmpty ? "Type of water is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select type of water",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "License Plate *",
child: TextFormField(
controller: _plateCtrl,
validator: (v) => _required(v, field: "License Plate"),
decoration: InputDecoration(
hintText: "AB 05 H 4948",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textCapitalization: TextCapitalization.characters,
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Manufacturing Year (opt)",
child: TextFormField(
controller: _mfgYearCtrl,
decoration: InputDecoration(
hintText: "YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Insurance Expiry Date (opt)",
child: TextFormField(
controller: _insExpiryCtrl,
readOnly: true,
decoration: InputDecoration(
hintText: "DD-MM-YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
suffixIcon: const Icon(Icons.calendar_today_outlined, size: 18),
),
onTap: _pickInsuranceDate,
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: (){
_updateTanker();
/*if (_formKey.currentState!.validate()) {
final updatedTanker = {
"tanker_name": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_expiry": _insExpiryCtrl.text.trim(),
"tanker_type": selectedType,
"type_of_water": selectedTypeOfWater,
};
// You can now call your update API or local DB function
print("Updated Tanker: $updatedTanker");
Navigator.pop(context);
}*/
},
child: Text(
"Update",
style: fontTextStyle(14, Colors.white, FontWeight.w600),
),
),
),
],
),
),
2 months ago
),
),
),
2 months ago
);
},
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// ✅ Return true to indicate successful tanker update refresh
Navigator.pop(context, true);
return false; // prevent default pop since we manually handled it
},
child: Scaffold(
backgroundColor: Color(0XFFF1F1F1),
appBar: AppSettings.supplierAppBarWithActionsText(widget.tankerDetails.tanker_name.isNotEmpty
? widget.tankerDetails.tanker_name[0].toUpperCase() +
widget.tankerDetails.tanker_name.substring(1)
: '', context),
2 months ago
body: SingleChildScrollView(
child: Padding(
2 months ago
padding: const EdgeInsets.all(0),
2 months ago
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
2 months ago
Container(
decoration: const BoxDecoration(
color: Color(0XFFFFFFFF),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(16),
child: Column(
2 months ago
children: [
2 months ago
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
children: [
Image.asset(
'images/tanker_image.jpeg', // Replace with your image
width: double.infinity,
height: 180,
fit: BoxFit.cover,
),
/*Positioned(
2 months ago
bottom: 12,
left: 12,
child: Row(
children: [
_buildStatusChip("filled", const Color(0xFF4F46E5)),
const SizedBox(width: 8),
_buildStatusChip("available", const Color(0xFF0A9E04)),
],
),
),*/
2 months ago
],
),
),
const SizedBox(height: 16),
Row(
children: widget.tankerDetails.availability
.map<Widget>((s) {
final chipTextColor = _chipTextColor(s);
return Container(
margin: const EdgeInsets.only(right: 6),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _chipColor(s),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: chipTextColor, width: 1),
),
child: Text(
s,
style: fontTextStyle(10, chipTextColor, FontWeight.w400),
),
);
})
.toList(),
),
const SizedBox(height: 16),
// 🚚 Tanker Info
Row(
2 months ago
crossAxisAlignment: CrossAxisAlignment.start,
children: [
2 months ago
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.tankerDetails.tanker_name,
style: fontTextStyle(16, const Color(0xFF2A2A2A), FontWeight.w600),
),
SizedBox(height: 2),
Text(
widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L',
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
),
SizedBox(height: 2),
Text(
widget.tankerDetails.license_plate,
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
),
],
2 months ago
),
),
2 months ago
PopupMenuButton<String>(
// 🔁 Use `child:` so you can place any widget (your 3-dots image)
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: Image.asset(
'images/popup_menu.png', // your 3-dots image
width: 22,
height: 22,
// If you want to tint it like an icon:
color: Color(0XFF939495), // remove if you want original colors
colorBlendMode: BlendMode.srcIn,
),
2 months ago
),
2 months ago
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
offset: const Offset(0, 40),
color: Colors.white,
elevation: 4,
onSelected: (value) {
if (value == 'edit') {
openTankerSimpleSheet(context);
} else if (value == 'disable') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Disable selected')),
);
} else if (value == 'delete') {
showDeleteTankerDialog(context);
2 months ago
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Image.asset(
'images/edit.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Edit',
style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
),
],
),
),
PopupMenuItem(
value: 'disable',
child: Row(
children: [
Image.asset(
'images/disable.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Disable',
style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
Image.asset(
'images/delete.png',
width: 20,
height: 20,
color: Color(0XFFE2483D), // red like your example
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Delete',
style: fontTextStyle(14, const Color(0xFFE2483D), FontWeight.w400),
),
],
),
),
],
)
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFFFFFF),
foregroundColor: const Color(0xFF515253),
padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF939495), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
),
onPressed: () async {
_showDropdownBottomSheet(defaultValue: currentAvailability);
2 months ago
},
child: Text(
"Change Status",
style: fontTextStyle(14, const Color(0xFF515253), FontWeight.w500),
),
)
2 months ago
),
2 months ago
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: const Color(0xFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF8270DB), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
),
onPressed: () async {
// _showAssignTankerBottomSheet();
},
child: Text(
"Assign",
style: fontTextStyle(14, const Color(0xFFFFFFFF), FontWeight.w500),
),
)
2 months ago
),
],
),
2 months ago
],
2 months ago
),
),
2 months ago
Padding(padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"RECENT TRIPS",
style: fontTextStyle(10, const Color(0xFF343637), FontWeight.w600),
),
const SizedBox(height: 12),
_buildTripCard(
driverName: "Ramesh Krishna",
time: "7:02 PM, 28 Jun 2025",
from: "Bachupally Filling Station",
to: "Akriti Heights",
),
const SizedBox(height: 8),
_buildTripCard(
driverName: "Ramesh Krishna",
time: "12:44 PM, 26 Jun 2025",
from: "Bachupally Filling Station",
to: "Akriti Heights",
),
],
),
2 months ago
),
2 months ago
Expanded(
child: isTankerTripsLoading
? const Center(child: CircularProgressIndicator())
: (tankerTripsList.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12),
child: Text(
'No Data Available',
style: fontTextStyle(
12,
const Color(0xFF939495),
FontWeight.w500),
),
),
)
: ListView.separated(
itemCount: tankerTripsList.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, idx) {
final it = tankerTripsList[idx];
return GestureDetector(
onTap: () async{
/*final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TankerDetailsPage(tankerDetails: it),
),
);
if (result == true) {
_fetchTankers();
}*/
},
child: _buildTripCard(
driverName: it.driver_name,
time: "7:02 PM, 28 Jun 2025",
from: "Bachupally Filling Station",
to: "Akriti Heights",
),
);
},
)),
),
2 months ago
],
),
),
),
2 months ago
)
2 months ago
);
}
Widget _buildTripCard({
required String driverName,
required String time,
required String from,
required String to,
}) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Driver + time
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const CircleAvatar(
radius: 14,
backgroundImage: AssetImage('images/avatar.png'),
),
const SizedBox(width: 8),
Text(
driverName,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
Text(
time,
style: const TextStyle(
fontSize: 11,
color: Colors.black54,
),
),
],
),
const SizedBox(height: 8),
// From location
Row(
children: [
const Icon(Icons.location_on, color: Color(0xFF4F46E5), size: 16),
const SizedBox(width: 6),
Expanded(
child: Text(
from,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
],
),
const SizedBox(height: 4),
// To location
Row(
children: [
const Icon(Icons.location_on, color: Colors.red, size: 16),
const SizedBox(width: 6),
Expanded(
child: Text(
to,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
],
),
],
),
);
}
}
2 months ago
// ====== Labeled Field Wrapper ======
class _LabeledField extends StatelessWidget {
final String label;
final Widget child;
const _LabeledField({required this.label, required this.child});
2 months ago
2 months ago
String _capFirstWord(String input) {
if (input.isEmpty) return input;
final i = input.indexOf(RegExp(r'\S'));
if (i == -1) return input;
return input.replaceRange(i, i + 1, input[i].toUpperCase());
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_capFirstWord(label),
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600),
),
const SizedBox(height: 6),
child,
],
),
);
}
}