Update codebase
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
// food_cart_page.dart — مراجعة السلة، عرض السعر الموقّع، وإنشاء الطلب
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
import '../../main.dart';
|
||||
import '../../views/widgets/error_snakbar.dart';
|
||||
import '../../controller/food/food_controller.dart';
|
||||
import '../../controller/food/food_models.dart';
|
||||
import '../widgets/my_scafold.dart';
|
||||
import 'food_order_tracking_page.dart';
|
||||
|
||||
class FoodCartPage extends StatefulWidget {
|
||||
const FoodCartPage({super.key});
|
||||
|
||||
@override
|
||||
State<FoodCartPage> createState() => _FoodCartPageState();
|
||||
}
|
||||
|
||||
class _FoodCartPageState extends State<FoodCartPage> {
|
||||
final TextEditingController _addressController = TextEditingController();
|
||||
final TextEditingController _noteController = TextEditingController();
|
||||
String _paymentMethod = 'wallet';
|
||||
Position? _position;
|
||||
bool _isFetchingLocation = false;
|
||||
|
||||
bool get _isAr => box.read(BoxName.lang) == 'ar';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchLocation();
|
||||
}
|
||||
|
||||
Future<void> _fetchLocation() async {
|
||||
setState(() => _isFetchingLocation = true);
|
||||
try {
|
||||
final permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
await Geolocator.requestPermission();
|
||||
}
|
||||
_position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
||||
} catch (_) {
|
||||
_position = null;
|
||||
}
|
||||
if (mounted) setState(() => _isFetchingLocation = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<FoodController>(
|
||||
builder: (c) => MyScafolld(
|
||||
title: _isAr ? 'السلة' : 'Cart',
|
||||
isleading: true,
|
||||
body: [
|
||||
c.cartLines.isEmpty ? _emptyCart() : _cartContent(c),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _emptyCart() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.shopping_cart_outlined, size: 72, color: AppColor.grayColor),
|
||||
const SizedBox(height: 16),
|
||||
Text(_isAr ? 'سلتك فارغة' : 'Your cart is empty', style: AppStyle.title),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _cartContent(FoodController c) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
...c.cartLines.map((line) => _cartLineTile(c, line)),
|
||||
const SizedBox(height: 16),
|
||||
_summaryRow(_isAr ? 'المجموع الفرعي' : 'Subtotal', foodFormatPrice(c.cartTotal)),
|
||||
if (c.currentQuote != null) ...[
|
||||
_summaryRow(_isAr ? 'رسوم التوصيل' : 'Delivery fee', foodFormatPrice(c.currentQuote!.deliveryFee)),
|
||||
const Divider(),
|
||||
_summaryRow(_isAr ? 'الإجمالي' : 'Total', foodFormatPrice(c.currentQuote!.grandTotal), bold: true),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: _addressController,
|
||||
decoration: InputDecoration(
|
||||
labelText: _isAr ? 'عنوان التوصيل' : 'Delivery address',
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
suffixIcon: _isFetchingLocation
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
)
|
||||
: Icon(_position != null ? Icons.my_location_rounded : Icons.location_off_rounded,
|
||||
color: _position != null ? AppColor.greenColor : AppColor.redColor),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _noteController,
|
||||
decoration: InputDecoration(
|
||||
labelText: _isAr ? 'ملاحظات (اختياري)' : 'Notes (optional)',
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_paymentMethodPicker(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 8, 16, MediaQuery.of(context).padding.bottom + 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 52,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.primaryColor,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
),
|
||||
onPressed: c.isQuoting || c.isPlacingOrder ? null : () => _onCheckoutPressed(c),
|
||||
child: c.isPlacingOrder
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: Text(
|
||||
c.currentQuote == null
|
||||
? (_isAr ? 'احسب السعر' : 'Get Quote')
|
||||
: '${_isAr ? "اطلب الآن" : "Place Order"} · ${foodFormatPrice(c.currentQuote!.grandTotal)}',
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCheckoutPressed(FoodController c) async {
|
||||
if (c.currentQuote == null) {
|
||||
await c.refreshQuote();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_addressController.text.trim().isEmpty) {
|
||||
mySnackbarWarning(_isAr ? 'أدخل عنوان التوصيل' : 'Enter a delivery address');
|
||||
return;
|
||||
}
|
||||
if (_position == null) {
|
||||
mySnackbarWarning(_isAr ? 'تعذّر تحديد موقعك — فعّل خدمة الموقع' : 'Could not get your location — enable location services');
|
||||
return;
|
||||
}
|
||||
|
||||
final orderId = await c.placeOrder(
|
||||
deliveryAddress: _addressController.text.trim(),
|
||||
deliveryLat: _position!.latitude,
|
||||
deliveryLng: _position!.longitude,
|
||||
paymentMethod: _paymentMethod,
|
||||
customerNote: _noteController.text.trim(),
|
||||
);
|
||||
|
||||
if (orderId != null) {
|
||||
Get.offAll(() => FoodOrderTrackingPage(orderId: orderId));
|
||||
}
|
||||
}
|
||||
|
||||
Widget _cartLineTile(FoodController c, FoodCartLine line) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
color: AppColor.cardColor,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
side: BorderSide(color: AppColor.borderColor),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(line.item.nameAr, style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
Text(foodFormatPrice(line.lineTotal), style: AppStyle.subtitle.copyWith(color: AppColor.accentColor)),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove_circle_outline_rounded),
|
||||
onPressed: () => c.updateQuantity(line.lineKey, line.quantity - 1),
|
||||
),
|
||||
Text(line.quantity.toString(), style: AppStyle.title),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add_circle_outline_rounded),
|
||||
onPressed: () => c.updateQuantity(line.lineKey, line.quantity + 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _summaryRow(String label, String value, {bool bold = false}) {
|
||||
final style = bold
|
||||
? AppStyle.title.copyWith(fontWeight: FontWeight.bold, fontSize: 18)
|
||||
: AppStyle.subtitle;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [Text(label, style: style), Text(value, style: style)],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _paymentMethodPicker() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(_isAr ? 'طريقة الدفع' : 'Payment method', style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: 'wallet',
|
||||
groupValue: _paymentMethod,
|
||||
title: Text(_isAr ? 'المحفظة' : 'Wallet'),
|
||||
onChanged: (v) => setState(() => _paymentMethod = v!),
|
||||
),
|
||||
RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: 'cash',
|
||||
groupValue: _paymentMethod,
|
||||
title: Text(_isAr ? 'نقداً عند الاستلام' : 'Cash on delivery'),
|
||||
onChanged: (v) => setState(() => _paymentMethod = v!),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user