diff --git a/lib/app.dart b/lib/app.dart index 6c097dd..6290b9b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -3,6 +3,7 @@ import 'package:thesis_shop/route_key.dart'; import 'package:thesis_shop/screens/cart/cart_screen.dart'; import 'package:thesis_shop/screens/product_list/product_list_screen.dart'; import 'package:thesis_shop/service/product_service.dart'; +import 'package:thesis_shop/stores/product_store.dart'; import 'package:thesis_shop/stores/user_store.dart'; import 'package:thesis_shop/utils/map_keys_extension.dart'; @@ -12,7 +13,12 @@ class ThesisShopApp extends StatelessWidget { : super(key: key); Widget injectStores(BuildContext context, {required Widget child}) { - return UserStoreImplementation(child: child); + return UserStoreImplementation( + child: ProductStoreImplementation( + productService: productService, + child: child, + ), + ); } @override diff --git a/lib/models/remote_resource.dart b/lib/models/remote_resource.dart new file mode 100644 index 0000000..8879dd3 --- /dev/null +++ b/lib/models/remote_resource.dart @@ -0,0 +1,38 @@ +abstract class RemoteResource { + RemoteResource._(); + + factory RemoteResource.loading() { + return LoadingRemoteResource(); + } + + factory RemoteResource.error(String errorMessage) { + return ErrorRemoteResource(errorMessage); + } + + factory RemoteResource.finished(T value) { + return FinishedRemoteResource(value); + } + + FinishedRemoteResource asFinished() { + return this as FinishedRemoteResource; + } + + ErrorRemoteResource asError() { + return this as ErrorRemoteResource; + } +} + +class ErrorRemoteResource extends RemoteResource { + final String errorMessage; + + ErrorRemoteResource(this.errorMessage) : super._(); +} + +class LoadingRemoteResource extends RemoteResource { + LoadingRemoteResource() : super._(); +} + +class FinishedRemoteResource extends RemoteResource { + final T value; + FinishedRemoteResource(this.value) : super._(); +} diff --git a/lib/screens/product_list/product_list_screen.dart b/lib/screens/product_list/product_list_screen.dart index e623621..e89cd0b 100644 --- a/lib/screens/product_list/product_list_screen.dart +++ b/lib/screens/product_list/product_list_screen.dart @@ -1,31 +1,44 @@ import 'package:flutter/material.dart'; -import 'package:thesis_shop/models/product.dart'; +import 'package:thesis_shop/models/remote_resource.dart'; +import 'package:thesis_shop/screens/product_list/cart_button_overlay.dart'; +import 'package:thesis_shop/screens/product_list/product_list.dart'; +import 'package:thesis_shop/stores/product_store.dart'; import 'package:thesis_shop/widgets/user_switch.dart'; -import 'cart_button_overlay.dart'; -import 'product_list.dart'; - -const _productPlaceholder = [ - Product(title: 'Bananen', price: 3), - Product(title: 'Äpfel', price: 2), - Product(title: 'Birnen', price: 2.5), - Product(title: 'Kirschen', price: 1.2), -]; - class ProductListScreen extends StatelessWidget { const ProductListScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - const products = _productPlaceholder; return Scaffold( appBar: AppBar( title: const Text('Thesis Shop'), actions: const [UserSwitch()], ), - body: const CartButtonOverlay( - child: ProductList(products: products), - ), + body: const _ProductStateSwitch(), ); } } + +class _ProductStateSwitch extends StatelessWidget { + const _ProductStateSwitch({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final remoteProducts = ProductStore.of(context).products; + if (remoteProducts is FinishedRemoteResource) { + final products = remoteProducts.asFinished().value; + return CartButtonOverlay( + child: ProductList( + products: products, + ), + ); + } else if (remoteProducts is ErrorRemoteResource) { + return Center(child: Text(remoteProducts.asError().errorMessage)); + } else if (remoteProducts is LoadingRemoteResource) { + return const Center(child: CircularProgressIndicator.adaptive()); + } else { + throw UnimplementedError(); + } + } +} diff --git a/lib/stores/product_store.dart b/lib/stores/product_store.dart new file mode 100644 index 0000000..91f61d6 --- /dev/null +++ b/lib/stores/product_store.dart @@ -0,0 +1,70 @@ +import 'package:flutter/widgets.dart'; +import 'package:thesis_shop/models/product.dart'; +import 'package:thesis_shop/models/remote_resource.dart'; +import 'package:thesis_shop/service/product_service.dart'; + +class ProductStoreImplementation extends StatefulWidget { + final Widget child; + final ProductService productService; + const ProductStoreImplementation({ + Key? key, + required this.child, + required this.productService, + }) : super(key: key); + + @override + _ProductStoreImplementationState createState() => + _ProductStoreImplementationState(); +} + +class _ProductStoreImplementationState + extends State { + RemoteResource> products = RemoteResource.loading(); + + @override + void initState() { + super.initState(); + loadProducts().catchError( + (err) => setState( + () { + products = RemoteResource.error(err.toString()); + }, + ), + ); + } + + Future loadProducts() async { + final remoteProducts = await widget.productService.fetchProducts(); + setState(() { + products = RemoteResource.finished(remoteProducts); + }); + } + + @override + Widget build(BuildContext context) { + return ProductStore(products: products, child: widget.child); + } +} + +class ProductStore extends InheritedWidget { + const ProductStore({ + Key? key, + required this.products, + required Widget child, + }) : super(key: key, child: child); + + final RemoteResource> products; + List get mustProducts => products.asFinished().value; + + static ProductStore of(BuildContext context) { + final ProductStore? result = + context.dependOnInheritedWidgetOfExactType(); + assert(result != null, 'No ProductStore found in context'); + return result!; + } + + @override + bool updateShouldNotify(ProductStore oldWidget) { + return oldWidget.products != products; + } +}