Skip to content

Instantly share code, notes, and snippets.

@ch-muhammad-adil
Forked from plotsklapps/main.dart
Created May 17, 2023 16:51
Show Gist options
  • Save ch-muhammad-adil/ca5a1a6eab8bc7a5b4436201ff5029ea to your computer and use it in GitHub Desktop.
Save ch-muhammad-adil/ca5a1a6eab8bc7a5b4436201ff5029ea to your computer and use it in GitHub Desktop.
Flutter Architecture #3

Flutter Architecture #3

Created with <3 with dartpad.dev.

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 const SplashPage();
},
'/main': (BuildContext context) {
return const DonutShopMain();
},
'/details': (BuildContext context) {
return const DonutShopDetails();
}
},
),
),
);
}
// PAGES
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@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 {
const DonutShopMain({super.key});
@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: const 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 = const DonutMainPage();
}
return PageRouteBuilder(
pageBuilder: (
_,
__,
___,
) =>
page,
transitionDuration: const Duration(seconds: 0),
);
}),
),
const DonutBottomBar(),
],
),
);
}
}
class DonutMainPage extends StatelessWidget {
const DonutMainPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
const DonutPager(),
const DonutFilterBar(),
Expanded(child: Consumer<DonutService>(builder: (
BuildContext context,
DonutService donutService,
Widget? child,
) {
return DonutList(donutsList: donutService.filteredDonuts);
})),
],
);
}
}
// WIDGETS
class DonutSideMenu extends StatelessWidget {
const DonutSideMenu({super.key});
@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 {
const DonutPager({super.key});
@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({super.key,
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({super.key, 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({super.key, this.donutInfo});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// Obtain reference to the DonutService using the Provider.of
// instance along with the provided Context, using the
// listen: false option.
var donutService = Provider.of<DonutService>(
context,
listen: false,
);
// With the DonutService in hand, call the method onDonutSelected and
// supply the reference to a DonutModel available in this widget
// called donutInfo.
donutService.onDonutSelected(donutInfo!);
},
child: 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: Hero(
// Both source hero tag and destination hero tag as
// well as the images must match for the animation
// work effectively.
tag: donutInfo!.name!,
child: Image.network(
donutInfo!.imgUrl!,
width: 150.0,
height: 150.0,
fit: BoxFit.contain,
),
),
),
],
),
);
}
}
class DonutFilterBar extends StatelessWidget {
const DonutFilterBar({super.key});
@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 {
const DonutBottomBar({super.key});
@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,
),
),
]);
}),
);
}
}
class DonutShopDetails extends StatefulWidget {
const DonutShopDetails({super.key});
@override
State<DonutShopDetails> createState() {
return DonutShopDetailsState();
}
}
class DonutShopDetailsState extends State<DonutShopDetails>
with SingleTickerProviderStateMixin {
DonutModel? selectedDonut;
AnimationController? controller;
Animation<double>? rotationAnimation;
@override
void initState() {
super.initState();
// For the AnimationController, we set its duration to 20 secs,
// and assigned the vsync property to this - this means since we've
// made the current widget a SingleTickerProvider, we pass ourselves in.
// Calling the repeat method on the controller means this animation will
// repeat forever, infinitely - until this widget gets destroyed.
controller =
AnimationController(duration: const Duration(seconds: 20), vsync: this)
..repeat();
// For the Animation (rotationAnimation) we use a Tween object,
// pass a begin value of 0 and an end of 1 - this means make the animation
// completed its whole cycle, and since the AnimationController is in repeat,
// after it concludes, it starts again. Therefore we invoke the Tween
// object's animate method, where pass the controller as the parent, as
// well as the animation's curve (in our case, we want it linear,
// hence the Curves.linear option).
rotationAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller!,
curve: Curves.linear,
),
);
}
@override
void dispose() {
controller!.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Pull a reference to the DonutService using the Provider.of
// method with the listen: false option.
var donutService = Provider.of<DonutService>(
context,
listen: false,
);
// Extract the selected donut by calling the method getSelectedDonut
// out of the DonutService reference, and assigning it to the local
// property selectedDonut.
selectedDonut = donutService.getSelectedDonut();
return Scaffold(
appBar: AppBar(
iconTheme: const IconThemeData(color: Utils.mainDark),
backgroundColor: Colors.transparent,
elevation: 0.0,
centerTitle: true,
title: SizedBox(
width: 120.0,
child: Image.network(Utils.donutLogoRedText),
),
),
body: Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Stack(
children: [
// A Positioned widget must be a direct descendant of the Stack.
// The Positioned widget positions the wrapping widget in regards
// to the parent Stack coordinates (0,0 being the very top left
// corner of the Stack and so on).
Positioned(
top: -40.0,
right: -120.0,
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.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
selectedDonut!.name!,
style: const TextStyle(
color: Utils.mainDark,
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 50.0),
IconButton(
onPressed: () {},
color: Utils.mainDark,
icon: const Icon(
Icons.favorite_outline,
),
),
],
),
const SizedBox(height: 10.0),
Container(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
decoration: BoxDecoration(
color: Utils.mainDark,
borderRadius: BorderRadius.circular(20.0),
),
child: Text(
'\$${selectedDonut!.price!.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.white,
),
),
),
const SizedBox(height: 20.0),
Text(selectedDonut!.description!),
Container(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
margin: const EdgeInsets.only(top: 20.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Utils.mainColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(50.0),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.shopping_cart,
color: Utils.mainDark,
),
SizedBox(width: 20.0),
Text(
'Add to cart',
style: TextStyle(
color: Utils.mainDark,
),
),
],
),
),
],
),
),
),
],
),
);
}
}
// 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 = [];
// The late modifier marks this property for delayed initialization,
// since by making it nullable (using the ‘?') we are implying that
// at some point this property may be null - a useful, meaningful
// value for this field, which is not the case.
late DonutModel selectedDonut;
// Create a method called getSelectedDonut() which will serve as
// a getter method or wrapper to the selectedDonut property.
DonutModel getSelectedDonut() {
return selectedDonut;
}
// Create a method called onDonutSelected, that takes as a parameter
// an object of type DonutModel. The key here is the following: we'll
// encapsulate even the navigation to the details page once we've
// ensured the selectedDonut property has been set, which guarantees
// that when we've navigated to the details page (DonutShopDetails)
// the value will be available.
void onDonutSelected(DonutModel donut) {
selectedDonut = donut;
Utils.mainAppNav.currentState!.pushNamed(
'/details',
);
}
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')
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment