Files
daily_mobileapp_customer/lib/view/cart/cart_view.dart
2026-05-26 18:01:57 +05:30

1870 lines
87 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import 'package:nearledaily/helper/logger.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../constants/color_constants.dart';
import '../../constants/font_constants.dart';
import '../../controllers/cart_controller/cart.dart';
import '../../controllers/order_controller/create_order_controller.dart';
import '../../controllers/product/product_controller.dart';
import '../../domain/provider/tenant/get_tenant_pro.dart';
import '../../helper/distance.dart';
import '../../modules/authentication/auth.dart';
import '../../modules/orders/create_order.dart';
import '../../modules/product/product.dart';
import '../../modules/tenant/get_tenant.dart';
import '../../widgets/slider_button.dart';
import '../../widgets/text_widget.dart';
import '../home_view.dart';
import '../map_view/location.dart';
import '../orders/order_succes.dart';
import 'order_countdown_page.dart';
class CartPage extends StatefulWidget {
const CartPage({super.key});
@override
State<CartPage> createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
DateTime? selectedDate;
String? selectedTime;
String? selectedTimeDisplay;
TenantLocation? selectedLocation;
List<TenantLocation> tenantLocations = [];
// ── date helpers ──────────────────────────────────────────
String _fmtDate(DateTime d) =>
'${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
String _fmtNow() {
final n = DateTime.now();
return '${_fmtDate(n)} '
'${n.hour.toString().padLeft(2, '0')}:'
'${n.minute.toString().padLeft(2, '0')}:'
'${n.second.toString().padLeft(2, '0')}';
}
// ──────────────────────────────────────────────────────────
Future<void> openPreorderPicker() async {
final picked = await showDatePicker(
context: context,
initialDate: selectedDate ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 30)),
);
if (picked != null) {
setState(() {
selectedDate = picked;
selectedTime = null;
selectedTimeDisplay = null;
});
}
await fetchTenantLocations();
if (tenantLocations.isEmpty) return;
final loc = tenantLocations.first;
var slots24 = _generateTimeSlots(loc.openTime, loc.closeTime);
final now = DateTime.now();
if (selectedDate != null &&
selectedDate!.year == now.year &&
selectedDate!.month == now.month &&
selectedDate!.day == now.day) {
slots24 = slots24.where((s) {
final p = s.split(':');
final t = DateTime(
now.year,
now.month,
now.day,
int.parse(p[0]),
int.parse(p[1]),
);
return t.isAfter(now);
}).toList();
}
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) => Container(
height: 300,
padding: const EdgeInsets.all(16),
child: slots24.isEmpty
? const Center(child: Text('No slots available'))
: ListView.separated(
itemCount: slots24.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (_, i) {
final t24 = slots24[i];
final t12 = _to12Hour(t24);
return ListTile(
title: Text(t12),
onTap: () {
setState(() {
selectedTime = t24;
selectedTimeDisplay = t12;
});
Navigator.pop(context);
},
);
},
),
),
);
}
Future<void> fetchTenantLocations() async {
final cartController = Get.find<CartController>();
if (cartController.cartItems.isEmpty) return;
int? tenantId = cartController.cartItems.first.product.tenantid;
try {
final provider = CustomerTenantsProvider();
final response = await provider.getTenantLocations(tenantId!);
if (response != null && response.details.isNotEmpty) {
setState(() => tenantLocations = response.details);
} else {
setState(() => tenantLocations = []);
}
} catch (e) {
// silent
}
}
@override
void initState() {
super.initState();
_loadProfile();
_setDefaultDateAndTime();
final cartCtrl = Get.find<CartController>();
cartCtrl.appliedCoupon.value = "";
cartCtrl.amt.value = "";
}
Future<void> _setDefaultDateAndTime() async {
selectedDate = DateTime.now(); // today
await fetchTenantLocations();
if (tenantLocations.isEmpty) {
setState(() => selectedTime = null);
return;
}
final first = tenantLocations.first;
final slots24 = _generateTimeSlots(first.openTime, first.closeTime);
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final future24 = slots24.where((s) {
final p = s.split(':');
final t = DateTime(today.year, today.month, today.day,
int.parse(p[0]), int.parse(p[1]));
return t.isAfter(now);
}).toList();
setState(() {
selectedTime = future24.isNotEmpty ? future24.first : null;
selectedTimeDisplay =
selectedTime != null ? _to12Hour(selectedTime!) : null;
});
}
List<String> _generateTimeSlots(String openTime, String closeTime) {
final fmt = RegExp(r'^(\d{1,2}):(\d{2})$');
if (!fmt.hasMatch(openTime) || !fmt.hasMatch(closeTime)) return [];
final o = fmt.firstMatch(openTime)!;
final c = fmt.firstMatch(closeTime)!;
final openH = int.parse(o.group(1)!);
final openM = int.parse(o.group(2)!);
final closeH = int.parse(c.group(1)!);
final closeM = int.parse(c.group(2)!);
DateTime cur = DateTime(0, 0, 0, openH, openM);
DateTime end = DateTime(0, 0, 0, closeH, closeM);
List<String> slots = [];
while (cur.isBefore(end) || cur.isAtSameMomentAs(end)) {
slots.add(
'${cur.hour.toString().padLeft(2, '0')}:${cur.minute.toString().padLeft(2, '0')}');
cur = cur.add(const Duration(minutes: 60));
}
return slots;
}
String _to12Hour(String time24) {
final parts = time24.split(':');
int h = int.parse(parts[0]);
final m = parts[1];
final period = h >= 12 ? 'PM' : 'AM';
if (h == 0) h = 12;
if (h > 12) h -= 12;
return '$h:$m $period';
}
String name = '', num = '', address = '', suburb = '', city = '', doorNo = '';
Future<void> _loadProfile() async {
final prefs = await SharedPreferences.getInstance();
final id = prefs.getInt('customerId');
if (id == null) {
Get.snackbar('Error', 'Customer ID not found');
return;
}
try {
final cartCtrl = Get.find<CartController>();
await cartCtrl.fetchCustomer(id);
final profile = cartCtrl.customer.value;
if (profile != null) {
setState(() {
name = '${profile.firstName} ${profile.lastName}'.trim();
num = profile.contactNo ?? '';
address = profile.address ?? '';
suburb = profile.suburb ?? '';
city = profile.city ?? '';
doorNo = profile.doorNo ?? '';
});
}
} catch (e) {
// silent
}
}
@override
Widget build(BuildContext context) {
final cartCtrl = Get.find<CartController>();
final tenant = cartCtrl.currentTenant.value;
return Obx(() {
final items = cartCtrl.cartItems;
final dis = double.tryParse(cartCtrl.amt.value.toString()) ?? 0.0;
final subtotal = items.fold(
0.0,
(s, i) {
final cost = i.product.productcost ?? 0;
final discount = i.product.discount ?? 0;
final priceAfterDiscount = cost - discount;
return s + (priceAfterDiscount * i.quantity);
},
);
final tax = cartCtrl.totalTax;
final delivery = tenant?.tenantcharge ?? 0.0;
final total = subtotal + tax + delivery - dis;
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white,
Color(0xFFF9F7FC),
Color(0xFFF8F6FC),
Colors.white
],
),
borderRadius: BorderRadius.circular(16),
),
child: SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
surfaceTintColor: Colors.transparent,
scrolledUnderElevation: 0,
animateColor: false,
leading: Navigator.of(context).canPop()
? Row(
children: [
IconButton(
onPressed: () {
Navigator.pop(context);
final nav = Get.find<BottomNavController>();
nav.currentIndex.value = 0;
},
icon:
const Icon(Icons.arrow_back, color: Colors.black),
),
ReusableTextWidget(
text: "Cart",
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: FontConstants.fontFamily,
color: Colors.black,
),
],
)
: const Padding(
padding: EdgeInsets.all(15.0),
child: Text(
'Cart',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
),
),
leadingWidth: double.infinity,
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
),
body: items.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 120,
width: 120,
child: Lottie.asset(
'assets/lotties/no_slots_lottie.json',
fit: BoxFit.contain),
),
const SizedBox(height: 20),
const Padding(
padding: EdgeInsets.all(6.0),
child: Text(
'No slots available for today.\nPlease try changing dates to schedule orders.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold),
),
),
],
),
)
: SingleChildScrollView(
padding: const EdgeInsets.all(12),
physics: const ClampingScrollPhysics(),
child: Column(
children: [
SizedBox(height: 13),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
left: BorderSide(
color: Colors.black12, width: 0.50),
right: BorderSide(
color: Colors.black12, width: 0.50),
top: BorderSide(
color: Colors.black12, width: 0.50),
),
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor
.withOpacity(0.02),
blurRadius: 1,
spreadRadius: 0.50,
offset: Offset(0, -3),
),
],
),
child: Column(
children: items
.map((item) => _cartItemWidget(item, cartCtrl))
.toList(),
),
),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
left: BorderSide(
color: Colors.black12, width: 0.50),
right: BorderSide(
color: Colors.black12, width: 0.50),
bottom: BorderSide(
color: Colors.black12, width: 0.50),
),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor
.withOpacity(0.08),
blurRadius: 2,
spreadRadius: 0,
offset: Offset(0, 3),
),
],
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
)),
child:
_priceSummary(subtotal, tax, delivery, total, dis)),
const SizedBox(height: 35),
],
),
),
),
),
);
});
}
Widget _cartItemWidget(CartItem item, CartController ctrl) {
final p = item.product;
final double screenWidth = MediaQuery.of(context).size.width;
final double imageSize =
math.min(100, math.max(80, screenWidth * 0.22));
return Slidable(
key: ValueKey(p.productid),
endActionPane: ActionPane(
motion: const DrawerMotion(),
extentRatio: 0.23,
children: [
SlidableAction(
onPressed: (_) {
ctrl.removeFromCart(p);
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete_outline,
label: 'Delete',
borderRadius: const BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18),
),
),
],
),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 0),
padding: const EdgeInsets.all(14),
decoration: const BoxDecoration(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SizedBox(
height: imageSize,
width: imageSize,
child: Image.network(
p.productimage ?? '',
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(
color: Colors.grey[300],
child: const Icon(Icons.image, size: 24),
),
),
),
),
SizedBox(width: screenWidth * 0.03),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: p.productname!,
color: Colors.black87,
fontWeight: FontWeight.w600,
fontSize: screenWidth * 0.035,
fontFamily: FontConstants.fontFamily,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 6),
ReusableTextWidget(
text: p.subcategoryname!,
color: Colors.black87,
fontWeight: FontWeight.w500,
fontSize: screenWidth * 0.034,
fontFamily: FontConstants.fontFamily,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
ReusableTextWidget(
text: p.productdesc!,
color: Colors.black45,
fontWeight: FontWeight.w500,
fontSize: screenWidth * 0.030,
fontFamily: FontConstants.fontFamily,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
_qtyButton(
icon: Icons.remove,
onTap: () {
if (item.quantity > 1) {
item.quantity--;
ctrl.cartItems.refresh();
} else {
ctrl.removeFromCart(p);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: const [
Icon(Icons.arrow_upward,
color: Colors.white, size: 18),
SizedBox(width: 8),
Text("Removed"),
],
),
backgroundColor: Colors.red,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
'${item.quantity}',
style: TextStyle(
fontSize: screenWidth * 0.038,
fontWeight: FontWeight.w600,
),
),
),
_qtyButton(
icon: Icons.add,
gradient: LinearGradient(
colors: [
Colors.white,
ColorConstants.primaryColor.withOpacity(0.6),
],
),
onTap: () {
item.quantity++;
ctrl.cartItems.refresh();
},
),
],
),
const SizedBox(height: 18),
Row(
children: [
ReusableTextWidget(
text:
'${(p.productcost! - (p.discount ?? 0)).toInt()}',
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: screenWidth * 0.032,
fontFamily: FontConstants.fontFamily,
),
const SizedBox(width: 6),
if ((p.discount ?? 0) > 0)
ReusableTextWidget(
text: '${p.productcost?.toInt() ?? 0}',
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: screenWidth * 0.03,
fontFamily: FontConstants.fontFamily,
isUnderText: TextDecoration.lineThrough,
),
],
),
],
),
],
),
),
);
}
Widget _priceSummary(
double subtotal, double tax, double delivery, double total, double dis) {
final cartCtrl = Get.find<CartController>();
return Column(
children: [
const SizedBox(height: 5),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(7),
),
child: Column(
children: [
Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
final cartCtrl = Get.find<CartController>();
cartCtrl.loadCoupons();
cartCtrl.showCouponBottomSheet(context);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Row(
children: [
Obx(() {
bool applied = cartCtrl.amt.value.isNotEmpty;
return Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: applied
? Colors.green.shade100
: Colors.orange.shade100,
shape: BoxShape.circle,
),
child: Icon(
applied
? Icons.check_circle
: Icons.discount_outlined,
size: 14,
color: applied ? Colors.green : Colors.orange,
),
);
}),
const SizedBox(width: 10),
Obx(() {
return Text(
cartCtrl.amt.value.isEmpty
? 'Apply a promo code'
: 'Discount Applied: ₹${cartCtrl.amt.value}',
style: TextStyle(
fontSize: 11,
color: cartCtrl.amt.value.isEmpty
? Colors.black.withOpacity(0.65)
: Colors.green.shade700,
fontWeight: FontWeight.bold,
),
);
}),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: ColorConstants.primaryColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
ReusableTextWidget(
text: "Apply",
color: Colors.white,
fontFamily: FontConstants.fontFamily,
fontSize: 12,
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
SizedBox(width: 6),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Colors.white,
),
],
),
)
],
),
),
),
),
SizedBox(height: 12),
_priceRow('Subtotal', subtotal),
_priceRow('Discount', dis),
_priceRow('GST', tax),
_priceRow('Other Charges', delivery),
const Divider(),
_priceRow('Total', total, isBold: true),
const SizedBox(height: 12),
SliderCheckoutButton(
onConfirmed: () async {
final cartCtrl = Get.find<CartController>();
if (cartCtrl.cartItems.any((i) => i.quantity <= 0)) {
Get.snackbar('', 'Invalid Quantity',
backgroundColor: Colors.white,
colorText: Colors.black);
print('Invalid quantity in cart items');
return;
}
final orderCtrl = Get.find<OrderController>();
final loc = await Get.to(() => LocationPage());
if (loc != null && loc is Authentication) {
final tenant =
Get.find<CartController>().currentTenant.value;
final tenantLat =
double.tryParse(tenant?.latitude ?? '0') ?? 0;
final tenantLon =
double.tryParse(tenant?.longitude ?? '0') ?? 0;
final selLat =
double.tryParse(loc.latitude.toString()) ?? 0;
final selLon =
double.tryParse(loc.longitude.toString()) ?? 0;
final dist = calculateDistance(
tenantLat, tenantLon, selLat, selLon);
if (dist <= 5) {
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16))),
builder: (ctx) => SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ReusableTextWidget(
text: 'Confirm Order',
color: Colors.black.withOpacity(0.7),
fontFamily: FontConstants.fontFamily,
fontSize: 18,
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
const SizedBox(height: 20),
// ── CASH ON DELIVERY ──────────────────────────
Container(
padding: EdgeInsets.symmetric(
horizontal: 14, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.grey.shade300,
width: 1.5,
),
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.04),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: GestureDetector(
behavior: HitTestBehavior.opaque, // ← ADD THIS
child: Row(children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color:
Colors.green.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.money,
color: Colors.green,
size: 22,
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: 'Cash on Delivery',
color: Colors.black,
fontFamily:
FontConstants.fontFamily,
fontSize: 15,
fontWeight: FontWeight.w600,
),
SizedBox(height: 4),
Text(
'Pay when order arrives',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
size: 18,
color: Colors.grey.shade400,
),
]),
onTap: () async {
Navigator.pop(ctx);
final items = cartCtrl.cartItems
.map((i) => OrderItem(
productid:
i.product.productid,
productname:
i.product.productname,
productdescription: '',
orderqty: i.quantity,
price: i.product.productcost
?.toDouble() ??
0,
unitid: int.tryParse(i
.product.unitvalue
.toString()) ??
1,
unitname: i.product.productunit
?.toString()
.split('.')
.last ??
'unit',
productsumprice: total,
tax: tax,
discount: dis,
tenantfee: delivery,
))
.toList();
final order = CreateOrder(
applocationid:
tenant?.applocationid,
applocation: '',
tenantid: cartCtrl.cartItems.first
.product.tenantid,
partnerid: 60,
locationid: int.tryParse(
cartCtrl.pastLocationId ?? ""),
categoryid: cartCtrl.cartItems.first
.product.categoryid,
subcategoryid: cartCtrl
.cartItems.first.product.subcategoryid,
moduleid: 2,
configid: 1,
orderdate: _fmtNow(), // ✅ FIXED
deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created',
deliverycharge: tenant?.tenantcharge,
customerid: int.tryParse(
loc.customerid ?? '0'),
pickupcustomer:
tenant?.tenantname ?? '',
pickupcontactno:
tenant?.primarycontact,
pickupaddress: tenant?.address,
pickuplocationid: tenant?.locationid,
pickupcity: tenant?.city,
deliverycustomer: name,
deliverycontactno: num,
deliveryaddress: loc.address,
deliverylocationid: loc.locationid,
deliverylat: loc.latitude ?? '',
deliverylong: loc.longitude ?? '',
paymenttype: 42,
items: items,
);
print(order.toJson());
print(items.map((i) => i.toJson()).toList());
print('customer details');
// ✅ Navigate to countdown page
Get.to(() => OrderCountdownPage(
order: order,
orderCtrl: orderCtrl,
cartCtrl: cartCtrl,
customerName: name,
));
},
),
),
const SizedBox(height: 10),
// ── PAY ONLINE (RAZORPAY) ─────────────────────
Container(
padding: EdgeInsets.symmetric(
horizontal: 14, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.grey.shade300,
width: 1.5,
),
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.04),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: GestureDetector(
behavior: HitTestBehavior.opaque, // ← ADD THIS
child: Row(children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.deepPurple
.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.account_balance_wallet,
color: Colors.deepPurple,
size: 22,
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: 'Pay Online',
color: Colors.black,
fontFamily:
FontConstants.fontFamily,
fontSize: 15,
fontWeight: FontWeight.w600,
),
SizedBox(height: 4),
Text(
'UPI / Cards / Net Banking',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
size: 18,
color: Colors.grey.shade400,
),
]),
onTap: () {
Navigator.pop(ctx);
final Razorpay razorpay = Razorpay();
razorpay.on(
Razorpay.EVENT_PAYMENT_SUCCESS,
(PaymentSuccessResponse
response) async {
razorpay.clear();
if (!context.mounted) return;
final items = cartCtrl.cartItems
.map((i) => OrderItem(
productid:
i.product.productid,
productname:
i.product.productname,
productdescription: '',
orderqty: i.quantity,
price: i.product
.productcost
?.toDouble() ??
0,
unitid: int.tryParse(i
.product.unitvalue
.toString()) ??
1,
unitname: i.product
.productunit
?.toString()
.split('.')
.last ??
'unit',
productsumprice: total,
tax: tax,
discount: dis,
tenantfee: delivery,
))
.toList();
final order = CreateOrder(
applocationid:
tenant?.applocationid,
applocation: '',
tenantid: cartCtrl.cartItems.first
.product.tenantid,
partnerid: 60,
locationid: int.tryParse(
cartCtrl.pastLocationId ??
""),
categoryid: cartCtrl
.cartItems.first.product.categoryid,
subcategoryid: cartCtrl
.cartItems.first.product.subcategoryid,
moduleid: 6,
configid: 1,
orderdate: _fmtNow(), // ✅ FIXED
deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created',
deliverycharge:
tenant?.tenantcharge,
customerid: int.tryParse(
loc.customerid ?? '0'),
pickupcustomer:
tenant?.tenantname ?? '',
pickupcontactno:
tenant?.primarycontact,
pickupaddress: tenant?.address,
pickuplocationid:
tenant?.locationid,
pickupcity: tenant?.city,
deliverycustomer: name,
deliverycontactno: num,
deliveryaddress: loc.address,
deliverylocationid:
loc.locationid,
deliverylat: loc.latitude ?? '',
deliverylong: loc.longitude ?? '',
paymenttype: 2,
items: items,
);
await orderCtrl.createOrder(
CreateOrderRequest(
orders: order));
if (!context.mounted) return;
if (!orderCtrl.isLoading.value) {
Get.to(OrderSuccessView());
print(order);
print(items);
cartCtrl.clearCart();
await cartCtrl.notifyAdmin(
title:
'Nearle Deals - New Order',
body:
'A new order has been placed successfully by $name!');
}
});
razorpay.on(
Razorpay.EVENT_PAYMENT_ERROR,
(PaymentFailureResponse
response) {
razorpay.clear();
if (!context.mounted) return;
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(response.message ??
'Something went wrong'),
backgroundColor: Colors.red,
behavior:
SnackBarBehavior.floating,
),
);
});
razorpay.on(
Razorpay.EVENT_EXTERNAL_WALLET,
(ExternalWalletResponse
response) {
razorpay.clear();
});
razorpay.open({
'key': 'rzp_test_pUSj1Pz4LFLb',
'amount': (total * 100).toInt(),
'name': 'Nearle Deals',
'description': 'Order Payment',
'prefill': {
'contact': num,
'name': name,
},
'external': {
'wallets': ['paytm']
},
'theme': {
'full_screen': true,
},
});
},
),
),
const SizedBox(height: 10),
ListTile(
title: ReusableTextWidget(
text: 'Cancel',
color: Colors.red,
fontFamily: FontConstants.fontFamily,
fontSize: 18,
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
onTap: () => Navigator.pop(ctx),
),
],
),
),
),
);
} else {
showDialog(
context: Get.context!,
builder: (_) => AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
title: Column(
children: [
Lottie.asset('assets/lotties/location.json',
height: 100, repeat: true),
const SizedBox(height: 8),
const Text('Not Serviceable',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 17,
color: Colors.black87)),
],
),
content: const Text(
'Sorry, we don\'t serve this area yet.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15, color: Colors.black54)),
actionsAlignment: MainAxisAlignment.center,
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10))),
onPressed: () =>
Navigator.of(context).pop(),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
child: Text('OK',
style: TextStyle(
color: Colors.white, fontSize: 15)),
),
),
],
),
);
}
}
},
),
// GestureDetector(
// onTap: () async {
// final cartCtrl = Get.find<CartController>();
// if (cartCtrl.cartItems.any((i) => i.quantity <= 0)) {
// Get.snackbar('', 'Invalid Quantity',
// backgroundColor: Colors.white,
// colorText: Colors.black);
//
// print('Invalid quantity in cart items');
// return;
// }
// final orderCtrl = Get.find<OrderController>();
// final loc = await Get.to(() => LocationPage());
//
// if (loc != null && loc is Authentication) {
// final tenant =
// Get.find<CartController>().currentTenant.value;
// final tenantLat =
// double.tryParse(tenant?.latitude ?? '0') ?? 0;
// final tenantLon =
// double.tryParse(tenant?.longitude ?? '0') ?? 0;
// final selLat =
// double.tryParse(loc.latitude.toString()) ?? 0;
// final selLon =
// double.tryParse(loc.longitude.toString()) ?? 0;
// final dist = calculateDistance(
// tenantLat, tenantLon, selLat, selLon);
//
// if (dist <= 5) {
// showModalBottomSheet(
// backgroundColor: Colors.white,
// context: context,
// shape: const RoundedRectangleBorder(
// borderRadius: BorderRadius.vertical(
// top: Radius.circular(16))),
// builder: (ctx) => SafeArea(
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// ReusableTextWidget(
// text: 'Confirm Order',
// color: Colors.black.withOpacity(0.7),
// fontFamily: FontConstants.fontFamily,
// fontSize: 18,
// fontWeight: FontWeight.bold,
// textAlign: TextAlign.center,
// overflow: TextOverflow.ellipsis,
// maxLines: 1,
// ),
// const SizedBox(height: 20),
//
// // ── CASH ON DELIVERY ──────────────────────────
// Container(
// padding: EdgeInsets.symmetric(
// horizontal: 14, vertical: 16),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(16),
// border: Border.all(
// color: Colors.grey.shade300,
// width: 1.5,
// ),
// boxShadow: [
// BoxShadow(
// color:
// Colors.black.withOpacity(0.04),
// blurRadius: 8,
// offset: Offset(0, 4),
// ),
// ],
// ),
// child: GestureDetector(
// behavior: HitTestBehavior.opaque, // ← ADD THIS
// child: Row(children: [
// Container(
// padding: EdgeInsets.all(10),
// decoration: BoxDecoration(
// color:
// Colors.green.withOpacity(0.1),
// shape: BoxShape.circle,
// ),
// child: Icon(
// Icons.money,
// color: Colors.green,
// size: 22,
// ),
// ),
// SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// ReusableTextWidget(
// text: 'Cash on Delivery',
// color: Colors.black,
// fontFamily:
// FontConstants.fontFamily,
// fontSize: 15,
// fontWeight: FontWeight.w600,
// ),
// SizedBox(height: 4),
// Text(
// 'Pay when order arrives',
// style: TextStyle(
// fontSize: 12,
// color: Colors.grey.shade600,
// ),
// ),
// ],
// ),
// ),
// Icon(
// Icons.arrow_forward_ios,
// size: 18,
// color: Colors.grey.shade400,
// ),
// ]),
// onTap: () async {
// Navigator.pop(ctx);
//
// final items = cartCtrl.cartItems
// .map((i) => OrderItem(
// productid:
// i.product.productid,
// productname:
// i.product.productname,
// productdescription: '',
// orderqty: i.quantity,
// price: i.product.productcost
// ?.toDouble() ??
// 0,
// unitid: int.tryParse(i
// .product.unitvalue
// .toString()) ??
// 1,
// unitname: i.product.productunit
// ?.toString()
// .split('.')
// .last ??
// 'unit',
// productsumprice: total,
// tax: tax,
// discount: dis,
// tenantfee: delivery,
// ))
// .toList();
//
// final order = CreateOrder(
// applocationid:
// tenant?.applocationid,
// applocation: '',
// tenantid: cartCtrl.cartItems.first
// .product.tenantid,
// partnerid: 60,
// locationid: int.tryParse(
// cartCtrl.pastLocationId ?? ""),
// categoryid: cartCtrl.cartItems.first
// .product.categoryid,
// subcategoryid: cartCtrl
// .cartItems.first.product.subcategoryid,
// moduleid: 6,
// configid: 1,
// orderdate: _fmtNow(), // ✅ FIXED
// deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created',
// deliverycharge: tenant?.tenantcharge,
// customerid: int.tryParse(
// loc.customerid ?? '0'),
// pickupcustomer:
// tenant?.tenantname ?? '',
// pickupcontactno:
// tenant?.primarycontact,
// pickupaddress: tenant?.address,
// pickuplocationid: tenant?.locationid,
// pickupcity: tenant?.city,
// deliverycustomer: name,
// deliverycontactno: num,
// deliveryaddress: loc.address,
// deliverylocationid: loc.locationid,
// deliverylat: loc.latitude ?? '',
// deliverylong: loc.longitude ?? '',
// paymenttype: 42,
// items: items,
// );
//
//
// print(order.toJson());
// print(items.map((i) => i.toJson()).toList());
// print('orders');
// // ✅ Navigate to countdown page
// Get.to(() => OrderCountdownPage(
// order: order,
// orderCtrl: orderCtrl,
// cartCtrl: cartCtrl,
// customerName: name,
// ));
//
// },
// ),
// ),
//
// const SizedBox(height: 10),
//
// // ── PAY ONLINE (RAZORPAY) ─────────────────────
// Container(
// padding: EdgeInsets.symmetric(
// horizontal: 14, vertical: 16),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(16),
// border: Border.all(
// color: Colors.grey.shade300,
// width: 1.5,
// ),
// boxShadow: [
// BoxShadow(
// color:
// Colors.black.withOpacity(0.04),
// blurRadius: 8,
// offset: Offset(0, 4),
// ),
// ],
// ),
// child: GestureDetector(
// behavior: HitTestBehavior.opaque, // ← ADD THIS
// child: Row(children: [
// Container(
// padding: EdgeInsets.all(10),
// decoration: BoxDecoration(
// color: Colors.deepPurple
// .withOpacity(0.1),
// shape: BoxShape.circle,
// ),
// child: Icon(
// Icons.account_balance_wallet,
// color: Colors.deepPurple,
// size: 22,
// ),
// ),
// SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// ReusableTextWidget(
// text: 'Pay Online',
// color: Colors.black,
// fontFamily:
// FontConstants.fontFamily,
// fontSize: 15,
// fontWeight: FontWeight.w600,
// ),
// SizedBox(height: 4),
// Text(
// 'UPI / Cards / Net Banking',
// style: TextStyle(
// fontSize: 12,
// color: Colors.grey.shade600,
// ),
// ),
// ],
// ),
// ),
// Icon(
// Icons.arrow_forward_ios,
// size: 18,
// color: Colors.grey.shade400,
// ),
// ]),
// onTap: () {
// Navigator.pop(ctx);
//
// final Razorpay razorpay = Razorpay();
//
// razorpay.on(
// Razorpay.EVENT_PAYMENT_SUCCESS,
// (PaymentSuccessResponse
// response) async {
// razorpay.clear();
// if (!context.mounted) return;
//
// final items = cartCtrl.cartItems
// .map((i) => OrderItem(
// productid:
// i.product.productid,
// productname:
// i.product.productname,
// productdescription: '',
// orderqty: i.quantity,
// price: i.product
// .productcost
// ?.toDouble() ??
// 0,
// unitid: int.tryParse(i
// .product.unitvalue
// .toString()) ??
// 1,
// unitname: i.product
// .productunit
// ?.toString()
// .split('.')
// .last ??
// 'unit',
// productsumprice: total,
// tax: tax,
// discount: dis,
// tenantfee: delivery,
// ))
// .toList();
//
// final order = CreateOrder(
// applocationid:
// tenant?.applocationid,
// applocation: '',
// tenantid: cartCtrl.cartItems.first
// .product.tenantid,
// partnerid: 60,
// locationid: int.tryParse(
// cartCtrl.pastLocationId ??
// ""),
// categoryid: cartCtrl
// .cartItems.first.product.categoryid,
// subcategoryid: cartCtrl
// .cartItems.first.product.subcategoryid,
// moduleid: 6,
// configid: 1,
// orderdate: _fmtNow(), // ✅ FIXED
// deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created',
// deliverycharge:
// tenant?.tenantcharge,
// customerid: int.tryParse(
// loc.customerid ?? '0'),
// pickupcustomer:
// tenant?.tenantname ?? '',
// pickupcontactno:
// tenant?.primarycontact,
// pickupaddress: tenant?.address,
// pickuplocationid:
// tenant?.locationid,
// pickupcity: tenant?.city,
// deliverycustomer: name,
// deliverycontactno: num,
// deliveryaddress: loc.address,
// deliverylocationid:
// loc.locationid,
// deliverylat: loc.latitude ?? '',
// deliverylong: loc.longitude ?? '',
// paymenttype: 2,
// items: items,
// );
//
// await orderCtrl.createOrder(
// CreateOrderRequest(
// orders: order));
//
// if (!context.mounted) return;
//
// if (!orderCtrl.isLoading.value) {
// Get.to(OrderSuccessView());
// print(order);
// print(items);
// cartCtrl.clearCart();
// await cartCtrl.notifyAdmin(
// title:
// 'Nearle Deals - New Order',
// body:
// 'A new order has been placed successfully by $name!');
// }
// });
//
// razorpay.on(
// Razorpay.EVENT_PAYMENT_ERROR,
// (PaymentFailureResponse
// response) {
// razorpay.clear();
// if (!context.mounted) return;
// ScaffoldMessenger.of(context)
// .showSnackBar(
// SnackBar(
// content: Text(response.message ??
// 'Something went wrong'),
// backgroundColor: Colors.red,
// behavior:
// SnackBarBehavior.floating,
// ),
// );
// });
//
// razorpay.on(
// Razorpay.EVENT_EXTERNAL_WALLET,
// (ExternalWalletResponse
// response) {
// razorpay.clear();
// });
//
// razorpay.open({
// 'key': 'rzp_test_pUSj1Pz4LFLb',
// 'amount': (total * 100).toInt(),
// 'name': 'Nearle Deals',
// 'description': 'Order Payment',
// 'prefill': {
// 'contact': num,
// 'name': name,
// },
// 'external': {
// 'wallets': ['paytm']
// },
// 'theme': {
// 'full_screen': true,
// },
// });
// },
// ),
// ),
//
// const SizedBox(height: 10),
// ListTile(
// title: ReusableTextWidget(
// text: 'Cancel',
// color: Colors.red,
// fontFamily: FontConstants.fontFamily,
// fontSize: 18,
// fontWeight: FontWeight.bold,
// textAlign: TextAlign.center,
// overflow: TextOverflow.ellipsis,
// maxLines: 1,
// ),
// onTap: () => Navigator.pop(ctx),
// ),
// ],
// ),
// ),
// ),
// );
// } else {
// showDialog(
// context: Get.context!,
// builder: (_) => AlertDialog(
// backgroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(16)),
// title: Column(
// children: [
// Lottie.asset('assets/lotties/location.json',
// height: 100, repeat: true),
// const SizedBox(height: 8),
// const Text('Not Serviceable',
// textAlign: TextAlign.center,
// style: TextStyle(
// fontWeight: FontWeight.w600,
// fontSize: 17,
// color: Colors.black87)),
// ],
// ),
// content: const Text(
// 'Sorry, we don\'t serve this area yet.',
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: 15, color: Colors.black54)),
// actionsAlignment: MainAxisAlignment.center,
// actions: [
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.redAccent,
// shape: RoundedRectangleBorder(
// borderRadius:
// BorderRadius.circular(10))),
// onPressed: () =>
// Navigator.of(context).pop(),
// child: const Padding(
// padding: EdgeInsets.symmetric(
// horizontal: 20, vertical: 8),
// child: Text('OK',
// style: TextStyle(
// color: Colors.white, fontSize: 15)),
// ),
// ),
// ],
// ),
// );
// }
// }
// },
// child: Container(
// width: double.infinity,
// padding: const EdgeInsets.symmetric(vertical: 16),
// decoration: BoxDecoration(
// gradient: LinearGradient(
// colors: [
// ColorConstants.primaryColor,
// ColorConstants.primaryColor.withOpacity(0.8)
// ],
// begin: Alignment.topLeft,
// end: Alignment.bottomRight,
// ),
// borderRadius: BorderRadius.circular(14),
// ),
// child: Obx(() {
// final oc = Get.find<OrderController>();
// return oc.isLoading.value
// ? const Center(
// child: Text('Please wait...',
// style: TextStyle(
// color: Colors.white,
// fontSize: 16,
// fontWeight: FontWeight.w500)),
// )
// : Center(
// child: Text('Proceed to Checkout',
// style: TextStyle(
// color: Colors.white,
// fontSize: 16,
// fontWeight: FontWeight.w500)),
// );
// }),
// ),
// )
],
),
),
],
);
}
Widget _qtyButton({
required IconData icon,
required VoidCallback onTap,
Gradient? gradient,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 32,
height: 32,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: ColorConstants.primaryColor.withOpacity(0.25),
),
gradient: gradient,
color: gradient == null ? Colors.white : null,
),
child: Icon(icon, size: 18, color: Colors.black87),
),
);
}
Widget _priceRow(String title, double value, {bool isBold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: LayoutBuilder(
builder: (context, constraints) {
final baseWidth = 392.0;
final screenWidth = MediaQuery.of(context).size.width;
final scale = screenWidth / baseWidth;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontWeight: isBold ? FontWeight.w700 : FontWeight.w500,
fontSize: 13 * scale,
color: Colors.black87,
),
),
Text(
'${value.toStringAsFixed(2)}',
style: TextStyle(
fontWeight: isBold ? FontWeight.w800 : FontWeight.w600,
fontSize: (isBold ? 14 : 13) * scale,
color: isBold ? Colors.black : Colors.black87,
),
),
],
);
},
),
);
}
Widget _buildProductItem(Product product) {
return Container(
width: 70,
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
clipBehavior: Clip.none,
children: [
Container(
height: 70,
width: 70,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(14),
image: product.productimage != null
? DecorationImage(
image: NetworkImage(product.productimage!),
fit: BoxFit.cover,
)
: null,
),
),
Positioned(
bottom: -8,
right: -8,
child: GestureDetector(
onTap: () {
// TODO: Add to cart
},
child: Container(
height: 26,
width: 26,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
blurRadius: 6,
),
],
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 18,
),
),
),
),
],
),
const SizedBox(height: 8),
Text(
product.productname ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}