payfrit-app/lib/models/cart.dart
John Mizerek 4ebfbbc03b Add app branding, splash screen, and fix modifier validation
App Branding:
- New Payfrit app icon (blue gradient with white P logo)
- Custom splash screen with animated logo and tagline
- Android adaptive icon with foreground/background layers
- iOS app icons for all required sizes
- Updated launch screen backgrounds with brand colors

Splash Screen Experience:
- Animated logo with fade-in effect
- "Order. Pay. Go." tagline with staggered animations
- Restaurant list display with search functionality
- Beacon scanning integration from splash
- Smooth transition to menu browse

Modifier Validation Fix:
- Fixed validation to check ALL modifier groups (not just selected items)
- Ensures required selections are enforced for nested modifier groups
- Modifier groups with children now always get validated
- Prevents adding items without required selections

Cart Improvements:
- Better modifier display in cart items
- Improved line item quantity handling
- Enhanced order submission flow

Beacon Scanning:
- Improved beacon detection reliability
- Better handling of multiple beacons
- Enhanced error messaging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 01:55:13 -08:00

238 lines
7.3 KiB
Dart

class Cart {
final int orderId;
final String orderUuid;
final int userId;
final int businessId;
final double businessDeliveryMultiplier;
final int orderTypeId;
final double deliveryFee;
final int statusId;
final int? addressId;
final int? paymentId;
final String? remarks;
final DateTime addedOn;
final DateTime lastEditedOn;
final DateTime? submittedOn;
final int servicePointId;
final List<OrderLineItem> lineItems;
const Cart({
required this.orderId,
required this.orderUuid,
required this.userId,
required this.businessId,
required this.businessDeliveryMultiplier,
required this.orderTypeId,
required this.deliveryFee,
required this.statusId,
this.addressId,
this.paymentId,
this.remarks,
required this.addedOn,
required this.lastEditedOn,
this.submittedOn,
required this.servicePointId,
required this.lineItems,
});
factory Cart.fromJson(Map<String, dynamic> json) {
final order = (json["ORDER"] ?? json["Order"]) as Map<String, dynamic>? ?? {};
final lineItemsJson = (json["ORDERLINEITEMS"] ?? json["OrderLineItems"]) as List? ?? [];
return Cart(
orderId: _parseInt(order["OrderID"]) ?? 0,
orderUuid: (order["OrderUUID"] as String?) ?? "",
userId: _parseInt(order["OrderUserID"]) ?? 0,
businessId: _parseInt(order["OrderBusinessID"]) ?? 0,
businessDeliveryMultiplier: _parseDouble(order["OrderBusinessDeliveryMultiplier"]) ?? 0.0,
orderTypeId: _parseInt(order["OrderTypeID"]) ?? 0,
deliveryFee: _parseDouble(order["OrderDeliveryFee"]) ?? 0.0,
statusId: _parseInt(order["OrderStatusID"]) ?? 0,
addressId: _parseInt(order["OrderAddressID"]),
paymentId: _parseInt(order["OrderPaymentID"]),
remarks: order["OrderRemarks"] as String?,
addedOn: _parseDateTime(order["OrderAddedOn"]),
lastEditedOn: _parseDateTime(order["OrderLastEditedOn"]),
submittedOn: _parseDateTime(order["OrderSubmittedOn"]),
servicePointId: _parseInt(order["OrderServicePointID"]) ?? 0,
lineItems: lineItemsJson
.map((item) => OrderLineItem.fromJson(item as Map<String, dynamic>))
.toList(),
);
}
static int? _parseInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String) {
if (value.isEmpty) return null;
return int.tryParse(value);
}
return null;
}
static double? _parseDouble(dynamic value) {
if (value == null) return null;
if (value is double) return value;
if (value is num) return value.toDouble();
if (value is String) {
if (value.isEmpty) return null;
return double.tryParse(value);
}
return null;
}
static DateTime _parseDateTime(dynamic value) {
if (value == null) return DateTime.now();
if (value is DateTime) return value;
if (value is String) {
try {
return DateTime.parse(value);
} catch (e) {
return DateTime.now();
}
}
return DateTime.now();
}
double get subtotal {
// Sum all non-deleted line items (root items and modifiers)
// Root items have their base price, modifiers have their add-on prices
// Modifier prices are multiplied by the root item's quantity
double total = 0.0;
// First, get all root items and their prices
final rootItems = lineItems.where((item) => !item.isDeleted && item.parentOrderLineItemId == 0);
for (final rootItem in rootItems) {
total += rootItem.price * rootItem.quantity;
// Add all modifier prices for this root item (recursively)
total += _sumModifierPrices(rootItem.orderLineItemId, rootItem.quantity);
}
return total;
}
/// Recursively sum modifier prices for a parent line item
double _sumModifierPrices(int parentOrderLineItemId, int rootQuantity) {
double total = 0.0;
final children = lineItems.where(
(item) => !item.isDeleted && item.parentOrderLineItemId == parentOrderLineItemId
);
for (final child in children) {
// Modifier price is multiplied by root item quantity
total += child.price * rootQuantity;
// Recursively add grandchildren modifier prices
total += _sumModifierPrices(child.orderLineItemId, rootQuantity);
}
return total;
}
// Only include delivery fee for delivery orders (orderTypeId == 3)
// Sales tax rate (8.25% for California - can be made configurable per business later)
static const double taxRate = 0.0825;
// Calculate sales tax on subtotal
double get tax => subtotal * taxRate;
double get total => subtotal + tax + (orderTypeId == 3 ? deliveryFee : 0);
int get itemCount {
return lineItems
.where((item) => !item.isDeleted && item.parentOrderLineItemId == 0)
.fold(0, (sum, item) => sum + item.quantity);
}
}
class OrderLineItem {
final int orderLineItemId;
final int parentOrderLineItemId;
final int orderId;
final int itemId;
final int statusId;
final double price;
final int quantity;
final String? remark;
final bool isDeleted;
final DateTime addedOn;
const OrderLineItem({
required this.orderLineItemId,
required this.parentOrderLineItemId,
required this.orderId,
required this.itemId,
required this.statusId,
required this.price,
required this.quantity,
this.remark,
required this.isDeleted,
required this.addedOn,
});
factory OrderLineItem.fromJson(Map<String, dynamic> json) {
return OrderLineItem(
orderLineItemId: _parseInt(json["OrderLineItemID"]) ?? 0,
parentOrderLineItemId: _parseInt(json["OrderLineItemParentOrderLineItemID"]) ?? 0,
orderId: _parseInt(json["OrderLineItemOrderID"]) ?? 0,
itemId: _parseInt(json["OrderLineItemItemID"]) ?? 0,
statusId: _parseInt(json["OrderLineItemStatusID"]) ?? 0,
price: _parseDouble(json["OrderLineItemPrice"]) ?? 0.0,
quantity: _parseInt(json["OrderLineItemQuantity"]) ?? 0,
remark: json["OrderLineItemRemark"] as String?,
isDeleted: _parseBool(json["OrderLineItemIsDeleted"]),
addedOn: _parseDateTime(json["OrderLineItemAddedOn"]),
);
}
static int? _parseInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String) {
if (value.isEmpty) return null;
return int.tryParse(value);
}
return null;
}
static double? _parseDouble(dynamic value) {
if (value == null) return null;
if (value is double) return value;
if (value is num) return value.toDouble();
if (value is String) {
if (value.isEmpty) return null;
return double.tryParse(value);
}
return null;
}
static bool _parseBool(dynamic value) {
if (value == null) return false;
if (value is bool) return value;
if (value is num) return value != 0;
if (value is String) {
final lower = value.toLowerCase();
return lower == "true" || lower == "1";
}
return false;
}
static DateTime _parseDateTime(dynamic value) {
if (value == null) return DateTime.now();
if (value is DateTime) return value;
if (value is String) {
try {
return DateTime.parse(value);
} catch (e) {
return DateTime.now();
}
}
return DateTime.now();
}
bool get isRootItem => parentOrderLineItemId == 0;
bool get isModifier => parentOrderLineItemId != 0;
}