Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/features/cart/domain/models/cart_item_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class CartItemData {
required this.normalPrice,
required this.offerPrice,
required this.suitableFor,
required this.businessId,
this.quantity = 1,
});
final String id;
Expand All @@ -16,11 +17,13 @@ class CartItemData {
final double normalPrice;
final double offerPrice;
final List<DietType> suitableFor;
final String businessId;
int quantity;

CartItemData copyWith({
int? quantity,
List<DietType>? suitableFor,
String? businessId,
}) {
return CartItemData(
id: id,
Expand All @@ -30,6 +33,7 @@ class CartItemData {
offerPrice: offerPrice,
quantity: quantity ?? this.quantity,
suitableFor: suitableFor ?? this.suitableFor,
businessId: businessId ?? this.businessId,
);
}
}
59 changes: 57 additions & 2 deletions lib/features/cart/presentation/bloc/cart_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@ import 'package:eco_bites/core/blocs/resettable_mixin.dart';
import 'package:eco_bites/features/cart/domain/models/cart_item_data.dart';
import 'package:eco_bites/features/cart/presentation/bloc/cart_event.dart';
import 'package:eco_bites/features/cart/presentation/bloc/cart_state.dart';
import 'package:eco_bites/features/orders/data/models/order_model.dart';
import 'package:eco_bites/features/orders/domain/entities/order.dart';
import 'package:eco_bites/features/orders/presentation/bloc/order_bloc.dart';
import 'package:eco_bites/features/orders/presentation/bloc/order_event.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CartBloc extends Bloc<CartEvent, CartState>
with ResettableMixin<CartEvent, CartState> {
CartBloc(List<CartItemData> initialItems)
: super(CartState(items: initialItems)) {
CartBloc({
required this.orderBloc,
required List<CartItemData> initialItems,
}) : super(CartState(items: initialItems)) {
on<AddToCart>(_onAddToCart);
on<CartItemQuantityChanged>(_onCartItemQuantityChanged);
on<CartItemRemoved>(_onCartItemRemoved);
on<ClearCart>(_onClearCart);
on<CompletePurchase>(_onCompletePurchase);
on<PurchaseCartEvent>(_onPurchaseCart);
}

final OrderBloc orderBloc;

void _onClearCart(ClearCart event, Emitter<CartState> emit) {
emit(
const CartState(items: <CartItemData>[]),
Expand Down Expand Up @@ -74,6 +83,52 @@ class CartBloc extends Bloc<CartEvent, CartState>
emit(CartState(items: updatedItems));
}

Future<void> _onPurchaseCart(
PurchaseCartEvent event,
Emitter<CartState> emit,
) async {
if (state.items.isEmpty) {
emit(state.copyWith(items: state.items, error: 'Cart is empty'));
return;
}

try {
final List<OrderItemModel> orderItems =
state.items.map((CartItemData cartItem) {
return OrderItemModel(
id: cartItem.id,
name: cartItem.title,
quantity: cartItem.quantity,
price: cartItem.offerPrice,
);
}).toList();

final double totalAmount = state.items.fold<double>(
0,
(double sum, CartItemData item) =>
sum + (item.offerPrice * item.quantity),
);

final OrderModel order = OrderModel(
id: '', // Will be set by Firestore
businessId: state.items.first.businessId,
items: orderItems,
totalAmount: totalAmount,
status: OrderStatus.pending,
createdAt: DateTime.now(),
);

orderBloc.add(CreateOrderEvent(order: order));

// Clear cart after successful purchase
emit(const CartState(items: <CartItemData>[]));
} catch (e) {
emit(
state.copyWith(items: state.items, error: 'Failed to create order: $e'),
);
}
}

@override
void reset() {
// ignore: invalid_use_of_visible_for_testing_member
Expand Down
4 changes: 4 additions & 0 deletions lib/features/cart/presentation/bloc/cart_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ class AddToCart extends CartEvent {
class ClearCart extends CartEvent {}

class CompletePurchase extends CartEvent {}

class PurchaseCartEvent extends CartEvent {
const PurchaseCartEvent();
}
15 changes: 11 additions & 4 deletions lib/features/cart/presentation/bloc/cart_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@ import 'package:eco_bites/features/cart/domain/models/cart_item_data.dart';
import 'package:equatable/equatable.dart';

class CartState extends Equatable {
const CartState({required this.items});
const CartState({
required this.items,
this.error,
});

final List<CartItemData> items;
final String? error;

// Calculate subtotal as the sum of offer prices times quantity for each item
double get subtotal => items.fold(
0.0,
(double sum, CartItemData item) =>
sum + (item.offerPrice * item.quantity),
);

@override
List<Object?> get props => <Object>[items];
List<Object?> get props => <Object?>[items, error];

CartState copyWith({List<CartItemData>? items}) {
CartState copyWith({
List<CartItemData>? items,
String? error,
}) {
return CartState(
items: items ?? this.items,
error: error,
);
}
}
20 changes: 15 additions & 5 deletions lib/features/cart/presentation/screens/cart_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,22 @@ class CartScreenContent extends StatelessWidget {
),
ElevatedButton(
onPressed: () {
context.read<CartBloc>().add(CompletePurchase());
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Purchase Completed')),
);
if (state.items.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cart is empty'),
),
);
} else {
context.read<CartBloc>().add(const PurchaseCartEvent());
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Order placed successfully!'),
),
);
}
},
child: const Text('Complete Purchase'),
child: const Text('Purchase'),
),
],
),
Expand Down
1 change: 1 addition & 0 deletions lib/features/food/domain/entities/offer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ extension OfferExtensions on Offer {
offerPrice: offerPrice,
imageUrl: imageUrl,
suitableFor: suitableFor,
businessId: businessId,
);
}
}
46 changes: 39 additions & 7 deletions lib/features/orders/data/datasources/order_remote_data_source.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:eco_bites/core/error/exceptions.dart';
import 'package:eco_bites/core/utils/user_util.dart';
import 'package:eco_bites/features/orders/data/models/order_model.dart';
import 'package:eco_bites/features/orders/domain/entities/order.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:logger/logger.dart';

abstract class OrderRemoteDataSource {
Expand All @@ -14,17 +14,14 @@ abstract class OrderRemoteDataSource {
class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
const OrderRemoteDataSourceImpl({
required FirebaseFirestore firestore,
required FirebaseAuth auth,
}) : _firestore = firestore,
_auth = auth;
}) : _firestore = firestore;

final FirebaseFirestore _firestore;
final FirebaseAuth _auth;

@override
Future<List<OrderModel>> getOrders() async {
try {
final String? userId = _auth.currentUser?.uid;
final String? userId = await getUserId();
if (userId == null) {
throw const AuthException('User not authenticated');
}
Expand Down Expand Up @@ -66,6 +63,41 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {

@override
Future<void> createOrder(OrderModel order) async {
await _firestore.collection('orders').doc().set(order.toMap());
try {
final String? userId = await getUserId();
if (userId == null) {
throw const AuthException('User not authenticated');
}

// validate if the order items are not empty
if (order.items.isEmpty) {
Logger().e('Order items cannot be empty');
// do not throw, snackbar will handle this
return;
}

// Get business name from Firestore
final DocumentSnapshot<Map<String, dynamic>> businessDoc =
await _firestore
.collection('foodBusiness')
.doc(order.businessId)
.get();

if (!businessDoc.exists) {
Logger().e('Business not found');
}

final String businessName = businessDoc.data()?['name'] as String;

// Create the order with the fetched business name
final Map<String, dynamic> orderData = order.toMap()
..['userId'] = userId
..['businessName'] = businessName;

await _firestore.collection('orders').doc().set(orderData);
} catch (e) {
Logger().e('Error creating order: $e');
throw AuthException('Failed to create order: $e');
}
}
}
6 changes: 4 additions & 2 deletions lib/features/orders/data/models/order_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ class OrderModel extends order_entity.Order {
const OrderModel({
required super.id,
required super.businessId,
required super.businessName,
String? businessName,
required super.items,
required super.totalAmount,
required super.status,
required super.createdAt,
super.completedAt,
});
}) : super(
businessName: businessName ?? '',
);

factory OrderModel.fromMap(Map<String, dynamic> map, String id) {
return OrderModel(
Expand Down
16 changes: 16 additions & 0 deletions lib/features/orders/data/repositories/order_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,20 @@ class OrderRepositoryImpl implements OrderRepository {
);
}
}

@override
Future<Either<Failure, void>> createOrder(OrderModel order) async {
if (await networkInfo.isConnected) {
try {
await remoteDataSource.createOrder(order);
return const Right<Failure, void>(null);
} on AuthException catch (e) {
return Left<Failure, void>(AuthFailure(e.message));
}
} else {
return const Left<Failure, void>(
NetworkFailure('No internet connection'),
);
}
}
}
2 changes: 2 additions & 0 deletions lib/features/orders/domain/repositories/order_repository.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:dartz/dartz.dart';
import 'package:eco_bites/core/error/failures.dart';
import 'package:eco_bites/features/orders/data/models/order_model.dart';
import 'package:eco_bites/features/orders/domain/entities/order.dart'
as entities;

Expand All @@ -9,4 +10,5 @@ abstract class OrderRepository {
String orderId,
entities.OrderStatus newStatus,
);
Future<Either<Failure, void>> createOrder(OrderModel order);
}
24 changes: 24 additions & 0 deletions lib/features/orders/domain/usecases/create_order.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:dartz/dartz.dart';
import 'package:eco_bites/core/error/failures.dart';
import 'package:eco_bites/features/orders/data/models/order_model.dart';
import 'package:eco_bites/features/orders/domain/repositories/order_repository.dart';
import 'package:equatable/equatable.dart';

class CreateOrder {
const CreateOrder(this.repository);

final OrderRepository repository;

Future<Either<Failure, void>> call(CreateOrderParams params) async {
return repository.createOrder(params.order);
}
}

class CreateOrderParams extends Equatable {
const CreateOrderParams({required this.order});

final OrderModel order;

@override
List<Object?> get props => <Object?>[order];
}
Loading
Loading