-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Conflicts: # ecommerce_app/lib/src/features/orders/presentation/orders_list/orders_list_screen.dart
- Loading branch information
Showing
6 changed files
with
157 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
ecommerce_app/lib/src/features/checkout/application/fake_checkout_service.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import 'package:ecommerce_app/src/features/authentication/data/fake_auth_repository.dart'; | ||
import 'package:ecommerce_app/src/features/cart/data/remote/remote_cart_repository.dart'; | ||
import 'package:ecommerce_app/src/features/cart/domain/cart.dart'; | ||
import 'package:ecommerce_app/src/features/orders/data/fake_orders_repository.dart'; | ||
import 'package:ecommerce_app/src/features/orders/domain/order.dart'; | ||
import 'package:ecommerce_app/src/features/products/data/fake_products_repository.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
/// A fake checkout service that doesn't process real payments. | ||
class FakeCheckoutService { | ||
FakeCheckoutService(this.ref); | ||
final Ref ref; | ||
|
||
/// Temporary client-side logic for placing an order. | ||
/// Part of this logic should run on the server, so that we can: | ||
/// - setup a payment intent | ||
/// - show the payment UI | ||
/// - process the payment and fullfill the order | ||
/// The server-side logic will be covered in course #2 | ||
Future<void> placeOrder() async { | ||
final authRepository = ref.read(authRepositoryProvider); | ||
final remoteCartRepository = ref.read(remoteCartRepositoryProvider); | ||
final ordersRepository = ref.read(ordersRepositoryProvider); | ||
// * Assertion operator is ok here since this method is only called from | ||
// * a place where the user is signed in | ||
final uid = authRepository.currentUser!.uid; | ||
// 1. Get the cart object | ||
final cart = await remoteCartRepository.fetchCart(uid); | ||
final total = _totalPrice(cart); | ||
// * If we want to make this code more testable, a DateTime builder | ||
// * should be injected as a dependency | ||
final orderDate = DateTime.now(); | ||
// * The orderId is a unique string that could be generated with the UUID | ||
// * package. Since this is a fake service, we just derive it from the date. | ||
final orderId = orderDate.toIso8601String(); | ||
// 2. Create an order | ||
final order = Order( | ||
id: orderId, | ||
userId: uid, | ||
items: cart.items, | ||
orderStatus: OrderStatus.confirmed, | ||
orderDate: orderDate, | ||
total: total, | ||
); | ||
// 3. Save it using the repository | ||
await ordersRepository.addOrder(uid, order); | ||
// 4. Empty the cart | ||
await remoteCartRepository.setCart(uid, const Cart()); | ||
} | ||
|
||
// Helper method to calculate the total price | ||
double _totalPrice(Cart cart) { | ||
if (cart.items.isEmpty) { | ||
return 0.0; | ||
} | ||
final producsRepository = ref.read(productsRepositoryProvider); | ||
return cart.items.entries | ||
// first extract quantity * price for each item | ||
.map((entry) => | ||
entry.value * // quantity | ||
producsRepository.getProduct(entry.key)!.price) // price | ||
// then add them up | ||
.reduce((value, element) => value + element); | ||
} | ||
} | ||
|
||
final checkoutServiceProvider = Provider<FakeCheckoutService>((ref) { | ||
return FakeCheckoutService(ref); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
ecommerce_app/lib/src/features/orders/application/user_orders_provider.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import 'package:ecommerce_app/src/features/authentication/data/fake_auth_repository.dart'; | ||
import 'package:ecommerce_app/src/features/orders/data/fake_orders_repository.dart'; | ||
import 'package:ecommerce_app/src/features/orders/domain/order.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
/// Watch the list of user orders | ||
/// NOTE: Only watch this provider if the user is signed in. | ||
final userOrdersProvider = StreamProvider.autoDispose<List<Order>>((ref) { | ||
final user = ref.watch(authStateChangesProvider).value; | ||
if (user != null) { | ||
final ordersRepository = ref.watch(ordersRepositoryProvider); | ||
return ordersRepository.watchUserOrders(user.uid); | ||
} else { | ||
// If the user is null, just return an empty screen. | ||
return const Stream.empty(); | ||
} | ||
}); |
39 changes: 39 additions & 0 deletions
39
ecommerce_app/lib/src/features/orders/data/fake_orders_repository.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import 'package:ecommerce_app/src/features/orders/domain/order.dart'; | ||
import 'package:ecommerce_app/src/utils/delay.dart'; | ||
import 'package:ecommerce_app/src/utils/in_memory_store.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
class FakeOrdersRepository { | ||
FakeOrdersRepository({this.addDelay = true}); | ||
final bool addDelay; | ||
|
||
/// A map of all the orders placed by each user, where: | ||
/// - key: user ID | ||
/// - value: list of orders for that user | ||
final _orders = InMemoryStore<Map<String, List<Order>>>({}); | ||
|
||
// A stream that returns all the orders for a given user, ordered by date | ||
Stream<List<Order>> watchUserOrders(String uid) { | ||
return _orders.stream.map((ordersData) { | ||
final ordersList = ordersData[uid] ?? []; | ||
ordersList.sort( | ||
(lhs, rhs) => rhs.orderDate.compareTo(lhs.orderDate), | ||
); | ||
return ordersList; | ||
}); | ||
} | ||
|
||
// A method to add a new order to the list for a given user | ||
Future<void> addOrder(String uid, Order order) async { | ||
await delay(addDelay); | ||
final value = _orders.value; | ||
final userOrders = value[uid] ?? []; | ||
userOrders.add(order); | ||
value[uid] = userOrders; | ||
_orders.value = value; | ||
} | ||
} | ||
|
||
final ordersRepositoryProvider = Provider<FakeOrdersRepository>((ref) { | ||
return FakeOrdersRepository(); | ||
}); |
64 changes: 29 additions & 35 deletions
64
ecommerce_app/lib/src/features/orders/presentation/orders_list/orders_list_screen.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,52 @@ | ||
import 'package:ecommerce_app/src/common_widgets/async_value_widget.dart'; | ||
import 'package:ecommerce_app/src/features/orders/application/user_orders_provider.dart'; | ||
import 'package:ecommerce_app/src/features/orders/presentation/orders_list/order_card.dart'; | ||
import 'package:ecommerce_app/src/localization/string_hardcoded.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:ecommerce_app/src/common_widgets/responsive_center.dart'; | ||
import 'package:ecommerce_app/src/constants/app_sizes.dart'; | ||
import 'package:ecommerce_app/src/features/orders/domain/order.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
/// Shows the list of orders placed by the signed-in user. | ||
class OrdersListScreen extends StatelessWidget { | ||
const OrdersListScreen({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// TODO: Read from data source | ||
final orders = [ | ||
Order( | ||
id: 'abc', | ||
userId: '123', | ||
items: { | ||
'1': 1, | ||
'2': 2, | ||
'3': 3, | ||
}, | ||
orderStatus: OrderStatus.confirmed, | ||
orderDate: DateTime.now(), | ||
total: 104, | ||
), | ||
]; | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: Text('Your Orders'.hardcoded), | ||
), | ||
body: orders.isEmpty | ||
? Center( | ||
child: Text( | ||
'No previous orders'.hardcoded, | ||
style: Theme.of(context).textTheme.displaySmall, | ||
textAlign: TextAlign.center, | ||
), | ||
) | ||
: CustomScrollView( | ||
slivers: <Widget>[ | ||
SliverList( | ||
delegate: SliverChildBuilderDelegate( | ||
(BuildContext context, int index) => ResponsiveCenter( | ||
padding: const EdgeInsets.all(Sizes.p8), | ||
child: OrderCard( | ||
order: orders[index], | ||
body: Consumer(builder: (context, ref, _) { | ||
final userOrdersValue = ref.watch(userOrdersProvider); | ||
return AsyncValueWidget<List<Order>>( | ||
value: userOrdersValue, | ||
data: (orders) => orders.isEmpty | ||
? Center( | ||
child: Text( | ||
'No previous orders'.hardcoded, | ||
style: Theme.of(context).textTheme.displaySmall, | ||
textAlign: TextAlign.center, | ||
), | ||
) | ||
: CustomScrollView( | ||
slivers: <Widget>[ | ||
SliverList( | ||
delegate: SliverChildBuilderDelegate( | ||
(BuildContext context, int index) => ResponsiveCenter( | ||
padding: const EdgeInsets.all(Sizes.p8), | ||
child: OrderCard( | ||
order: orders[index], | ||
), | ||
), | ||
childCount: orders.length, | ||
), | ||
), | ||
childCount: orders.length, | ||
), | ||
], | ||
), | ||
], | ||
), | ||
); | ||
}), | ||
); | ||
} | ||
} |