What You'll Build in this Workshop:

Shopping Cart

Let's proceed!

We'll start with setting up the provider service that will handle all the business logic for our shopping cart. We'll add functionality for the following:

Creating the DonutShoppingCartService

Start by creating a class called DonutShoppingCartService that extends ChangeNotifier. This service will engage with other widgets to trigger their rebuilding by calling notifyListeners so as to update themselves based on the state of the shopping cart at any given time.

Shopping Cart

Create a property called cartDonuts; this will be a List of type DonutModel. Initialize it with an empty array by default:

Shopping Cart

Add to cart functionality

Create a method called addToCart, which takes as a parameter an object of type DonutModel. Add it to the cartDonuts collection, followed by the notifyListeners call. Eventually when we use this method, this will add the donut model, and trigger a rebuild on the listening widget:

Shopping Cart

Remove donut from cart by name

We'll use the name as something unique - feel free to pick a stronger strategy, but for the sake of this codelab, we'll use the name. Let's create a method called removeFromCart which takes an object of type DonutModel; finds a match by name in the list of cart donuts using the removeWhere helper method, passing the lambda expression that performs the evaluation, and if it finds it, then removes it, followed by a call to notifyListeners:

Shopping Cart

Clear all donuts from cart

To add the abilily to clear items out of the cart, let's add a method called clearCart, whose task will be to clear all items from the cartDonuts collection by calling the method clear on the list, as well as notify all listeners of it.

Shopping Cart

Get total price of items in the cart

Let's create a simple method - we'll call it getTotal - that just loops through all of the donuts in the cart and adds up all the prices, and conveniently returns it to whoever requests it:

Shopping Cart

Check for the existing of a donut in the cart by name

Finally, let's create a simple method called isDonutInCart which takes an object of type DonutModel, and verifies whether there is an item with the same name in the collection, using the any plus a lambda to evaluate the condition, as such:

Shopping Cart

And with that, we complete all methods required by the DonutShoppingCartService.

Before using this provided service, make sure you add it to the list of MultiProvider providers at the root of the application, as follows:

Shopping Cart

Now, let's go ahead and start using it!

Let's use the newly created DonutShoppingCartService to provide the ability to add items to a shopping cart from the details page.

Shopping Cart

Let's go to the DonutShopDetails widget page.

In order to accomplish the switching of Add To Cart to Added to Cart we definitely need to listen to the state of the DonutShoppingCartService to property rebuild the widget upon its state change.

Locate the Container that represents the button Add To Cart. First things first, let's make this container tappable by wrapping it inside a GestureDetector, and set up the onTap event available by adding an empty handler we'll come back to it:

Shopping Cart

Next, wrap the GestureDetector inside the return statement of a Consumer widget that listens to the DonutShoppingCartService, as follows:

Shopping Cart

By wrapping it inside a Consumer widget, we ensure that upon any changes that occur in the DonutShoppingCartService, the builder in the Consumer widget will be executed and rebuild its contents.

Now that we have access to the DonutShoppingCartService via the Consumer widget so let's add a bit more logic.

The Container widget that represents the Add To Cart button should only be displayed when the current donut we're viewing in the details page is not in the shopping cart. Let's leverage the shopping cart service's isDonutInCart method to conditionally render the button:

Shopping Cart

We're not done yet! We're only handling the case when we need to show the Add To Cart button. For the case when the donut is indeed in the shopping cart, we need to render another little structure, the one that shows that the donut has already been added.

Right at the end the Consumer builder, we will return a separate widget structure to illustrate the fact that the item is in the shopping cart.

Return a Padding widget, with its padding set to 30px (top and bottom only). An as a child, add a Row widget with its children horizontally aligned using the MainAxisAlignment.center. Initialize its children with an empty array by default:

Shopping Cart

We need a similar structure as the Add To Cart button (an icon, some space, then a text label) so let's do just that:

Shopping Cart

Last thing to do is now execute the actual adding of the current donut into the shopping cart via the DonutShoppingCartService's *addToCart method, which will eventually trigger the rebuilding of the Consumer itself.

Go back up a few lines, inside the GestureDetector that wraps our Add To Cart button. Inside its onTap event, add the following line:

Shopping Cart

Upon adding the item to the shopping cart, since the adding functionality triggers the notifyListeners and this widget is a listener as well, it will rebuild itself, and in the next build cycle, the logic to show the button will be false, making the execution fall down to the last return statement, thus showing the Added To Cart structure.

Run it now on DartPad and notice how upon the user tapping on the Add To Cart it switches immediately to Added To Cart. This is the power of Consumer widgets and ChangeNotifiers in action!

Shopping Cart

Let's proceed now to see how many items have been added to the shopping cart, since we can't see them now anywhere in our UI (unless you debug it).

Right now we are able to add items to a cart, but we can't see how many, or where they are. Let's start implementing those pieces.

We want a quick and simple way to view how many items have been added to the cart, and for that we'll implement a small widget called DonutShoppingCartBadge, which will be a small badge we show at the top right corner of the screen; it will be part of the AppBar. The cool thing is that since "everything is a widget" in Flutter, we can just add this custom component as part of the actions in our AppBar. Let's proceed!

Shopping Cart

Creating the DonutShoppingCartBadge widget

Let's create a custom widget class called DonutShoppingCartBadge that extends StatelessWidget. This one will be a StatelessWidget since it won't be maintaining state internally - rather it will be housing a Consumer widget, which is the one that will be listening for changes instead. Return a Container widget for now:

Shopping Cart

Let's add some styling to the Container to make it look like the badge in the design. Add the following to the Container:

Shopping Cart

As the child of this Container, we'll add the little shopping cart icon, some spacing, then a text label.

For the Text widget, let's hardcode a value for a minute, just to get things in place. Make it bold, white and a font size of 20px:

Shopping Cart

Add some spacing right after the Text widget, via a SizedBox widget, with 10px width:

Shopping Cart

And last but not least, the Icon widget; use the Icons.shopping_cart icon, 25px in size and white:

Shopping Cart

The core widget has been created. Let's put it in place before wrapping up its functionality. Locate the DonutShopDetails page, and go to the AppBar widget. Add an actions property, which is a property that allows you to add widgets to the right hand side of the app bar, appropriate for options, menus - and in our case, a badge.

Therefore, open up the actions property (which takes an array of widgets) and add our newly created DonutShoppingCartBadge widget inside, as such:

Shopping Cart

Running this on DartPad should yield the following output:

Shopping Cart

Oh, it looks huge! Let's go back to the DonutShopppingCartBadget widget to scale it down a bit. I have a trick - scale it down using a Transform widget with the *scale option, so wrap the Container inside a Transform widget, as such:

Shopping Cart

The Transform widget with the scale option, and passing a scael value of 0.7 allows it to scale down the enclosing widget uniformly.

Try it out again in DartPad and see the effect:

Shopping Cart

That looks about right! Let's now go ahead and provide the logic that will display it plus show the shopping cart items count in it.

In the build method of the DonutShoppingCartBadge, instead of returning the Transform widget the encloses the Container, return a Consumer widget that listens to the DonutShoppingCartService this way, any changes that occur on the shopping cart (adding, removing, etc.) will be captured by this widget and render accordingly.

Shopping Cart

Since we have the DonutShoppingCartService accessible here via the cartService reference, access the cart items count and supply it to the nested Text widget, replace the hardcoded value in there:

Shopping Cart

Lastly, add the logic to return an empty SizedBox container when the cart is empty; we don't want to just show the badge with zero items. We'll only show it when 1 or more items:

Shopping Cart

Taking this functionality for a spin by adding items via the details page's Add To Cart functionality and you should see the following behavior:

Shopping Cart

Notice how the badge only appears when there's one ore more, and as we add more items, the number increments! Awesome!

Proceed to the next step to see the added items as a list on the shopping cart page!

Shopping Cart

We now need to see the items in the shopping cart page, where you can view their details, remove items, clear all items, and see the total price of all items added.

Check out both schematics:

Shopping Cart

Shopping Cart

Let's proceed!

Create a class called DonutShoppingCartPage that extends StatefulWidget since we'll be using animations in here. Override its build method and return an empty Container widget for now:

Shopping Cart

Go to the DonutShopMain page now. Replace the placeholder content we have on the Navigator widget for the "/shoppingcart" route in the onGenerateRoute switch statement. We currently have a Center widget wrapping a Text widget. Replace it by our newly created class DonutShoppingCartPage.

Shopping Cart

Let's start building the layout. Go to this widget's build method. Replace the placeholder Container by a Padding widget. Add padding of 40px all around, and as its direct child, add a Column widget. Initialize the Column with an empty array as its children, and its children horizontally aligned to the left, using CrossAxisAlignment.start:

Shopping Cart

As the first child of this Column, add an Image widget using the network option, with a width of 170px:

Shopping Cart

Let's give this image title an entrance! We want this title to come in with a fading animated transition, so let's work on that.

Back to the top of the DonutShoppingCartPageState class, add the SingleTickerProviderStateMixin mixin to this class:

Shopping Cart

Of course, since this will be an explicit animation, we need an AnimationController, and we'll call it titleAnimation. Add it at the top of the class:

Shopping Cart

Override the class' initState so we can initialize the AnimationController appropriately. Initialize the titleAnimation AnimationController with a duration of 500 ms, with a forward motion, calling the forward method on it:

Shopping Cart

Always remember to clean up after yourself: override the dispose method, where you can dispose of the controller in question:

Shopping Cart

Back on the build method of this class, wrap the Image.network we placed a while back inside a FadeTransition widget. This is what will provide it with the desired fading effect. The FadeTransition takes an opacity property, to which you'll assign a Tween instance, with begin of 0.0 (invisible), end 1.0 (visible), and execute the animate method on it, passing the titleAnimation instance of AnimationController plus an animation curve of Curves.easeInOut, as follows:

Shopping Cart

Testing this on DartPad, and we should see the title fading in as we navigate to the DonutShoppingCartPage:

Shopping Cart

Making great progress so far!

Let's continue to the next section.

We'll focus now on the middle content of the DonutShoppingCartPage widget, where we display the list of items, as well as a message for when there are no items as well.

Shopping Cart

Since we want this middle region to occupy most of the real estate at the root Column, we need to start with an Expanded widget. Add as its immediate child for now a Center widget:

Shopping Cart

Let's build the Center widget's structure.

Add a SizedBox as a child of the Center widget. We are concernted on the width the most, so set a width of 200px:

Shopping Cart

We want to build that layout above, so we need a Column. Add it as an immediate child of the SizedBox, with its children center aligned vertically, so use the MainAxisAlignment.center option:

Shopping Cart

We want to continue building the structure, which contains three items: an icon, some spacing, and a text label.

Start by an Icon widget with the Icons.shopping_cart icon, grey color and 50px in size:

Shopping Cart

Add some spacing under it using a SizedBox, 20px in height:

Shopping Cart

Lastly, add the text label saying "You don't have any items on your cart yet!". Use a Text widget, with centered text and a grey color:

Shopping Cart

That should do it for this structure. Check it out how's looking by running it on DartPad:

Shopping Cart

Now, we don't always show this little structure. We need to conditionally show it when there are no items in the cart. So as we've done in the past, you must wrap this widget structure into a Consumer widget that listens to the changes in the DonutShoppingCartService, and checks whether the cart is empty so as to display it:

Shopping Cart

Notice that if the empty cart condition doesn't get hit, we still must return something. We are returning an empty Container, which we'll replace later with our own custom widget that holds the list of donuts in the cart.

Let's focus now on the actual list of shopping cart items, but first we'll build out each row in the list. The schematic of this row is shown below:

Creating the Donut Shopping List Row Item

Shopping Cart

Before we load the list, we need to style how each item in the list will look like. Let's proceed and create a custom widget class called DonutShoppingListRow.

While overriding its build method, return a Padding widget, with its padding set to 10px top and bottom, 20px right, as such:

Shopping Cart

We'll be adding the image of the donut, title, price and the ability for an item to be removed using a trash can icon, as in the image above, all of these lined up initially in a row fashion.

Go ahead and add a Row as the immediate child of the Padding widget; start with an empty array as its children:

Shopping Cart

Before we go any further, this widget must take a DonutModel object, injected via the constructor. Create a property called donut, type DonutModel to hold on the supplied value via the constructor. This is the object that will feed all the child widgets within this row widget (the image, name, price, etc.):

Shopping Cart

Now we can move on.

Start by adding an Image widget with the network option, pulling the name of the image out of the donut model's imgUrl property; give it a width and height of 80px:

Shopping Cart

Add some breathing room via a SizedBox with 10px width:

Shopping Cart

Let's continue focusing on the Row layout.

We want the middle content of this row to take up most of the row's real estate, and also to contain the name and the price, stacked as a column. To accomplish this, start by adding an Expanded widget, and as its child, a Column with items aligned horizontally to the left:

Shopping Cart

In this Column widget, we'll lay out a Text widget to hold the name of the donut, followed by some space (SizedBox(height: 5)). Add the following specs to the Text:

Your code should look like this:

Shopping Cart

Let's work on the little badge for the price information. We also want it to look like a pill, so start with a Container widget with the following specs:

Your Container code should look like this:

Shopping Cart

Now, to finish up this Container, add a Text widget as its direct child, that pulls the price information from the donut model's price property. Use the utility method toStringAsFixed to format it accordingly, plus the following specs for styling it:

Final Container code should look as follows:

Shopping Cart

Done with the middle portion of the Row widget. Proceed to add some spacing between that and what we'll be laying out next, which is the IconButton (a SizedBox with 10px width should do):

Shopping Cart

Now, let's add the last piece of this Row widget, which is an IconButton widget. Create an IconButton; add an empty handler for its onPressed event (required), plus add an Icon widget with the Icons.delete_forever as its icon property, with a color of Utils.mainColor. We'll work on the functionality of deleting the row in a bit:

Shopping Cart

To keep this widget truly encapsulated, instead of triggering the functionality of deleting the widget right from inside (which we could!), but i'd be cleaner if instead we provide some sort of callback that triggers the action, but from the outside.

Add an additional property, type Function, called onDeleteRow, and its corresponding constructor parameter. This will hold a reference to a callback that we'll supply from its parent, which in turn will handle the actual deletion, that way this widget can remain truly encapsulated and can still trigger external functions via this callback.

Notice we decorated the constructor parameter with the keyword required; this will enforce that this method is provided upon creating this wigdet so it doesn't blow up.

Shopping Cart

Finally, trigger this callback method right from inside our IconButton's onPressed event; this will ensure that when the user taps on it, it will trigger whichever callback function we assign to it.

Shopping Cart

That about wraps up the layout for the DonutShoppingListRow widget. Now we need the ability to populate a ListView with widgets of type DonutShoppingListRow.

Now that we developed how the rows will look like and function, let's proceed with the list that will load them.

Shopping Cart

Let's proceed right away to create a custom widget called DonutShoppingList that extends StatefulWidget since we'll be adding animations to the list later. For now we'll be doing the core implementation. While overriding the build method, return a ListView widget, since we want a scrollable container of row items, each of which will be a DonutShoppingListRow widget.

Shopping Cart

Let's feed a list of DonutModel objects into it via the constructor, therefore create a property called donutCart, type List of DonutModel, and its corresponding constructor parameter:

Shopping Cart

In the build method, replace the ListView widget with a more appropriate List.builder, since we'll be providing a collection of items plus manage the way they get rendered. Set the ListView.builder's properties as follows:

Your code should look like this:

Shopping Cart

Proceed now to pull each DonutModel in turn out of the donutCart collection, using the index provided in the itemBuilder's callback:

Shopping Cart

Now, we need to generate the actual item to display for each row. Yes, you guessed it - we'll return a DonutShoppingListRow out of this method, providing to it the currentDonut model, as well as a callback, which we'll populate in a minute:

Shopping Cart

Let's take a pause now and test what we've built so far.

Go back to the DonutShoppingCartPageState's build method, and find the return statement inside the Consumer widget listening to the DonutShoppingCartService, where we're returning the empty Container. Replace that empty Container by our newly created DonutShoppingList widget, and pass into its donutCart parameter the list of donuts, available in cartService.cartDonuts, as such:

Shopping Cart

Let's run what we have so far on DartPad, just to see what we have. Click on a few donuts, navigate to their details page, and add them to cart; then navigate to the shopping cart page and you should see something like this:

Shopping Cart

Nice. Go ahead and tap on the trash can icon to the right of each row; it doesn't do anything at the moment. Let's take care of that right now.

Since we already have an instance of DonutShoppingCartService at the DonutShoppingCartPage level, we could pass it down to the list.

In the DonutShoppingList widget, add an additional parameter of type DonutShoppingCartService; add its corresponding constructor paramter.

Shopping Cart

Now that the service made all the way to the list, we can now access its functionality from within here, and thus add it to our list row callback method, as such:

Shopping Cart

To recap: we injected the DonutShoppingCartService through the constructor; inside our row-rendering callback, at the point where we're building each row, we invoke the service's removeFromCart method inside the row's required callback onDeleteRow, passing into it the donut model in turn.

Now, back in the DonutShoppingCartPage, right where we return the DonutShoppingList, pass the service instance we get from the Consumer and pass it down to it, as such:

Shopping Cart

Run it once again on DartPad, and now attempt to remove items by tapping on their trash can icon:

Shopping Cart

And then they are gone! Excellent execution, folks! See what happens when you architect your Flutter apps in a simple, clean and straightforward - things run smooth and nice! Kudos to all!

We'll work on the clearing of all cart items via the "Clear Cart" button. Here's a basic schematics of what we'll be working on:

Shopping Cart

Let's go back to the DonutShoppingCartPage widget (specifically the State class) and let's focus our attention to its build method, under the Expanded widget holding the DonutShoppingList widget.

Add a new Row widget with their children's horizontal alignment set to spaceBetween, and vertical alignment to end. Initialize its children to an empty array:

Shopping Cart

Let's add the first child to this Row. Start creating the foundation of this button by adding a Container widget with padding of 10px top and bottom, 20px left and right:

Shopping Cart

As a child of this Container, since we'll be adding an icon and a text label laid out in a horizontal fashion, add a Row widget with their children left aligned and its children initialized to an empty array:

Shopping Cart

To this Row, add the required pieces: an Icon widget and a Text widget, as follows:

Shopping Cart

Since we want this button to have a material look to it, let's wrap it in a Material widget, and give it an inkwell effect using an Inkwell widget.

Wrap the Container inside an InkWell widget, which we'll use to leverage touch capabilities as well as the inkwell effect, therefore use the following specs when creating it:

Your code should look as follows:

Shopping Cart

Now, for the InkWell widget to work, it must have a Material widget as an ancestor, since the Material widget is where the ink reactions are actually painted.

Wrap the InkWell inside a Material widget, with its colors set to Utils.mainColor with a 20% opacity:

Shopping Cart

We want to clip the edges of this widget in order to have round edges, so use a ClipRRect widget, with a borderRadius with 30px of radius:

Shopping Cart

Running what we have so far in DartPad, should display a button at the designated place; it shows on the left since we haven't added the widget that will push it to the right:

Shopping Cart

Let's now add the functionality required to execute the clearing of items.

Wrap the whole Row parent widget into a Consumer widget that listens to the DonutShoppingCartService service, as we've done in previous cases:

Shopping Cart

Let's leverage the provided service so we can not only trigger the action we want (clear all items from the cart), but also modify the styling of the button accordingly.

Let's get back to that custo button we created, and start by changing the color of the Material widget based on whether the cart is empty or not, by checking the cartService.cartDonuts.isEmpty property:

Shopping Cart

We'll also enable the tappability of this custom button also by checking the length of the shopping cart items. Set it to null if empty, otherwise, wire it up with a handler and inside, call the cartService.clearCart method:

Shopping Cart

To go even deeper, we'll do it to both the Icon and Text widgets nested in this widget structure, as follows:

Shopping Cart

With all the functionaly and styling in place, run it again through DartPad and notice the behavior; add a few items to the cart, and then hit the Clear Cart button:

Shopping Cart

And with that, you got clearing shopping cart items good to go!

Let's move on to showing the price of the items in the cart. Click Next to proceed.

Shopping Cart

This is a simple one so let's knock this right out of the park.

We'll be creating this little structure as a Column widget with two Text widgets, aligned to the left, right on top of the ClipRect widget that wraps the whole button that clears the shopping cart items, like so:

Shopping Cart

Let's not show it all the time; let's have logic that if the cart is empty, have a placeholder empty widget, otherwise show this structure:

Shopping Cart

Running this on DartPad should display the following behavior; as items get removed, since this is inside the Consumer widget listening to changes in the DonutShoppingCartService, it rebuilds on every change, reflecting the update at every turn; notice the price changing, until there are no more items, where it just gets replaced by an empty SizedBox widget:

Shopping Cart

And with that, the core shopping cart functionality is in place!

You can complete the next two bonus codelabs if you wish, but other than that you're done! Thanks again for making it this far!

In this bonus codelab, we will animate the entrance of each of the items as we navigate to this page:

Shopping Cart

We'll do the same pretty much we did to the DonutList widget in the DonutMainPage.

Go to the DonutShoppingList widget, specifically its State class. Let's add two properties: a GlobalKey property, type AnimatedListState, called _key, as well as a List of type DonutModel objects, called insertedItems; initialize with an empty array:

Shopping Cart

Override the initState method, and just like we did before, we'll loop through the available items in the donutCart collection, and introduce a tiny delay (125 ms) as we insert items into the insertedItems collection, in order to simulate the staggered approach as they enter:

Shopping Cart

Replace the ListView.builder by an AnimatedList, and the following properties:

Your code should look like this:

Shopping Cart

Now, time to add the animation to the items!

Wrap the existing DonutShoppingListRow widget, first inside a FadeTransition then a SlideTransition.

Let's wrap the DonutShoppingListRow first inside the FadeTransition widget:

Shopping Cart

Now wrap the FadeTransition inside a SlideTransition widget:

Shopping Cart

You just added animated list items to your shopping cart! Kudos for making all the way here!!!

Shopping Cart

Last piece we'll add to this item as a nice-to-have, is the ability to see the shopping cart items count right from the bottom bar, on top of the shopping cart icon.

What we'll do is replace the IconButton that represents the shopping cart by another widget structure, and since we need to listen to the changes going on in the DonutShoppingCartService, we'll use a Consumer widget here as well.

Let's start.

Replace the whole IconButton for the shopping cart by a Consumer widget that listens to the DonutShoppingCartService.

Shopping Cart

Inside of the builder callback method, get the length of the donuts in the shopping cart (cartService.cartDonuts.length) and hold it on a local variable for use later:

Shopping Cart

Out of this method, let's return a Container with the following specs:

Your Container code should look like this:

Shopping Cart

Inside of this Container, we'll build the structure of a text label on top of the shopping cart icon. As the child of this Container, add a Column widget, with its children items aligned to the bottom (MainAxisAlignment.end); initialize its children to an empty array by default:

Shopping Cart

Let's focus on the little structure inside the Column widget.

We want to not display the text label with the count on top of the item when there are no items in the cart - so it matches the rest of the icons in the bottom bar.

Shopping Cart

Let's dissect the code a bit.

We check whether we have any cartItems, therefore we render a Text widget with the specified specs, otherwise we do a SizedBox with the same size as the Text widget.

We add also some spacing in between, using a SizedBox with a 10px height.

Lastly, we add the shopping_cart icon, also checking for the cartItems count and changing the value depending on both the length of items and the selection, and assigning the appropriate value.

Running this code through DartPad and you should see the bottom bar display the structure as soon as you add the first item to it:

Shopping Cart

Amazing! You've now given your app the pizzaz it needs, with very little effort! All using the existing infrastructure you set up. Thanks for going all the way - your app is complete!

Congrats in making it this far! In this codelab, we accomplished the following:

Please don't forget to follow me on social media:

In case you fell behind on this codelab, below is the whole code for this codelab in a way you can copy / paste directly into DartPad:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(

    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => DonutBottomBarSelectionService(),
        ),
        ChangeNotifierProvider(
          create: (_) => DonutService(),
        ),
        ChangeNotifierProvider(
          create: (_) => DonutShoppingCartService(),
        ),
      ],

      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        initialRoute: '/',
        navigatorKey: Utils.mainAppNav,
        routes: {
          '/': (context) => SplashPage(),
          '/main': (context) => DonutShopMain(),
          '/details': (context) => DonutShopDetails(),
        }
      )
    )
  ); 
}

class SplashPage extends StatefulWidget {

  @override 
  SplashPageState createState() => SplashPageState();
}

class SplashPageState extends State<SplashPage> 
  with SingleTickerProviderStateMixin {

  AnimationController? donutController;
  Animation<double>? rotationAnimation;

  @override
  void initState() {
    super.initState();
    donutController = AnimationController(
      duration: const Duration(seconds: 5), 
      vsync: this)..repeat();

    rotationAnimation = Tween<double>(begin: 0, end: 1)
    .animate(CurvedAnimation(parent: donutController!, curve: Curves.linear));
  }

  @override 
  void dispose() {
    donutController!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    Future.delayed(const Duration(seconds: 2), () {
      Utils.mainAppNav.currentState!.pushReplacementNamed('/main');
    });

    return Scaffold(
      backgroundColor: Utils.mainColor,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            RotationTransition(
              turns: rotationAnimation!,
              child: Image.network(Utils.donutLogoWhiteNoText, width: 100, height: 100),
            ),
            Image.network(Utils.donutLogoWhiteText, width: 150, height: 150)
          ],
        ),
      )
    );
  }
}

class DonutShopMain extends StatelessWidget {

  @override 
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: Drawer(
        child: DonutSideMenu()
      ),
      appBar: AppBar(
        iconTheme: const IconThemeData(color: Utils.mainDark),
        backgroundColor: Colors.transparent,
        elevation: 0,
        centerTitle: true,
        title: Image.network(Utils.donutLogoRedText, width: 120)
      ),
      body: Column(
        children: [
          Expanded(
            child: Navigator(
              key: Utils.mainListNav,
              initialRoute: '/main',

              onGenerateRoute: (RouteSettings settings) {
                Widget page;
                switch(settings.name) {
                  case '/main':
                    page = DonutMainPage();
                    break;
                  case '/favorites':
                    page = Center(child: Text('favorites'));
                    break;
                  case '/shoppingcart':
                    page = DonutShoppingCartPage();
                    break;
                  default:
                    page = Center(child: Text('main'));
                    break;
                }

                return PageRouteBuilder(pageBuilder: (_, __, ___) => page,
                  transitionDuration: const Duration(seconds: 0)
                );
             }
             
           )


          ),
          DonutBottomBar()
        ]
      )
    );
  }
}

class DonutMainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        DonutPager(),
        DonutFilterBar(),
        Expanded(
          child: Consumer<DonutService>(
            builder: (context, donutService, child) {
              return DonutList(donuts: donutService.filteredDonuts);
            },
          )
        )
      ]
    );
  }
}

class DonutPager extends StatefulWidget {
  @override
  State<DonutPager> createState() => _DonutPagerState();
}

class _DonutPagerState extends State<DonutPager> {

  List<DonutPage> pages = [
    DonutPage(imgUrl: Utils.donutPromo1, logoImgUrl: Utils.donutLogoWhiteText),
    DonutPage(imgUrl: Utils.donutPromo2, logoImgUrl: Utils.donutLogoWhiteText),
    DonutPage(imgUrl: Utils.donutPromo3, logoImgUrl: Utils.donutLogoRedText),
  ];

  int currentPage = 0;
  PageController? controller;

  @override
  void initState() {
    controller = PageController(initialPage: 0);
    super.initState();
  }

  @override
  void dispose() {
    controller!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 350,
      child: Column(
        children: [
          Expanded(
            child: PageView(
              scrollDirection: Axis.horizontal,
              pageSnapping: true,
              controller: controller,
              onPageChanged: (int page) {
                setState(() {
                  currentPage = page;
                });
              },
              children: List.generate(pages.length, (index) {
                DonutPage currentPage = pages[index];
                return Container(
                  alignment: Alignment.bottomLeft,
                  margin: EdgeInsets.all(20),
                  padding: EdgeInsets.all(30),
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(30),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.2), 
                        blurRadius: 10, 
                        offset: Offset(0.0, 5.0)
                      )
                    ],
                    image: DecorationImage(
                      image: NetworkImage(currentPage.imgUrl!),
                      fit: BoxFit.cover
                    )
                  ),
                  child: Image.network(currentPage.logoImgUrl!, width: 120)
                );
              })
            )
          ),
          PageViewIndicator(
            controller: controller, 
            numberOfPages: pages.length,
            currentPage: currentPage,
          )
        ],
      )
    );
  }
}

class PageViewIndicator extends StatelessWidget {
  
  PageController? controller;
  int? numberOfPages;
  int? currentPage;

  PageViewIndicator({ this.controller, this.numberOfPages, this.currentPage });

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: List.generate(numberOfPages!, (index) {

        return GestureDetector(  
          onTap: () {
            controller!.animateToPage(
              index, 
              duration: const Duration(milliseconds: 500), 
              curve: Curves.easeInOut);
          },
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 250),
            curve: Curves.easeInOut,
            width: 15,
            height: 15,
            margin: EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: currentPage == index ? 
                Utils.mainColor : Colors.grey.withOpacity(0.2),
              borderRadius: BorderRadius.circular(10)
            )
          )
        );

        
      })
    );
  }
 
}

class DonutSideMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Utils.mainDark,
      padding: EdgeInsets.all(40),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Container(
            margin: EdgeInsets.only(top: 40),
            child: Image.network(Utils.donutLogoWhiteNoText,
              width: 100
            )
          ),
          Image.network(Utils.donutLogoWhiteText,
            width: 150
          )
        ],
      )
    );
  }
}

class DonutBottomBar extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(30),
      child: Consumer<DonutBottomBarSelectionService>(
        builder: (context, bottomBarSelectionService, child) {
          return Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              IconButton(
                icon: Icon(
                  Icons.trip_origin, 
                  color: bottomBarSelectionService.tabSelection == 'main' ? 
                            Utils.mainDark : Utils.mainColor
                ),
                onPressed: () {
                  bottomBarSelectionService.setTabSelection('main');
                }
              ),
              IconButton(
                icon: Icon(Icons.favorite, 
                color: bottomBarSelectionService.tabSelection == 'favorites' ? 
                            Utils.mainDark : Utils.mainColor
                ),
                onPressed: () {
                  bottomBarSelectionService.setTabSelection('favorites');
                }
              ),
              Consumer<DonutShoppingCartService>(
                builder: (context, cartService, child) {
                  int cartItems = cartService.cartDonuts.length;

                  return Container(
                    constraints: BoxConstraints(minHeight: 70),
                    padding: EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      color: cartItems > 0 ? 
                      (bottomBarSelectionService.tabSelection! == 'shopping' ? 
                      Utils.mainDark : Utils.mainColor)
                        : Colors.transparent,
                      borderRadius: BorderRadius.circular(50)
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        cartItems > 0 ? 
                        Text('$cartItems', style: TextStyle(
                          color: Colors.white, 
                          fontWeight: FontWeight.bold, fontSize: 14)
                        ) : SizedBox(height: 17),
                        SizedBox(height: 10),
                        Icon(Icons.shopping_cart, 
                          color: cartItems > 0 ? Colors.white : 
                          (bottomBarSelectionService.tabSelection! == 'shopping' ? 
                          Utils.mainDark : Utils.mainColor)
                        )
                      ]
                    )
                  );
                }
              )
            ]
          );
      })
    );
  }
}

class DonutFilterBar extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(20),
      child: Consumer<DonutService>(
        builder: (context, donutService, child) {
          return Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: List.generate(
                  donutService.filterBarItems.length, (index) {

                    DonutFilterBarItem item = donutService.filterBarItems[index];

                    return  GestureDetector(
                      onTap: () { 
                        donutService.filteredDonutsByType(item.id!);
                      },
                      child: Container(
                        child: Text('${item.label!}', 
                        style: TextStyle(
                          color: donutService.selectedDonutType == item.id ? 
                          Utils.mainColor : Colors.black, fontWeight: FontWeight.bold)
                        )
                      )
                    );
                  }
                )
              ),
              SizedBox(height: 10),
              Stack(
                children: [
                  AnimatedAlign(
                    duration: const Duration(milliseconds: 250),
                    curve: Curves.easeInOut,
                    alignment: alignmentBasedOnTap(donutService.selectedDonutType),
                    child: Container(
                      width: MediaQuery.of(context).size.width / 3 - 20,
                      height: 5,
                      decoration: BoxDecoration(
                        color: Utils.mainColor,
                        borderRadius: BorderRadius.circular(20)
                      )
                    )
                  )
                ],
              )
            ]
          );
        }
      )
    );
  }

  Alignment alignmentBasedOnTap(filterBarId) {
    
    switch(filterBarId) {
      case 'classic':
        return Alignment.centerLeft;
      case 'sprinkled':
        return Alignment.center;
      case 'stuffed':
        return Alignment.centerRight;
      default:
        return Alignment.centerLeft;
    }
  }
}

class DonutList extends StatefulWidget {
  List<DonutModel>? donuts;

  DonutList({ this.donuts });

  @override
  State<DonutList> createState() => _DonutListState();
}

class _DonutListState extends State<DonutList> {
  final GlobalKey<AnimatedListState> _key = GlobalKey();
  List<DonutModel> insertedItems = [];

  @override 
  void initState() {
    super.initState();

    var future = Future(() {});
    for (var i = 0; i < widget.donuts!.length; i++) {
      future = future.then((_) {
        return Future.delayed(const Duration(milliseconds: 125), () {
          insertedItems.add(widget.donuts![i]);
          _key.currentState!.insertItem(i);
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedList(
      key: _key,
      scrollDirection: Axis.horizontal,
      initialItemCount: insertedItems.length,
      itemBuilder: (context, index, animation) {
        
        DonutModel currentDonut = widget.donuts![index];

        return SlideTransition(
          position: Tween(
            begin: const Offset(0.2, 0.0),
            end: const Offset(0.0, 0.0),
          ).animate(CurvedAnimation(parent: animation, curve: Curves.easeInOut)),
          child: FadeTransition(
            opacity: Tween(begin: 0.0, end: 1.0)
            .animate(CurvedAnimation(
              parent: animation, curve: Curves.easeInOut)
            ),
            child: DonutCard(donutInfo: currentDonut)
          )
        );
      }
    );
  }
}

class DonutCard extends StatelessWidget {

  DonutModel? donutInfo;
  DonutCard({ this.donutInfo });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        var donutService = Provider.of<DonutService>(context, listen: false);
        donutService.onDonutSelected(donutInfo!);
      },
      child: Stack(
        alignment: Alignment.center,
        children: [
          Container(
            width: 150,
            padding: EdgeInsets.all(15),
            alignment: Alignment.bottomLeft,
            margin: EdgeInsets.only(left: 10, top: 80, right: 10, bottom: 20),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.05), 
                  blurRadius: 10, 
                  offset: Offset(0.0, 4.0)
                )
              ]
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.end,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('${donutInfo!.name}', 
                  style: TextStyle(
                    color: Utils.mainDark, 
                    fontWeight: FontWeight.bold, 
                    fontSize: 15
                  )
                ),
                SizedBox(height: 10),
                Container(
                  decoration: BoxDecoration(
                    color: Utils.mainColor,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  padding: EdgeInsets.only(
                    left: 10, right: 10, top: 5, bottom: 5
                  ),
                  child: Text('\$${donutInfo!.price!.toStringAsFixed(2)}', 
                    style: TextStyle(
                      fontSize: 12, 
                      color: Colors.white, 
                      fontWeight: FontWeight.bold
                    )
                  )
                )
              ]
            ),
          ),
          Align(
            alignment: Alignment.topCenter,
            child: Hero(
              tag: donutInfo!.name!,
              child: Image.network(
                  donutInfo!.imgUrl!, 
                  width: 150, height: 150, 
                  fit: BoxFit.contain
                )
              ),
          )
        ]
      ),
    );
  }
}

class DonutBottomBarSelectionService extends ChangeNotifier {

  String? tabSelection = 'main';

  void setTabSelection(String selection) {
    Utils.mainListNav.currentState!.pushReplacementNamed('/' + selection);
    tabSelection = selection;
    notifyListeners();
  }
}

class DonutService extends ChangeNotifier {

  List<DonutFilterBarItem> filterBarItems = [
    DonutFilterBarItem(id: 'classic', label: 'Classic'),
    DonutFilterBarItem(id: 'sprinkled', label: 'Sprinkled'),
    DonutFilterBarItem(id: 'stuffed', label: 'Stuffed'),
  ];

  String? selectedDonutType;
  List<DonutModel> filteredDonuts = [];


  late DonutModel selectedDonut;

  DonutModel getSelecteDonut() {
    return selectedDonut;
  }

  void onDonutSelected(DonutModel donut) {
    selectedDonut = donut;
    Utils.mainAppNav.currentState!.pushNamed('/details');
  }

  DonutService() {
    selectedDonutType = filterBarItems.first.id;
    filteredDonutsByType(selectedDonutType!);
  }

  void filteredDonutsByType(String type) {
    selectedDonutType = type;
    filteredDonuts = Utils.donuts.where(
      (d) => d.type == selectedDonutType).toList();

    notifyListeners();
  }
}

class DonutShoppingCartService extends ChangeNotifier {

  List<DonutModel> cartDonuts = [];

  void addToCart(DonutModel donut) {
    cartDonuts.add(donut);
    notifyListeners();
  }

  void removeFromCart(DonutModel donut) {
    cartDonuts.removeWhere((d) => d.name == donut.name);
    notifyListeners();
  }

  void clearCart() {
    cartDonuts.clear();
    notifyListeners();
  }

  double getTotal() {
    double cartTotal = 0.0;
    cartDonuts.forEach((element) {
      cartTotal += element.price!;
    });

    return cartTotal;
  }

  bool isDonutInCart(DonutModel donut) {
    return cartDonuts.any((d) => d.name == donut.name);
  }
}

class DonutShopDetails extends StatefulWidget {
  @override
  State<DonutShopDetails> createState() => _DonutShopDetailsState();
}

class _DonutShopDetailsState extends State<DonutShopDetails> 
    with SingleTickerProviderStateMixin{

  DonutModel? selectedDonut;
  AnimationController? controller;
  Animation<double>? rotationAnimation;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      duration: const Duration(
        seconds: 20), 
        vsync: this)..repeat();

    rotationAnimation = Tween<double>(begin: 0, end: 1)
    .animate(CurvedAnimation(
      parent: controller!, curve: Curves.linear));
  }

  @override
  void dispose() {
    controller!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    DonutService donutService = Provider.of<DonutService>(context, listen: false);
    selectedDonut = donutService.getSelecteDonut();

    return Scaffold(
      appBar: AppBar(
        iconTheme: const IconThemeData(color: Utils.mainDark),
        backgroundColor: Colors.transparent,
        elevation: 0,
        title: SizedBox(
          width: 120,
          child: Image.network(Utils.donutLogoRedText)
        ),
        actions: [
          DonutShoppingCartBadge()
        ]
      ),
      body: Column(
        children: [
          Container(
            height: MediaQuery.of(context).size.height / 2,
            child: Stack(
              clipBehavior: Clip.none,
              children: [
                Positioned(
                  top: -40,
                  right: -120,
                  child: Hero(
                    tag: selectedDonut!.name!,
                    child: RotationTransition(
                      turns: rotationAnimation!,
                      child: Image.network(selectedDonut!.imgUrl!,
                        width: MediaQuery.of(context).size.width * 1.25, 
                        fit: BoxFit.contain
                      )
                    )
                  ),
                )
              ]
            )
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(30),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Expanded(
                        child: Text('${selectedDonut!.name!}',
                          style: TextStyle(color: Utils.mainDark, 
                          fontSize: 30, 
                          fontWeight: FontWeight.bold)    
                        )
                      ),
                      SizedBox(width: 50),
                      IconButton(
                        icon: Icon(Icons.favorite_outline),
                        color: Utils.mainDark,
                        onPressed: () {}
                      )
                    ]
                  ),
                  SizedBox(height: 10),
                  Container(
                    padding: EdgeInsets.only(top: 10, bottom: 10, left: 20, right: 20),
                    decoration: BoxDecoration(
                      color: Utils.mainDark,
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Text('\$${selectedDonut!.price!.toStringAsFixed(2)}', 
                      style: TextStyle(color: Colors.white)
                    ),
                  ),
                  SizedBox(height: 20),
                  Text('${selectedDonut!.description!}'),

                  Consumer<DonutShoppingCartService>(
                    builder: (context, cartService, child) {

                      if (!cartService.isDonutInCart(selectedDonut!)) {
                        return GestureDetector(
                          onTap: () {
                            cartService.addToCart(selectedDonut!);
                          },
                          child: Container(
                            margin: EdgeInsets.only(top: 20),
                            alignment: Alignment.center,
                            padding: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 10),
                            decoration: BoxDecoration(
                              color: Utils.mainDark.withOpacity(0.1),
                              borderRadius: BorderRadius.circular(50)
                            ),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Icon(Icons.shopping_cart, color: Utils.mainDark),
                                SizedBox(width: 20),
                                Text('Add To Cart', style: TextStyle(color: Utils.mainDark)),
                              ]
                            )
                          )
                        );
                      }

                      return Padding(
                        padding: EdgeInsets.only(top: 30, bottom: 30),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Icon(Icons.check_rounded, color: Utils.mainDark),
                            SizedBox(width: 20),
                            Text('Added to Cart', style: TextStyle(
                              fontWeight: FontWeight.bold, color: Utils.mainDark)
                            )
                          ],
                        ),
                      );
                    }
                  )


                ],
              ),
            )
          )
        ]
      )
    );
  }
}

class DonutShoppingCartBadge extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return Consumer<DonutShoppingCartService>(
      builder: (context, cartService, child) {

        if (cartService.cartDonuts.isEmpty) {
          return SizedBox();
        }

        return Transform.scale(
          scale: 0.7,
          child: Container(
            margin: EdgeInsets.only(right: 10),
            padding: EdgeInsets.only(left: 20, right: 20),
            decoration: BoxDecoration(
              color: Utils.mainColor,
              borderRadius: BorderRadius.circular(40)
            ),
            child: Row(
              children: [
                Text('${cartService.cartDonuts.length}', style: TextStyle(fontSize: 20, 
                  color: Colors.white, fontWeight: FontWeight.bold)
                ),
                SizedBox(width: 10),
                Icon(Icons.shopping_cart, size: 25, color: Colors.white)
              ]
            )
          )
        );
      }
    );
  }
}

class DonutShoppingCartPage extends StatefulWidget {
  @override
  State<DonutShoppingCartPage> createState() => _DonutShoppingCartPageState();
}

class _DonutShoppingCartPageState extends State<DonutShoppingCartPage> 
 with SingleTickerProviderStateMixin {

  AnimationController? titleAnimation;

  @override
  void initState() {
    super.initState();

    titleAnimation = AnimationController(
      duration: const Duration(milliseconds: 500),vsync: this
    )..forward();
  }

  @override 
  void dispose() {
    titleAnimation!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
        padding: EdgeInsets.all(40),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [

            FadeTransition(
              opacity: Tween(begin: 0.0, end: 1.0)
                .animate(CurvedAnimation(parent: titleAnimation!, 
                  curve: Curves.easeInOut)
                ),
                child: Image.network(Utils.donutTitleMyDonuts, width: 170)
            ),
            Expanded(
              child: Consumer<DonutShoppingCartService>(
                builder: (context, cartService, child) {

                  if (cartService.cartDonuts.isEmpty) {
                    return Center(
                      child: SizedBox(
                        width: 200,
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Icon(Icons.shopping_cart, color: Colors.grey[300], size: 50),
                            SizedBox(height: 20),
                            Text('You don\'t have any items on your cart yet!',
                              textAlign: TextAlign.center,
                              style: TextStyle(color: Colors.grey)
                            )
                          ]
                        )
                      )
                    );
                  }

                  return DonutShoppingList(
                    donutCart: cartService.cartDonuts,
                    cartService: cartService,  
                  );
                }
              )
            ),
            Consumer<DonutShoppingCartService>(
              builder: (context, cartService, child) {
                
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    cartService.cartDonuts.isEmpty ? SizedBox() : Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('Total', style: TextStyle(color: Utils.mainDark)),
                        Text('\$${cartService.getTotal().toStringAsFixed(2)}',
                          style: TextStyle(color: Utils.mainDark, 
                          fontWeight: FontWeight.bold, fontSize: 30)
                        )
                      ],
                    ),
                    ClipRRect(
                      borderRadius: BorderRadius.circular(30),
                      child: Material(
                        color: cartService.cartDonuts.isEmpty ? 
                          Colors.grey[200] : Utils.mainColor.withOpacity(0.2),
                        child: InkWell(
                          splashColor: Utils.mainDark.withOpacity(0.2),
                          highlightColor: Utils.mainDark.withOpacity(0.5),
                          onTap: cartService.cartDonuts.isEmpty ? null : () {
                            cartService.clearCart();
                          },
                          child: 
                          Container(
                            padding: EdgeInsets.only(
                              top: 10, bottom: 10, 
                              left: 20, right: 20
                            ),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.start,
                              children: [
                                Icon(Icons.delete_forever, 
                                  color: cartService.cartDonuts.isEmpty ? 
                                  Colors.grey : Utils.mainDark
                                ),
                                Text('Clear Cart', style: TextStyle(
                                  color: cartService.cartDonuts.isEmpty ? 
                                  Colors.grey : Utils.mainDark)
                                )
                              ],
                            )
                          )
                        )
                      )
                    )
                  ]
                );


              }
            )
          ]
        )
    );
  }
}

class DonutShoppingListRow extends StatelessWidget {

  DonutModel? donut;
  Function? onDeleteRow;

  DonutShoppingListRow({ this.donut, required this.onDeleteRow });
  
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 10, bottom: 10, right: 20),
      child: Row(
        children: [
          Image.network('${donut!.imgUrl}', width: 80, height: 80),
          SizedBox(width: 10),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('${donut!.name}', 
                  style: TextStyle(color: Utils.mainDark, 
                  fontSize: 15, fontWeight: FontWeight.bold)
                ),
                SizedBox(height: 5),
                Container(
                  padding: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10),
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(20),
                    border: Border.all(width: 2, color: Utils.mainDark.withOpacity(0.2))
                  ),
                  child: Text('\$${donut!.price!.toStringAsFixed(2)}', 
                    style: TextStyle(color: Utils.mainDark.withOpacity(0.4), 
                    fontWeight: FontWeight.bold)
                  ),
                )
              ]
            )
          ),
          SizedBox(width: 10),
          IconButton(
            onPressed: () { onDeleteRow!(); }, 
            icon: Icon(Icons.delete_forever, color: Utils.mainColor)
          )
        ]
      )
    );
  }
}

class DonutShoppingList extends StatefulWidget {

  List<DonutModel>? donutCart;
  DonutShoppingCartService? cartService;
  DonutShoppingList({ this.donutCart, this.cartService });

  @override
  State<DonutShoppingList> createState() => _DonutShoppingListState();
}

class _DonutShoppingListState extends State<DonutShoppingList> {
  final GlobalKey<AnimatedListState> _key = GlobalKey();
  List<DonutModel> insertedItems = [];

  @override
  void initState() {
    super.initState();

    var future = Future(() {});
    for (var i = 0; i < widget.donutCart!.length; i++) {
      future = future.then((_) {
        return Future.delayed(const Duration(milliseconds: 125), () {
          insertedItems.add(widget.donutCart![i]);
          _key.currentState!.insertItem(i);
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedList(
      key: _key,
      initialItemCount: insertedItems.length,
      itemBuilder: (context, index, animation) {
        DonutModel currentDonut = widget.donutCart![index];

        return SlideTransition(
          position: Tween(
            begin: const Offset(0.0, 0.2),
            end: const Offset(0.0, 0.0),
          )
          .animate(CurvedAnimation(
            parent: animation,
            curve: Curves.easeInOut
          )),
          child: FadeTransition(
            opacity: Tween(begin: 0.0, end: 1.0)
            .animate(CurvedAnimation(parent: animation, 
            curve: Curves.easeInOut)),
            child: DonutShoppingListRow(
              donut: currentDonut,
              onDeleteRow: () {
                widget.cartService!.removeFromCart(currentDonut);
              }
            )
          )
        );
      },
    );
  }
}

class DonutFilterBarItem {
  String? id;
  String? label;

  DonutFilterBarItem({ this.id, this.label });
}

class DonutPage {
  String? imgUrl;
  String? logoImgUrl;
  
  DonutPage({ this.imgUrl, this.logoImgUrl });
}

class DonutModel {

  String? imgUrl;
  String? name;
  String? description;
  double? price;
  String? type;

  DonutModel({
    this.imgUrl,
    this.name,
    this.description,
    this.price,
    this.type
  });
}

class Utils {
  static GlobalKey<NavigatorState> mainListNav = GlobalKey();
  static GlobalKey<NavigatorState> mainAppNav = GlobalKey();

  static const Color mainColor = Color(0xFFFF0F7E);
  static const Color mainDark = Color(0xFF980346);
  static const String donutLogoWhiteNoText = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_shop_logowhite_notext.png';
  static const String donutLogoWhiteText = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_shop_text_reversed.png';
  static const String donutLogoRedText = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_shop_text.png';
  static const String donutTitleFavorites = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_favorites_title.png';
  static const String donutTitleMyDonuts = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_mydonuts_title.png';
  static const String donutPromo1 = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_promo1.png';
  static const String donutPromo2 = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_promo2.png';
  static const String donutPromo3 = 'https://romanejaquez.github.io/flutter-codelab4/assets/donut_promo3.png';

  static List<DonutModel> donuts = [
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutclassic/donut_classic1.png',
      name: 'Strawberry Sprinkled Glazed',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'classic'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutclassic/donut_classic2.png',
      name: 'Chocolate Glazed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'classic',
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutclassic/donut_classic3.png',
      name: 'Chocolate Dipped Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'classic'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutclassic/donut_classic4.png',
      name: 'Cinamon Glazed Glazed',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'classic'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutclassic/donut_classic5.png',
      name: 'Sugar Glazed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'classic'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutsprinkled/donut_sprinkled1.png',
      name: 'Halloween Chocolate Glazed',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'sprinkled'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutsprinkled/donut_sprinkled2.png',
      name: 'Party Sprinkled Cream',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'sprinkled'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutsprinkled/donut_sprinkled3.png',
      name: 'Chocolate Glazed Sprinkled',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'sprinkled'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutsprinkled/donut_sprinkled4.png',
      name: 'Strawbery Glazed Sprinkled',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'sprinkled'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutsprinkled/donut_sprinkled5.png',
      name: 'Reese\'s Sprinkled',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 3.99,
      type: 'sprinkled'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutstuffed/donut_stuffed1.png',
      name: 'Brownie Cream Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'stuffed'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutstuffed/donut_stuffed2.png',
      name: 'Jelly Stuffed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.99,
      type: 'stuffed'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutstuffed/donut_stuffed3.png',
      name: 'Caramel Stuffed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 2.59,
      type: 'stuffed'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutstuffed/donut_stuffed4.png',
      name: 'Maple Stuffed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.99,
      type: 'stuffed'
    ),
    DonutModel(
      imgUrl: 'https://romanejaquez.github.io/flutter-codelab4/assets/donutstuffed/donut_stuffed5.png',
      name: 'Glazed Jelly Stuffed Doughnut',
      description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce blandit, tellus condimentum cursus gravida, lorem augue venenatis elit, sit amet bibendum quam neque id sapien.',
      price: 1.59,
      type: 'stuffed'
    )
  ];
}