Created with <3 with dartpad.dev.
Last active
May 16, 2023 18:14
-
-
Save plotsklapps/6f8ba76b99aef3b06e7df2407b544cd7 to your computer and use it in GitHub Desktop.
Flutter Architecture #2
This file contains hidden or 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
| import 'package:flutter/material.dart'; | |
| import 'package:provider/provider.dart'; | |
| // MAIN | |
| void main() { | |
| runApp( | |
| MultiProvider( | |
| providers: [ | |
| ChangeNotifierProvider(create: (_) { | |
| return DonutBottomBarSelectionService(); | |
| }), | |
| ChangeNotifierProvider(create: (_) { | |
| return DonutService(); | |
| }), | |
| ], | |
| child: MaterialApp( | |
| debugShowCheckedModeBanner: false, | |
| initialRoute: '/', | |
| navigatorKey: Utils.mainAppNav, | |
| routes: { | |
| '/': (BuildContext context) { | |
| return SplashPage(); | |
| }, | |
| '/main': (BuildContext context) { | |
| return DonutShopMain(); | |
| }, | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| // PAGES | |
| class SplashPage extends StatefulWidget { | |
| @override | |
| SplashPageState createState() { | |
| return SplashPageState(); | |
| } | |
| } | |
| class SplashPageState extends State<SplashPage> | |
| with SingleTickerProviderStateMixin { | |
| // The SingleTickerProviderStateMixin mixin will provide this class | |
| // with the Ticker instance it needs; an AnimationController needs a | |
| // TickerProvider — the AnimationController constructor takes a required | |
| // parameter vsync that must implement a TickerProvider interface, | |
| // therefore we are implementing SingleTickerProviderStateMixin on this | |
| // class so it serves as the AnimationController's ticker provider. | |
| AnimationController? donutController; | |
| Animation<double>? rotationAnimation; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| // vsync: the instance of the Ticker provider; in this case, the | |
| // existing instance (this) since we are implementing the | |
| // SingleTickerProviderStateMixin. Follow the instantiation by quickly | |
| // executing its repeat method by using the spread operator | |
| // (the two dots right after the instance; i.e. ..repeat()). | |
| // What this does is allow the animation to repeat in an infinite loop. | |
| 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 takes two parameters: a Duration object | |
| // with it's seconds property set to 2, and a callback. | |
| // When the 2 seconds have ellapsed, it will call the callback. | |
| Future.delayed(const Duration(seconds: 2), () { | |
| Utils.mainAppNav.currentState!.pushReplacementNamed( | |
| '/main', | |
| ); | |
| }); | |
| return Scaffold( | |
| backgroundColor: Utils.mainColor, | |
| body: Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| RotationTransition( | |
| turns: rotationAnimation!, | |
| child: Image.network( | |
| Utils.donutLogoWhiteNoText, | |
| width: 100.0, | |
| height: 100.0, | |
| ), | |
| ), | |
| Image.network( | |
| Utils.donutLogoWhiteText, | |
| width: 150.0, | |
| height: 150.0, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class DonutShopMain extends StatelessWidget { | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| iconTheme: const IconThemeData( | |
| color: Utils.mainDark, | |
| ), | |
| backgroundColor: Colors.transparent, | |
| elevation: 0.0, | |
| centerTitle: true, | |
| title: Image.network( | |
| Utils.donutLogoRedText, | |
| ), | |
| ), | |
| drawer: Drawer(child: DonutSideMenu(),), | |
| body: Column( | |
| children: [ | |
| Expanded( | |
| child: Navigator( | |
| key: Utils.mainListNav, | |
| initialRoute: '/main', | |
| // When a user calls Utils.mainListNav.currentState.pushNamed() | |
| // and pass the named route belonging to this child navigation stack, | |
| // the onGenerateRoute gets invoked. Its settings parameter type | |
| // RouteSettings has a property called named which you should use | |
| // to match the provided named route to the widget to be pushed at | |
| // the top of this child navigation stack. | |
| // At the end of the onGenerateRoute method, return a | |
| // PageRouteBuilder, provided a pageBuilder callback, which | |
| // returns the widget to be displayed onto the stack, as well | |
| // as a transitionDuration, in case you want to provide a duration | |
| // - in our case, we set a Duration to zero so no transition occurs. | |
| onGenerateRoute: (RouteSettings settings) { | |
| Widget page; | |
| if (settings.name == '/favorites') { | |
| page = const Center( | |
| child: Text('favorites'), | |
| ); | |
| } else if (settings.name == '/shoppingcart') { | |
| page = const Center( | |
| child: Text('shopping cart'), | |
| ); | |
| } else { | |
| page = DonutMainPage(); | |
| } | |
| 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: ( | |
| BuildContext context, | |
| DonutService donutService, | |
| Widget? child, | |
| ) { | |
| return DonutList(donutsList: donutService.filteredDonuts); | |
| })), | |
| ], | |
| ); | |
| } | |
| } | |
| // WIDGETS | |
| class DonutSideMenu extends StatelessWidget { | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| color: Utils.mainDark, | |
| padding: const EdgeInsets.all(40.0), | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Container( | |
| margin: const EdgeInsets.only(top: 40.0), | |
| child: Image.network( | |
| Utils.donutLogoWhiteNoText, | |
| width: 100.0, | |
| ), | |
| ), | |
| Image.network( | |
| Utils.donutLogoWhiteText, | |
| width: 150.0, | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class DonutPager extends StatefulWidget { | |
| @override | |
| State<DonutPager> createState() { | |
| return 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? pageController; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| pageController = PageController(initialPage: 0); | |
| } | |
| @override | |
| void dispose() { | |
| pageController!.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return SizedBox( | |
| height: 350.0, | |
| child: Column( | |
| children: [ | |
| Expanded( | |
| child: PageView( | |
| scrollDirection: Axis.horizontal, | |
| pageSnapping: true, | |
| controller: pageController, | |
| onPageChanged: (int page) { | |
| setState(() { | |
| currentPage = page; | |
| }); | |
| }, | |
| // List.generate's callback method receives an index, which represents | |
| // the current item in the iteration, with which we'll pull the | |
| // corresponding DonutPage object from the list of pages to build the page. | |
| children: List.generate(pages.length, (index) { | |
| DonutPage currentPage = pages[index]; | |
| return Container( | |
| alignment: Alignment.bottomLeft, | |
| margin: const EdgeInsets.all(20.0), | |
| padding: const EdgeInsets.all(30.0), | |
| decoration: BoxDecoration( | |
| borderRadius: BorderRadius.circular(30.0), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black.withOpacity(0.2), | |
| blurRadius: 10.0, | |
| offset: const Offset(0.0, 5.0), | |
| ), | |
| ], | |
| image: DecorationImage( | |
| image: NetworkImage(currentPage.imgUrl!), | |
| fit: BoxFit.cover, | |
| ), | |
| ), | |
| child: Image.network(currentPage.logoImgUrl!, width: 120.0,), | |
| ); | |
| }), | |
| ), | |
| ), | |
| PageViewIndicator( | |
| pageController: pageController, | |
| numberOfPages: pages.length, | |
| currentPage: currentPage, | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class PageViewIndicator extends StatelessWidget { | |
| final PageController? pageController; | |
| final int? numberOfPages; | |
| final int? currentPage; | |
| const PageViewIndicator({ | |
| this.pageController, | |
| this.numberOfPages, | |
| this.currentPage, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: List.generate(numberOfPages!, (index) { | |
| return GestureDetector( | |
| onTap: () { | |
| // We will leverage the GestureDetector's onTap event, | |
| // and inside it, use the PageController's animateToPage method, | |
| // to which you'll pass the index of the page indicator (which | |
| // will be the same as the page index), the duration of the | |
| // page sliding effect, and the curve of the animation. | |
| pageController!.animateToPage( | |
| index, | |
| duration: const Duration(milliseconds: 500), | |
| curve: Curves.easeInOut, | |
| ); | |
| }, | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 250), | |
| curve: Curves.easeInOut, | |
| width: 15.0, | |
| height: 15.0, | |
| margin: const EdgeInsets.all(10.0), | |
| decoration: BoxDecoration( | |
| color: currentPage == index | |
| ? Utils.mainColor | |
| : Colors.grey.withOpacity(0.2), | |
| borderRadius: BorderRadius.circular(10.0), | |
| ), | |
| ), | |
| ); | |
| }), | |
| ); | |
| } | |
| } | |
| class DonutList extends StatefulWidget { | |
| final List<DonutModel>? donutsList; | |
| const DonutList({this.donutsList}); | |
| @override | |
| State<DonutList> createState() { | |
| return DonutListState(); | |
| } | |
| } | |
| class DonutListState extends State<DonutList> { | |
| // Add a GlobalKey reference in the _DonutListState class. | |
| // All AnimatedList widgets require a key in order to tap into | |
| // its inserting capabilities so we can uniquely reference them | |
| // outside of the build method of the enclosing widget. | |
| final GlobalKey<AnimatedListState> key = GlobalKey(); | |
| List<DonutModel> insertedItems = []; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| // Each item will be inserted from the main collection (widget.donutsList) | |
| // to a holding collection (insertedItems). | |
| // As they are inserted, we introduce a delay of 125ms. | |
| // The AnimatedList widget receives one at a time within 125 ms from | |
| // each other, and performs whatever animation you decide. | |
| var future = Future(() {}); | |
| for (var i = 0; i < widget.donutsList!.length; i++) { | |
| future = future.then((_) { | |
| return Future.delayed(const Duration(milliseconds: 125), () { | |
| insertedItems.add(widget.donutsList![i]); | |
| key.currentState!.insertItem(i); | |
| }); | |
| }); | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return AnimatedList( | |
| key: key, | |
| scrollDirection: Axis.horizontal, | |
| initialItemCount: insertedItems.length, | |
| itemBuilder: (BuildContext context, int index, animation,) { | |
| DonutModel currentDonut = widget.donutsList![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 { | |
| // Add a new property to this DonutCard widget called donutInfo, | |
| // type DonutModel, and feed it via the constructor. We'll use | |
| // this property to feed data into this widget and render the | |
| // text labels and image | |
| final DonutModel? donutInfo; | |
| const DonutCard({this.donutInfo}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Stack( | |
| alignment: Alignment.center, | |
| children: [ | |
| Container( | |
| width: 150.0, | |
| padding: const EdgeInsets.all(15.0), | |
| margin: const EdgeInsets.fromLTRB(10.0, 80.0, 0.0, 20.0), | |
| alignment: Alignment.bottomLeft, | |
| decoration: BoxDecoration( | |
| color: Colors.white, | |
| borderRadius: BorderRadius.circular(20.0), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black.withOpacity(0.05), | |
| blurRadius: 10.0, | |
| offset: const Offset(0.0, 4.0), | |
| ), | |
| ], | |
| ), | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.end, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| donutInfo!.name!, | |
| style: const TextStyle( | |
| color: Utils.mainDark, | |
| fontWeight: FontWeight.bold, | |
| fontSize: 15.0, | |
| ), | |
| ), | |
| const SizedBox(height: 10.0), | |
| Container( | |
| padding: const EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0), | |
| decoration: BoxDecoration( | |
| color: Utils.mainColor, | |
| borderRadius: BorderRadius.circular(20.0), | |
| ), | |
| child: Center( | |
| child: Text( | |
| '\$${donutInfo!.price!.toStringAsFixed(2)}', | |
| style: const TextStyle( | |
| fontSize: 12.0, | |
| color: Colors.white, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| Align( | |
| alignment: Alignment.topCenter, | |
| child: Image.network( | |
| donutInfo!.imgUrl!, | |
| width: 150.0, | |
| height: 150.0, | |
| fit: BoxFit.contain, | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| class DonutFilterBar extends StatelessWidget { | |
| @override | |
| Widget build(BuildContext context) { | |
| Alignment alignmentBasedOnTap(filterBarId) { | |
| if (filterBarId == 'sprinkled') { | |
| return Alignment.center; | |
| } else if (filterBarId == 'stuffed') { | |
| return Alignment.centerRight; | |
| } else { | |
| return Alignment.centerLeft; | |
| } | |
| } | |
| return Padding( | |
| padding: const EdgeInsets.all(20.0), | |
| child: Consumer<DonutService>( | |
| builder: (BuildContext context, donutService, Widget? child,) { | |
| return Column( | |
| children: [ | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceAround, | |
| children: | |
| List.generate(donutService.filterBarItems.length, (index) { | |
| DonutFilterBarItem item = donutService.filterBarItems[index]; | |
| return GestureDetector( | |
| onTap: () { | |
| // Invoke the donutService.filteredDonutsByType method, | |
| // passing the corresponding item's id upon the user | |
| // tapping on the filter bar item. | |
| donutService.filteredDonutsByType(item.id!); | |
| }, | |
| child: Text( | |
| item.label!, | |
| style: TextStyle( | |
| color: donutService.selectedDonutType == item.id | |
| ? Utils.mainColor | |
| : Colors.black, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ); | |
| }), | |
| ), | |
| const SizedBox(height: 10.0), | |
| 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.0, | |
| height: 5.0, | |
| decoration: BoxDecoration( | |
| color: Utils.mainColor, | |
| borderRadius: BorderRadius.circular(20.0), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ); | |
| }), | |
| ); | |
| } | |
| } | |
| class DonutBottomBar extends StatelessWidget { | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| padding: const EdgeInsets.all(30.0), | |
| // Now that the service has been provided at the root, since the | |
| // DonutBottomBar widget is a widget located down the hierarchy, | |
| // we know for sure this service will trickle down to it. In order | |
| // to consume it - listen to changes occurring when the tab selection | |
| // changes (i.e. the setTabSelection method gets called), we will use | |
| // a convenient widget called Consumer. The Consumer widget rebuilds | |
| // itself when the notifyListeners method of the service it is | |
| // listening to gets called. | |
| // Notice how we make the Consumer widget a direct child of the main | |
| // Container widget in the bottom bar, which in turn returns the Row | |
| // widget from its builder callback method. This callback provides | |
| // a context, an instance of the service being listened to | |
| // (bottomBarSelectionService) and an optional child. | |
| child: Consumer<DonutBottomBarSelectionService>(builder: ( | |
| BuildContext context, | |
| DonutBottomBarSelectionService bottomBarSelectionService, | |
| Widget? child, | |
| ) { | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| crossAxisAlignment: CrossAxisAlignment.end, | |
| children: [ | |
| // Each button will call up to the service, set the value, | |
| // and thus invoking the notifyListeners method, which will | |
| // cause the Consumer widget to rebuild. | |
| IconButton( | |
| onPressed: () { | |
| bottomBarSelectionService.setTabSelection( | |
| 'main', | |
| ); | |
| }, | |
| icon: Icon( | |
| Icons.trip_origin, | |
| color: bottomBarSelectionService.tabSelection == 'main' | |
| ? Utils.mainDark | |
| : Utils.mainColor, | |
| ), | |
| ), | |
| IconButton( | |
| onPressed: () { | |
| bottomBarSelectionService.setTabSelection( | |
| 'favorites', | |
| ); | |
| }, | |
| icon: Icon( | |
| Icons.favorite, | |
| color: bottomBarSelectionService.tabSelection == 'favorites' | |
| ? Utils.mainDark | |
| : Utils.mainColor, | |
| ), | |
| ), | |
| IconButton( | |
| onPressed: () { | |
| bottomBarSelectionService.setTabSelection( | |
| 'shoppingcart', | |
| ); | |
| }, | |
| icon: Icon( | |
| Icons.shopping_cart, | |
| color: | |
| bottomBarSelectionService.tabSelection == 'shoppingcart' | |
| ? Utils.mainDark | |
| : Utils.mainColor, | |
| ), | |
| ), | |
| ]); | |
| }), | |
| ); | |
| } | |
| } | |
| // SERVICES | |
| // The ChangeNotifier class provides change notification capabilities | |
| // for those interested widgets in knowing when changes occur in any of | |
| // their properties, which is accomplished by calling its notifyListeners | |
| // method. | |
| class DonutBottomBarSelectionService extends ChangeNotifier { | |
| // We are interested in storing what the current bottom bar item | |
| // selection is at any given time, so we'll store that in a variable | |
| // called tabSelection, type string. We'll have a default value of "main" | |
| // which means there will be a bottom bar item named "main" that will | |
| // always be selected by default. | |
| String? tabSelection = 'main'; | |
| // We will provide a handy setter method called setTabSelection that | |
| // takes the new selection, and right after storing the new value, we | |
| // will invoke notifyListeners, which notifies any listening widgets | |
| // (i.e. Consumer widget) to rebuild based on the new changes. | |
| 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 = []; | |
| DonutService() { | |
| selectedDonutType = filterBarItems.first.id; | |
| // Execute the filteredDonutsByType in the DonutService's | |
| // constructor, using the default value assigned to | |
| // selectedDonutType from the first item in the filterBarItems | |
| // collection, and assigning it to the filteredDonuts collection. | |
| filteredDonutsByType(selectedDonutType!); | |
| } | |
| void filteredDonutsByType(String type) { | |
| selectedDonutType = type; | |
| // In the existing method called filteredDonutsByType, right | |
| // after we assigned the selectedDonutType with the currently | |
| // selected filter bar item from the DonutFilterBar widget, | |
| // perform a filter against the existing mocked data in the | |
| // Utils.donuts, and retrieved a filtered list of the donuts | |
| // that match their type against the selectedDonutType; for this, | |
| // use the existing type property from the DonutModel, then | |
| // assign the resulting list to the filteredDonuts collection. | |
| filteredDonuts = | |
| Utils.donuts.where((d) => d.type == selectedDonutType).toList(); | |
| notifyListeners(); | |
| } | |
| } | |
| // PODO | |
| class DonutPage { | |
| String? imgUrl; | |
| String? logoImgUrl; | |
| DonutPage({ | |
| this.imgUrl, | |
| this.logoImgUrl, | |
| }); | |
| } | |
| class DonutFilterBarItem { | |
| String? id; | |
| String? label; | |
| DonutFilterBarItem({ | |
| this.id, | |
| this.label, | |
| }); | |
| } | |
| class DonutModel { | |
| String? imgUrl; | |
| String? name; | |
| String? description; | |
| double? price; | |
| String? type; | |
| DonutModel({ | |
| this.imgUrl, | |
| this.name, | |
| this.description, | |
| this.price, | |
| this.type, | |
| }); | |
| } | |
| // UTILS | |
| 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') | |
| ]; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Started over 15-05-2023 to refresh.