├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── RunnerDebug.entitlements │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── build │ ├── Runner.build │ │ ├── Debug-iphonesimulator │ │ │ └── Runner.build │ │ │ │ ├── all-product-headers.yaml │ │ │ │ ├── dgph~ │ │ │ │ ├── dgph │ │ │ │ ├── Runner.hmap │ │ │ │ ├── Objects-normal │ │ │ │ └── arm64 │ │ │ │ │ ├── Runner.SwiftFileList │ │ │ │ │ ├── Runner-master.priors │ │ │ │ │ ├── Runner-master.swiftdeps │ │ │ │ │ └── Runner-OutputFileMap.json │ │ │ │ ├── Runner-generated-files.hmap │ │ │ │ ├── Runner-all-target-headers.hmap │ │ │ │ ├── Runner-own-target-headers.hmap │ │ │ │ ├── Runner-all-non-framework-target-headers.hmap │ │ │ │ ├── Runner-project-headers.hmap │ │ │ │ ├── DerivedSources │ │ │ │ └── Runner_vers.c │ │ │ │ ├── OutputFileList-BF2CC4323AB2AF5C34B5EA92-Pods-Runner-frameworks-Debug-output-files-ca8ff1673aaa4c5b8943c80b69bd6f4f-resolved.xcfilelist │ │ │ │ └── InputFileList-BF2CC4323AB2AF5C34B5EA92-Pods-Runner-frameworks-Debug-input-files-771a2f54aa84d9907c268cc1edc2a6a4-resolved.xcfilelist │ │ └── Debug-iphoneos │ │ │ └── Runner.build │ │ │ ├── dgph~ │ │ │ └── dgph │ ├── XCBuildData │ │ ├── build.db │ │ ├── 14dfdd0ab931d58dc68e5fe160609289-targetGraph.txt │ │ ├── e6260b308c42b72f53679ddba5415d91-targetGraph.txt │ │ ├── 14dfdd0ab931d58dc68e5fe160609289-desc.xcbuild │ │ ├── e6260b308c42b72f53679ddba5415d91-desc.xcbuild │ │ ├── BuildDescriptionCacheIndex-84d9dfa466f459d579ef5dc5119f8627 │ │ ├── 14dfdd0ab931d58dc68e5fe160609289-buildRequest.json │ │ └── e6260b308c42b72f53679ddba5415d91-buildRequest.json │ ├── Pods.build │ │ └── Release-iphonesimulator │ │ │ ├── abseil.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── nanopb.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── Firebase.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── Flutter.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── Pods-Runner.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── gRPC-C++.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── gRPC-Core.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── BoringSSL-GRPC.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── FirebaseCore.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── GoogleUtilities.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── PromisesObjC.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── cloud_firestore.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── firebase_core.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── leveldb-library.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── FirebaseFirestore.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── GoogleDataTransport.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ ├── FirebaseCoreDiagnostics.build │ │ │ ├── dgph~ │ │ │ └── dgph │ │ │ └── gRPC-C++-gRPCCertificates-Cpp.build │ │ │ ├── dgph~ │ │ │ └── dgph │ └── SharedPrecompiledHeaders │ │ └── Runner-Bridging-Header-1frmp8m6cpjz9.dia ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore └── Podfile ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── manifest.json └── index.html ├── assets ├── images │ └── logo.png ├── payment_profile_apple_pay.json ├── payment_profile_google_pay.json └── svgs │ └── garlands.svg ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_application_1 │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── lib ├── models │ ├── payment_method_model.dart │ ├── models.dart │ ├── wishlist_model.dart │ ├── cart_model.dart │ ├── category_model.dart │ ├── checkout_model.dart │ ├── user_model.dart │ ├── product_model.g.dart │ └── product_model.dart ├── repositories │ ├── product │ │ ├── base_product_repository.dart │ │ └── product_repository.dart │ ├── category │ │ ├── base_category_repository.dart │ │ └── category_repository.dart │ ├── checkout │ │ ├── base_checkout_repository.dart │ │ └── checkout_repository.dart │ ├── user │ │ ├── base_user_repository.dart │ │ └── user_repository.dart │ ├── local_storage │ │ ├── base_local_storage_repository.dart │ │ └── local_storage_repository.dart │ ├── auth │ │ ├── base_auth_repository.dart │ │ └── auth_repository.dart │ └── repositories.dart ├── blocs │ ├── blocs.dart │ ├── product │ │ ├── product_event.dart │ │ ├── product_state.dart │ │ └── product_bloc.dart │ ├── auth │ │ ├── auth_event.dart │ │ ├── auth_state.dart │ │ └── auth_bloc.dart │ ├── category │ │ ├── category_event.dart │ │ ├── category_state.dart │ │ └── category_bloc.dart │ ├── payment │ │ ├── payment_event.dart │ │ ├── payment_state.dart │ │ └── payment_bloc.dart │ ├── cart │ │ ├── cart_state.dart │ │ ├── cart_event.dart │ │ └── cart_bloc.dart │ ├── wishlist │ │ ├── wishlist_state.dart │ │ ├── wishlist_event.dart │ │ └── wishlist_bloc.dart │ └── checkout │ │ ├── checkout_event.dart │ │ ├── checkout_state.dart │ │ └── checkout_bloc.dart ├── widgets │ ├── widgets.dart │ ├── section_title.dart │ ├── custom_text_form_field.dart │ ├── product_carousel.dart │ ├── custom_appbar.dart │ ├── apple_pay.dart │ ├── google_pay.dart │ ├── hero_carousel_card.dart │ ├── order_summary.dart │ ├── custom_navbar.dart │ └── product_card.dart ├── screens │ ├── screens.dart │ ├── splash │ │ └── splash_screen.dart │ ├── wishlist │ │ └── wishlist_screen.dart │ ├── catalog │ │ └── catalog_screen.dart │ ├── payment_selection │ │ └── payment_selection_screen.dart │ ├── cart │ │ └── cart_screen.dart │ ├── home │ │ └── home_screen.dart │ ├── order_confirmation │ │ └── order_confirmation_screen.dart │ ├── product │ │ └── product_screen.dart │ └── checkout │ │ └── checkout_screen.dart ├── simple_bloc_observer.dart ├── config │ ├── theme.dart │ └── app_router.dart └── main.dart ├── .metadata ├── README.md ├── .gitignore └── pubspec.yaml /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/web/favicon.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /lib/models/payment_method_model.dart: -------------------------------------------------------------------------------- 1 | enum PaymentMethod { 2 | apple_pay, 3 | google_pay, 4 | credit_card, 5 | } 6 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/all-product-headers.yaml: -------------------------------------------------------------------------------- 1 | {"case-sensitive":"false","roots":[],"version":0} -------------------------------------------------------------------------------- /ios/build/XCBuildData/build.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/XCBuildData/build.db -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/build/XCBuildData/14dfdd0ab931d58dc68e5fe160609289-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner, no dependencies -------------------------------------------------------------------------------- /ios/build/XCBuildData/e6260b308c42b72f53679ddba5415d91-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner, no dependencies -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphoneos/Runner.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesios -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesios -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/abseil.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/nanopb.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphoneos/Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesios -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Firebase.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-C++.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-Core.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/BoringSSL-GRPC.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCore.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleUtilities.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/PromisesObjC.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/cloud_firestore.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/firebase_core.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/leveldb-library.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesios -------------------------------------------------------------------------------- /lib/repositories/product/base_product_repository.dart: -------------------------------------------------------------------------------- 1 | import '/models/models.dart'; 2 | 3 | abstract class BaseProductRepository { 4 | Stream> getAllProducts(); 5 | } 6 | -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseFirestore.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleDataTransport.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/abseil.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/nanopb.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner.hmap: -------------------------------------------------------------------------------- 1 | pamhx -------------------------------------------------------------------------------- /lib/repositories/category/base_category_repository.dart: -------------------------------------------------------------------------------- 1 | import '/models/models.dart'; 2 | 3 | abstract class BaseCategoryRepository { 4 | Stream> getAllCategories(); 5 | } 6 | -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Firebase.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCore.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCoreDiagnostics.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/PromisesObjC.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-C++.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-Core.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /lib/repositories/checkout/base_checkout_repository.dart: -------------------------------------------------------------------------------- 1 | import '/models/models.dart'; 2 | 3 | abstract class BaseCheckoutRepository { 4 | Future addCheckout(Checkout checkout); 5 | } 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/BoringSSL-GRPC.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseFirestore.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleUtilities.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/cloud_firestore.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/firebase_core.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-C++-gRPCCertificates-Cpp.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktopflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/leveldb-library.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner.SwiftFileList: -------------------------------------------------------------------------------- 1 | /Users/massimodelpezzo/Desktop/Flutter\ Apps/flutter_ecommerce_series/ios/Runner/AppDelegate.swift 2 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleDataTransport.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner-generated-files.hmap: -------------------------------------------------------------------------------- 1 | pamhx -------------------------------------------------------------------------------- /ios/build/XCBuildData/14dfdd0ab931d58dc68e5fe160609289-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/XCBuildData/14dfdd0ab931d58dc68e5fe160609289-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/e6260b308c42b72f53679ddba5415d91-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/XCBuildData/e6260b308c42b72f53679ddba5415d91-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCoreDiagnostics.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner-all-target-headers.hmap: -------------------------------------------------------------------------------- 1 | pamhx -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner-own-target-headers.hmap: -------------------------------------------------------------------------------- 1 | pamhx -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/gRPC-C++-gRPCCertificates-Cpp.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Oct 5 202123:29:11/UsersmassimodelpezzoDesktop Flutter Appsflutter_ecommerce_seriesiosPods -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner-all-non-framework-target-headers.hmap: -------------------------------------------------------------------------------- 1 | pamhx -------------------------------------------------------------------------------- /ios/build/SharedPrecompiledHeaders/Runner-Bridging-Header-1frmp8m6cpjz9.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/SharedPrecompiledHeaders/Runner-Bridging-Header-1frmp8m6cpjz9.dia -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.maxonflutter.ecommerce 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/build/XCBuildData/BuildDescriptionCacheIndex-84d9dfa466f459d579ef5dc5119f8627: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/XCBuildData/BuildDescriptionCacheIndex-84d9dfa466f459d579ef5dc5119f8627 -------------------------------------------------------------------------------- /lib/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'cart_model.dart'; 2 | export 'category_model.dart'; 3 | export 'checkout_model.dart'; 4 | export 'payment_method_model.dart'; 5 | export 'product_model.dart'; 6 | export 'user_model.dart'; 7 | export 'wishlist_model.dart'; 8 | -------------------------------------------------------------------------------- /lib/repositories/user/base_user_repository.dart: -------------------------------------------------------------------------------- 1 | import '/models/models.dart'; 2 | 3 | abstract class BaseUserRepository { 4 | Stream getUser(String userId); 5 | Future createUser(User user); 6 | Future updateUser(User user); 7 | } 8 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner-master.priors: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/flutter_ecommerce_series/HEAD/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner-master.priors -------------------------------------------------------------------------------- /lib/blocs/blocs.dart: -------------------------------------------------------------------------------- 1 | export 'auth/auth_bloc.dart'; 2 | export 'cart/cart_bloc.dart'; 3 | export 'category/category_bloc.dart'; 4 | export 'checkout/checkout_bloc.dart'; 5 | export 'product/product_bloc.dart'; 6 | export 'wishlist/wishlist_bloc.dart'; 7 | export 'payment/payment_bloc.dart'; 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Runner-project-headers.hmap: -------------------------------------------------------------------------------- 1 | pamhxnnGeneratedPluginRegistrant.h/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Runner/Runner-Bridging-Header.h -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/models/wishlist_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import '/models/product_model.dart'; 3 | 4 | class Wishlist extends Equatable { 5 | final List products; 6 | 7 | const Wishlist({this.products = const []}); 8 | 9 | @override 10 | List get props => [products]; 11 | } 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3 8 | channel: unknown 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export 'apple_pay.dart'; 2 | export 'custom_appbar.dart'; 3 | export 'custom_navbar.dart'; 4 | export 'custom_text_form_field.dart'; 5 | export 'hero_carousel_card.dart'; 6 | export 'google_pay.dart'; 7 | export 'order_summary.dart'; 8 | export 'product_card.dart'; 9 | export 'product_carousel.dart'; 10 | export 'section_title.dart'; 11 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/DerivedSources/Runner_vers.c: -------------------------------------------------------------------------------- 1 | extern const unsigned char RunnerVersionString[]; 2 | extern const double RunnerVersionNumber; 3 | 4 | const unsigned char RunnerVersionString[] __attribute__ ((used)) = "@(#)PROGRAM:Runner PROJECT:Runner-1" "\n"; 5 | const double RunnerVersionNumber __attribute__ ((used)) = (double)1.; 6 | -------------------------------------------------------------------------------- /ios/Runner/RunnerDebug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.in-app-payments 6 | 7 | merchant.com.maxonflutter.ecommerce 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/screens/screens.dart: -------------------------------------------------------------------------------- 1 | export 'home/home_screen.dart'; 2 | export 'product/product_screen.dart'; 3 | export 'catalog/catalog_screen.dart'; 4 | export 'wishlist/wishlist_screen.dart'; 5 | export 'cart/cart_screen.dart'; 6 | export 'splash/splash_screen.dart'; 7 | export 'checkout/checkout_screen.dart'; 8 | export 'order_confirmation/order_confirmation_screen.dart'; 9 | export 'payment_selection/payment_selection_screen.dart'; 10 | -------------------------------------------------------------------------------- /lib/repositories/local_storage/base_local_storage_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import '/models/models.dart'; 4 | 5 | abstract class BaseLocalStorageRepository { 6 | Future openBox(); 7 | List getWishlist(Box box); 8 | Future addProductToWishlist(Box box, Product product); 9 | Future removeProductFromWishlist(Box box, Product product); 10 | Future clearWishlist(Box box); 11 | } 12 | -------------------------------------------------------------------------------- /lib/repositories/auth/base_auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart' as auth; 2 | 3 | abstract class BaseAuthRepository { 4 | Stream get user; 5 | Future signUp({ 6 | required String email, 7 | required String password, 8 | }); 9 | Future logInWithEmailAndPassword({ 10 | required String email, 11 | required String password, 12 | }); 13 | Future signOut(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/blocs/product/product_event.dart: -------------------------------------------------------------------------------- 1 | part of 'product_bloc.dart'; 2 | 3 | abstract class ProductEvent extends Equatable { 4 | const ProductEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class LoadProducts extends ProductEvent {} 11 | 12 | class UpdateProducts extends ProductEvent { 13 | final List products; 14 | 15 | UpdateProducts(this.products); 16 | 17 | @override 18 | List get props => [products]; 19 | } 20 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner-master.swiftdeps: -------------------------------------------------------------------------------- 1 | version: "Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)" 2 | options: "1ecb50a3e59a8c48721fce77c6776c2e0f936e162f2b2edde496077473b9b079" 3 | build_start_time: [1643223434, 371310949] 4 | build_end_time: [1643223434, 428493976] 5 | inputs: 6 | "/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Runner/AppDelegate.swift": !private [1624287970, 371487140] 7 | -------------------------------------------------------------------------------- /lib/blocs/auth/auth_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | abstract class AuthEvent extends Equatable { 4 | const AuthEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class AuthUserChanged extends AuthEvent { 11 | final auth.User? authUser; 12 | final User? user; 13 | 14 | const AuthUserChanged({ 15 | required this.authUser, 16 | this.user, 17 | }); 18 | 19 | @override 20 | List get props => [authUser, user]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/blocs/category/category_event.dart: -------------------------------------------------------------------------------- 1 | part of 'category_bloc.dart'; 2 | 3 | abstract class CategoryEvent extends Equatable { 4 | const CategoryEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class LoadCategories extends CategoryEvent {} 11 | 12 | class UpdateCategories extends CategoryEvent { 13 | final List categories; 14 | 15 | UpdateCategories(this.categories); 16 | 17 | @override 18 | List get props => [categories]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/blocs/product/product_state.dart: -------------------------------------------------------------------------------- 1 | part of 'product_bloc.dart'; 2 | 3 | abstract class ProductState extends Equatable { 4 | const ProductState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class ProductLoading extends ProductState {} 11 | 12 | class ProductLoaded extends ProductState { 13 | final List products; 14 | 15 | ProductLoaded({this.products = const []}); 16 | 17 | @override 18 | List get props => [products]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/blocs/category/category_state.dart: -------------------------------------------------------------------------------- 1 | part of 'category_bloc.dart'; 2 | 3 | abstract class CategoryState extends Equatable { 4 | const CategoryState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CategoryLoading extends CategoryState {} 11 | 12 | class CategoryLoaded extends CategoryState { 13 | final List categories; 14 | 15 | CategoryLoaded({this.categories = const []}); 16 | 17 | @override 18 | List get props => [categories]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/blocs/payment/payment_event.dart: -------------------------------------------------------------------------------- 1 | part of 'payment_bloc.dart'; 2 | 3 | abstract class PaymentEvent extends Equatable { 4 | const PaymentEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class LoadPaymentMethod extends PaymentEvent {} 11 | 12 | class SelectPaymentMethod extends PaymentEvent { 13 | final PaymentMethod paymentMethod; 14 | 15 | const SelectPaymentMethod({required this.paymentMethod}); 16 | 17 | @override 18 | List get props => [paymentMethod]; 19 | } 20 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Max on Flutter - Flutter eCommerce App 2 | The eCommerce Series is a journey where we will build a production eCommerce app from scratch using Flutter. The series will take you through the development cycle of an app business from planning to design, development and testing. By the end of the series, the app will be launched and live on iOS and Android devices. 3 | 4 | If you want to see video tutorials about how to implement the ecommerce app, please go to: https://youtube.com/playlist?list=PLCAZyR6zw2px5C7L2cCG4aywx6g58MIoP 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/blocs/payment/payment_state.dart: -------------------------------------------------------------------------------- 1 | part of 'payment_bloc.dart'; 2 | 3 | abstract class PaymentState extends Equatable { 4 | const PaymentState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class PaymentLoading extends PaymentState {} 11 | 12 | class PaymentLoaded extends PaymentState { 13 | final PaymentMethod paymentMethod; 14 | 15 | const PaymentLoaded({ 16 | this.paymentMethod = PaymentMethod.google_pay, 17 | }); 18 | 19 | @override 20 | List get props => [paymentMethod]; 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/blocs/cart/cart_state.dart: -------------------------------------------------------------------------------- 1 | part of 'cart_bloc.dart'; 2 | 3 | @immutable 4 | abstract class CartState extends Equatable { 5 | const CartState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class CartLoading extends CartState {} 12 | 13 | class CartLoaded extends CartState { 14 | final Cart cart; 15 | 16 | const CartLoaded({this.cart = const Cart()}); 17 | 18 | @override 19 | List get props => [cart]; 20 | } 21 | 22 | class CartError extends CartState { 23 | @override 24 | List get props => []; 25 | } 26 | -------------------------------------------------------------------------------- /lib/repositories/repositories.dart: -------------------------------------------------------------------------------- 1 | export 'auth/base_auth_repository.dart'; 2 | export 'auth/auth_repository.dart'; 3 | export 'category/base_category_repository.dart'; 4 | export 'category/category_repository.dart'; 5 | export 'checkout/base_checkout_repository.dart'; 6 | export 'checkout/checkout_repository.dart'; 7 | export 'local_storage/base_local_storage_repository.dart'; 8 | export 'local_storage/local_storage_repository.dart'; 9 | export 'product/base_product_repository.dart'; 10 | export 'product/product_repository.dart'; 11 | export 'user/base_user_repository.dart'; 12 | export 'user/user_repository.dart'; 13 | -------------------------------------------------------------------------------- /lib/blocs/wishlist/wishlist_state.dart: -------------------------------------------------------------------------------- 1 | part of 'wishlist_bloc.dart'; 2 | 3 | @immutable 4 | abstract class WishlistState extends Equatable { 5 | const WishlistState(); 6 | } 7 | 8 | class WishlistLoading extends WishlistState { 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class WishlistLoaded extends WishlistState { 14 | final Wishlist wishlist; 15 | 16 | const WishlistLoaded({this.wishlist = const Wishlist()}); 17 | 18 | @override 19 | List get props => [wishlist]; 20 | } 21 | 22 | class WishlistError extends WishlistState { 23 | @override 24 | List get props => []; 25 | } 26 | -------------------------------------------------------------------------------- /lib/simple_bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | 3 | class SimpleBlocObserver extends BlocObserver { 4 | @override 5 | void onEvent(Bloc bloc, Object? event) { 6 | super.onEvent(bloc, event); 7 | print('${bloc.runtimeType} $event'); 8 | } 9 | 10 | @override 11 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 12 | print('${bloc.runtimeType} $error'); 13 | super.onError(bloc, error, stackTrace); 14 | } 15 | 16 | @override 17 | void onTransition(Bloc bloc, Transition transition) { 18 | super.onTransition(bloc, transition); 19 | print(transition); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/widgets/section_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SectionTitle extends StatelessWidget { 4 | final String title; 5 | 6 | const SectionTitle({ 7 | Key? key, 8 | required this.title, 9 | }) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 15 | child: Align( 16 | alignment: Alignment.topLeft, 17 | child: Text( 18 | title, 19 | style: Theme.of(context).textTheme.headline3, 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/blocs/cart/cart_event.dart: -------------------------------------------------------------------------------- 1 | part of 'cart_bloc.dart'; 2 | 3 | @immutable 4 | abstract class CartEvent extends Equatable { 5 | const CartEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class LoadCart extends CartEvent {} 12 | 13 | class AddProduct extends CartEvent { 14 | final Product product; 15 | 16 | const AddProduct(this.product); 17 | 18 | @override 19 | List get props => [product]; 20 | } 21 | 22 | class RemoveProduct extends CartEvent { 23 | final Product product; 24 | 25 | const RemoveProduct(this.product); 26 | 27 | @override 28 | List get props => [product]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/repositories/checkout/checkout_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import '/models/checkout_model.dart'; 3 | import '/repositories/checkout/base_checkout_repository.dart'; 4 | 5 | class CheckoutRepository extends BaseCheckoutRepository { 6 | final FirebaseFirestore _firebaseFirestore; 7 | 8 | CheckoutRepository({ 9 | FirebaseFirestore? firebaseFirestore, 10 | }) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance; 11 | 12 | @override 13 | Future addCheckout(Checkout checkout) { 14 | return _firebaseFirestore.collection('checkout').add(checkout.toDocument()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_application_1", 3 | "short_name": "flutter_application_1", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/blocs/wishlist/wishlist_event.dart: -------------------------------------------------------------------------------- 1 | part of 'wishlist_bloc.dart'; 2 | 3 | @immutable 4 | abstract class WishlistEvent extends Equatable { 5 | const WishlistEvent(); 6 | } 7 | 8 | class StartWishlist extends WishlistEvent { 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class AddProductToWishlist extends WishlistEvent { 14 | final Product product; 15 | 16 | const AddProductToWishlist(this.product); 17 | 18 | @override 19 | List get props => [product]; 20 | } 21 | 22 | class RemoveProductFromWishlist extends WishlistEvent { 23 | final Product product; 24 | 25 | const RemoveProductFromWishlist(this.product); 26 | 27 | @override 28 | List get props => [product]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/repositories/product/product_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import '/models/product_model.dart'; 3 | import '/repositories/product/base_product_repository.dart'; 4 | 5 | class ProductRepository extends BaseProductRepository { 6 | final FirebaseFirestore _firebaseFirestore; 7 | 8 | ProductRepository({FirebaseFirestore? firebaseFirestore}) 9 | : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance; 10 | 11 | @override 12 | Stream> getAllProducts() { 13 | return _firebaseFirestore 14 | .collection('products') 15 | .snapshots() 16 | .map((snapshot) { 17 | return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.20' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.8' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/repositories/category/category_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import '/models/category_model.dart'; 3 | import '/repositories/category/base_category_repository.dart'; 4 | 5 | class CategoryRepository extends BaseCategoryRepository { 6 | final FirebaseFirestore _firebaseFirestore; 7 | 8 | CategoryRepository({ 9 | FirebaseFirestore? firebaseFirestore, 10 | }) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance; 11 | 12 | @override 13 | Stream> getAllCategories() { 14 | return _firebaseFirestore 15 | .collection('categories') 16 | .snapshots() 17 | .map((snapshot) { 18 | return snapshot.docs.map((doc) => Category.fromSnapshot(doc)).toList(); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/blocs/payment/payment_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:ecommerce/models/payment_method_model.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | 5 | part 'payment_event.dart'; 6 | part 'payment_state.dart'; 7 | 8 | class PaymentBloc extends Bloc { 9 | PaymentBloc() : super(PaymentLoading()) { 10 | on(_onLoadPaymentMethod); 11 | on(_onSelectPaymentMethod); 12 | } 13 | 14 | void _onLoadPaymentMethod( 15 | LoadPaymentMethod event, 16 | Emitter emit, 17 | ) { 18 | emit( 19 | PaymentLoaded(), 20 | ); 21 | } 22 | 23 | void _onSelectPaymentMethod( 24 | SelectPaymentMethod event, 25 | Emitter emit, 26 | ) { 27 | emit( 28 | PaymentLoaded(paymentMethod: event.paymentMethod), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /assets/payment_profile_apple_pay.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "apple_pay", 3 | "data": { 4 | "merchantIdentifier": "merchant.com.maxonflutter.ecommerce", 5 | "displayName": "Max on Flutter", 6 | "merchantCapabilities": [ 7 | "3DS", 8 | "debit", 9 | "credit" 10 | ], 11 | "supportedNetworks": [ 12 | "amex", 13 | "visa", 14 | "discover", 15 | "masterCard" 16 | ], 17 | "countryCode": "US", 18 | "currencyCode": "USD", 19 | "requiredShippingContactFields": [], 20 | "shippingMethods": [ 21 | { 22 | "amount": "29.99", 23 | "detail": "1-3 Business Days", 24 | "identifier": "flat_rate_shipping_id_1", 25 | "label": "FedEx Priority Mail" 26 | } 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/blocs/auth/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | enum AuthStatus { unknown, authenticated, unauthenticated } 4 | 5 | class AuthState extends Equatable { 6 | final AuthStatus status; 7 | final auth.User? authUser; 8 | final User? user; 9 | 10 | const AuthState._({ 11 | this.status = AuthStatus.unknown, 12 | this.authUser, 13 | this.user, 14 | }); 15 | 16 | const AuthState.unknown() : this._(); 17 | 18 | const AuthState.authenticated({ 19 | required auth.User authUser, 20 | required User user, 21 | }) : this._( 22 | status: AuthStatus.authenticated, 23 | authUser: authUser, 24 | user: user, 25 | ); 26 | 27 | const AuthState.unauthenticated() 28 | : this._( 29 | status: AuthStatus.unauthenticated, 30 | ); 31 | 32 | @override 33 | List get props => [status, authUser, user]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/repositories/local_storage/local_storage_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import '/repositories/repositories.dart'; 4 | import '/models/models.dart'; 5 | 6 | class LocalStorageRepository extends BaseLocalStorageRepository { 7 | String boxName = 'wishlist_products'; 8 | Type boxType = Product; 9 | 10 | @override 11 | Future openBox() async { 12 | Box box = await Hive.openBox(boxName); 13 | return box; 14 | } 15 | 16 | @override 17 | List getWishlist(Box box) { 18 | return box.values.toList() as List; 19 | } 20 | 21 | @override 22 | Future addProductToWishlist(Box box, Product product) async { 23 | await box.put(product.id, product); 24 | } 25 | 26 | @override 27 | Future removeProductFromWishlist(Box box, Product product) async { 28 | await box.delete(product.id); 29 | } 30 | 31 | @override 32 | Future clearWishlist(Box box) async { 33 | await box.clear(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/blocs/checkout/checkout_event.dart: -------------------------------------------------------------------------------- 1 | part of 'checkout_bloc.dart'; 2 | 3 | abstract class CheckoutEvent extends Equatable { 4 | const CheckoutEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class UpdateCheckout extends CheckoutEvent { 11 | final String? fullName; 12 | final String? email; 13 | final String? address; 14 | final String? city; 15 | final String? country; 16 | final String? zipCode; 17 | final Cart? cart; 18 | final PaymentMethod? paymentMethod; 19 | 20 | UpdateCheckout({ 21 | this.fullName, 22 | this.email, 23 | this.address, 24 | this.city, 25 | this.country, 26 | this.zipCode, 27 | this.cart, 28 | this.paymentMethod, 29 | }); 30 | 31 | @override 32 | List get props => [ 33 | fullName, 34 | email, 35 | address, 36 | city, 37 | country, 38 | zipCode, 39 | cart, 40 | paymentMethod, 41 | ]; 42 | } 43 | 44 | class ConfirmCheckout extends CheckoutEvent { 45 | final Checkout checkout; 46 | 47 | const ConfirmCheckout({required this.checkout}); 48 | 49 | @override 50 | List get props => [checkout]; 51 | } 52 | -------------------------------------------------------------------------------- /lib/widgets/custom_text_form_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomTextFormField extends StatelessWidget { 4 | const CustomTextFormField({ 5 | Key? key, 6 | required this.title, 7 | this.onChanged, 8 | }) : super(key: key); 9 | 10 | final String title; 11 | final Function(String)? onChanged; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Padding( 16 | padding: const EdgeInsets.all(5.0), 17 | child: Row( 18 | children: [ 19 | SizedBox( 20 | width: 75, 21 | child: Text( 22 | title, 23 | style: Theme.of(context).textTheme.bodyText1, 24 | ), 25 | ), 26 | Expanded( 27 | child: TextFormField( 28 | onChanged: onChanged, 29 | decoration: InputDecoration( 30 | isDense: true, 31 | contentPadding: const EdgeInsets.only(left: 10), 32 | focusedBorder: UnderlineInputBorder( 33 | borderSide: BorderSide(color: Colors.black), 34 | ), 35 | ), 36 | ), 37 | ), 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/blocs/product/product_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import '/models/models.dart'; 6 | import '/repositories/product/product_repository.dart'; 7 | 8 | part 'product_event.dart'; 9 | part 'product_state.dart'; 10 | 11 | class ProductBloc extends Bloc { 12 | final ProductRepository _productRepository; 13 | StreamSubscription? _productSubscription; 14 | 15 | ProductBloc({required ProductRepository productRepository}) 16 | : _productRepository = productRepository, 17 | super(ProductLoading()) { 18 | on(_onLoadProducts); 19 | on(_onUpdateProducts); 20 | } 21 | 22 | void _onLoadProducts( 23 | LoadProducts event, 24 | Emitter emit, 25 | ) { 26 | _productSubscription?.cancel(); 27 | _productSubscription = _productRepository.getAllProducts().listen( 28 | (products) => add( 29 | UpdateProducts(products), 30 | ), 31 | ); 32 | } 33 | 34 | void _onUpdateProducts( 35 | UpdateProducts event, 36 | Emitter emit, 37 | ) { 38 | emit(ProductLoaded(products: event.products)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/repositories/user/user_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:ecommerce/models/user_model.dart'; 3 | 4 | import 'base_user_repository.dart'; 5 | 6 | class UserRepository extends BaseUserRepository { 7 | final FirebaseFirestore _firebaseFirestore; 8 | 9 | UserRepository({ 10 | FirebaseFirestore? firebaseFirestore, 11 | }) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance; 12 | 13 | @override 14 | Future createUser(User user) async { 15 | await _firebaseFirestore 16 | .collection('users') 17 | .doc(user.id) 18 | .set(user.toDocument()); 19 | } 20 | 21 | @override 22 | Stream getUser(String userId) { 23 | print('Getting user data from Cloud Firestore'); 24 | return _firebaseFirestore 25 | .collection('users') 26 | .doc(userId) 27 | .snapshots() 28 | .map((snap) => User.fromSnapshot(snap)); 29 | } 30 | 31 | @override 32 | Future updateUser(User user) async { 33 | return _firebaseFirestore 34 | .collection('users') 35 | .doc(user.id) 36 | .update(user.toDocument()) 37 | .then( 38 | (value) => print('User document updated.'), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/widgets/product_carousel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/models/models.dart'; 3 | 4 | import 'product_card.dart'; 5 | 6 | class ProductCarousel extends StatelessWidget { 7 | final List products; 8 | final bool? isRecommendedCarousel; 9 | final bool? isMostPopularCarousel; 10 | 11 | const ProductCarousel({ 12 | Key? key, 13 | required this.products, 14 | this.isRecommendedCarousel, 15 | this.isMostPopularCarousel, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Align( 21 | alignment: Alignment.topLeft, 22 | child: SizedBox( 23 | height: 165, 24 | child: ListView.builder( 25 | shrinkWrap: true, 26 | padding: const EdgeInsets.symmetric( 27 | horizontal: 20.0, 28 | vertical: 10.0, 29 | ), 30 | scrollDirection: Axis.horizontal, 31 | itemCount: products.length, 32 | itemBuilder: (context, index) { 33 | return Padding( 34 | padding: const EdgeInsets.only(right: 5.0), 35 | child: ProductCard.catalog(product: products[index]), 36 | ); 37 | }, 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/repositories/auth/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart' as auth; 2 | import '/repositories/repositories.dart'; 3 | 4 | class AuthRepository extends BaseAuthRepository { 5 | final auth.FirebaseAuth _firebaseAuth; 6 | 7 | AuthRepository({auth.FirebaseAuth? firebaseAuth}) 8 | : _firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance; 9 | 10 | @override 11 | Future signUp({ 12 | required String email, 13 | required String password, 14 | }) async { 15 | try { 16 | final credential = await _firebaseAuth.createUserWithEmailAndPassword( 17 | email: email, 18 | password: password, 19 | ); 20 | 21 | final user = credential.user; 22 | return user; 23 | } catch (_) {} 24 | } 25 | 26 | @override 27 | Future logInWithEmailAndPassword({ 28 | required String email, 29 | required String password, 30 | }) async { 31 | try { 32 | await _firebaseAuth.signInWithEmailAndPassword( 33 | email: email, 34 | password: password, 35 | ); 36 | } catch (_) {} 37 | } 38 | 39 | @override 40 | Stream get user => _firebaseAuth.userChanges(); 41 | 42 | @override 43 | Future signOut() async { 44 | await _firebaseAuth.signOut(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/blocs/category/category_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import '/models/models.dart'; 6 | import '/repositories/category/category_repository.dart'; 7 | 8 | part 'category_event.dart'; 9 | part 'category_state.dart'; 10 | 11 | class CategoryBloc extends Bloc { 12 | final CategoryRepository _categoryRepository; 13 | StreamSubscription? _categorySubscription; 14 | 15 | CategoryBloc({required CategoryRepository categoryRepository}) 16 | : _categoryRepository = categoryRepository, 17 | super(CategoryLoading()) { 18 | on(_onLoadCategories); 19 | on(_onUpdateCategories); 20 | } 21 | 22 | void _onLoadCategories( 23 | LoadCategories event, 24 | Emitter emit, 25 | ) { 26 | _categorySubscription?.cancel(); 27 | _categorySubscription = _categoryRepository.getAllCategories().listen( 28 | (products) => add( 29 | UpdateCategories(products), 30 | ), 31 | ); 32 | } 33 | 34 | void _onUpdateCategories( 35 | UpdateCategories event, 36 | Emitter emit, 37 | ) { 38 | emit( 39 | CategoryLoaded(categories: event.categories), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /assets/payment_profile_google_pay.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0, 7 | "allowedPaymentMethods": [ 8 | { 9 | "type": "CARD", 10 | "tokenizationSpecification": { 11 | "type": "PAYMENT_GATEWAY", 12 | "parameters": { 13 | "gateway": "example", 14 | "gatewayMerchantId": "gatewayMerchantId" 15 | } 16 | }, 17 | "parameters": { 18 | "allowedCardNetworks": [ 19 | "VISA", 20 | "MASTERCARD" 21 | ], 22 | "allowedAuthMethods": [ 23 | "PAN_ONLY", 24 | "CRYPTOGRAM_3DS" 25 | ], 26 | "billingAddressRequired": true, 27 | "billingAddressParameters": { 28 | "format": "FULL", 29 | "phoneNumberRequired": true 30 | } 31 | } 32 | } 33 | ], 34 | "transactionInfo": { 35 | "countryCode": "US", 36 | "currencyCode": "USD" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /lib/widgets/custom_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomAppBar extends StatelessWidget with PreferredSizeWidget { 4 | final String title; 5 | final bool automaticallyImplyLeading; 6 | 7 | const CustomAppBar({ 8 | Key? key, 9 | required this.title, 10 | this.automaticallyImplyLeading = true, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return AppBar( 16 | backgroundColor: Colors.transparent, 17 | elevation: 0, 18 | automaticallyImplyLeading: automaticallyImplyLeading, 19 | title: Container( 20 | padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), 21 | color: Colors.black, 22 | child: Text( 23 | title, 24 | style: Theme.of(context) 25 | .textTheme 26 | .headline2! 27 | .copyWith(color: Colors.white), 28 | ), 29 | ), 30 | iconTheme: IconThemeData(color: Colors.black), 31 | actions: [ 32 | IconButton( 33 | icon: Icon(Icons.favorite), 34 | onPressed: () { 35 | Navigator.pushNamed( 36 | context, 37 | '/wishlist', 38 | ); 39 | }, 40 | ), 41 | ], 42 | ); 43 | } 44 | 45 | Size get preferredSize => Size.fromHeight(50.0); 46 | } 47 | -------------------------------------------------------------------------------- /lib/config/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ThemeData theme() { 4 | return ThemeData( 5 | scaffoldBackgroundColor: Colors.white, 6 | fontFamily: "Avenir", 7 | textTheme: textTheme(), 8 | ); 9 | } 10 | 11 | TextTheme textTheme() { 12 | return TextTheme( 13 | headline1: TextStyle( 14 | color: Colors.black, 15 | fontWeight: FontWeight.bold, 16 | fontSize: 36, 17 | ), 18 | headline2: TextStyle( 19 | color: Colors.black, 20 | fontWeight: FontWeight.bold, 21 | fontSize: 22, 22 | ), 23 | headline3: TextStyle( 24 | color: Colors.black, 25 | fontWeight: FontWeight.bold, 26 | fontSize: 18, 27 | ), 28 | headline4: TextStyle( 29 | color: Colors.black, 30 | fontWeight: FontWeight.bold, 31 | fontSize: 16, 32 | ), 33 | headline5: TextStyle( 34 | color: Colors.black, 35 | fontWeight: FontWeight.bold, 36 | fontSize: 14, 37 | ), 38 | headline6: TextStyle( 39 | color: Colors.black, 40 | fontWeight: FontWeight.normal, 41 | fontSize: 14, 42 | ), 43 | bodyText1: TextStyle( 44 | color: Colors.black, 45 | fontWeight: FontWeight.normal, 46 | height: 1.75, 47 | fontSize: 12, 48 | ), 49 | bodyText2: TextStyle( 50 | color: Colors.black, 51 | fontWeight: FontWeight.normal, 52 | fontSize: 10, 53 | ), 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner-OutputFileMap.json: -------------------------------------------------------------------------------- 1 | {"":{"swift-dependencies":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/Runner-master.swiftdeps"},"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Runner/AppDelegate.swift":{"dependencies":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate.d","diagnostics":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate.dia","llvm-bc":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate.bc","object":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate.o","swift-dependencies":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate.swiftdeps","swiftmodule":"/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Runner.build/Debug-iphonesimulator/Runner.build/Objects-normal/arm64/AppDelegate~partial.swiftmodule"}} -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/models/cart_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import '/models/product_model.dart'; 3 | 4 | class Cart extends Equatable { 5 | final List products; 6 | 7 | const Cart({this.products = const []}); 8 | 9 | @override 10 | List get props => [products]; 11 | 12 | Map productQuantity(products) { 13 | var quantity = Map(); 14 | 15 | products.forEach((product) { 16 | if (!quantity.containsKey(product)) { 17 | quantity[product] = 1; 18 | } else { 19 | quantity[product] += 1; 20 | } 21 | }); 22 | 23 | return quantity; 24 | } 25 | 26 | double get subtotal => 27 | products.fold(0, (total, current) => total + current.price); 28 | 29 | double deliveryFee(subtotal) { 30 | if (subtotal >= 30.0) { 31 | return 0.0; 32 | } else 33 | return 10.0; 34 | } 35 | 36 | String freeDelivery(subtotal) { 37 | if (subtotal >= 30.0) { 38 | return 'You have Free Delivery'; 39 | } else { 40 | double missing = 30.0 - subtotal; 41 | return 'Add \$${missing.toStringAsFixed(2)} for FREE Delivery'; 42 | } 43 | } 44 | 45 | double total(subtotal, deliveryFee) { 46 | return subtotal + deliveryFee(subtotal); 47 | } 48 | 49 | String get deliveryFeeString => deliveryFee(subtotal).toStringAsFixed(2); 50 | 51 | String get subtotalString => subtotal.toStringAsFixed(2); 52 | 53 | String get totalString => total(subtotal, deliveryFee).toStringAsFixed(2); 54 | 55 | String get freeDeliveryString => freeDelivery(subtotal); 56 | } 57 | -------------------------------------------------------------------------------- /lib/models/category_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class Category extends Equatable { 5 | final String name; 6 | final String imageUrl; 7 | 8 | const Category({ 9 | required this.name, 10 | required this.imageUrl, 11 | }); 12 | 13 | @override 14 | List get props => [ 15 | name, 16 | imageUrl, 17 | ]; 18 | 19 | static Category fromSnapshot(DocumentSnapshot snap) { 20 | Category category = Category( 21 | name: snap['name'], 22 | imageUrl: snap['imageUrl'], 23 | ); 24 | return category; 25 | } 26 | 27 | static List categories = [ 28 | Category( 29 | name: 'Soft Drinks', 30 | imageUrl: 31 | 'https://images.unsplash.com/photo-1534057308991-b9b3a578f1b1?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80', //https://unsplash.com/photos/5lZhD2qQ2SE 32 | ), 33 | Category( 34 | name: 'Smoothies', 35 | imageUrl: 36 | 'https://images.unsplash.com/photo-1502741224143-90386d7f8c82?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/m741tj4Cz7M 37 | ), 38 | Category( 39 | name: 'Water', 40 | imageUrl: 41 | 'https://images.unsplash.com/photo-1559839914-17aae19cec71?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80', //https://unsplash.com/photos/7Zlds3gm7NU 42 | ), 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /lib/config/app_router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/models/models.dart'; 3 | import '/screens/screens.dart'; 4 | 5 | class AppRouter { 6 | static Route onGenerateRoute(RouteSettings settings) { 7 | print('Route: ${settings.name}'); 8 | switch (settings.name) { 9 | case '/': 10 | return HomeScreen.route(); 11 | case HomeScreen.routeName: 12 | return HomeScreen.route(); 13 | case SplashScreen.routeName: 14 | return SplashScreen.route(); 15 | case CartScreen.routeName: 16 | return CartScreen.route(); 17 | case ProductScreen.routeName: 18 | return ProductScreen.route(product: settings.arguments as Product); 19 | case CatalogScreen.routeName: 20 | return CatalogScreen.route(category: settings.arguments as Category); 21 | case WishlistScreen.routeName: 22 | return WishlistScreen.route(); 23 | case CheckoutScreen.routeName: 24 | return CheckoutScreen.route(); 25 | case OrderConfirmation.routeName: 26 | return OrderConfirmation.route(); 27 | case PaymentSelection.routeName: 28 | return PaymentSelection.route(); 29 | default: 30 | return _errorRoute(); 31 | } 32 | } 33 | 34 | static Route _errorRoute() { 35 | return MaterialPageRoute( 36 | settings: const RouteSettings(name: '/error'), 37 | builder: (_) => Scaffold( 38 | appBar: AppBar( 39 | title: const Text('Error'), 40 | ), 41 | body: const Center( 42 | child: Text('Something went wrong!'), 43 | ), 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widgets/apple_pay.dart: -------------------------------------------------------------------------------- 1 | import 'package:ecommerce/models/models.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pay/pay.dart'; 4 | 5 | class ApplePay extends StatelessWidget { 6 | const ApplePay({ 7 | Key? key, 8 | required this.total, 9 | required this.products, 10 | }) : super(key: key); 11 | 12 | final String total; 13 | final List products; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | var _paymentItems = products 18 | .map( 19 | (product) => PaymentItem( 20 | label: product.name, 21 | amount: product.price.toString(), 22 | type: PaymentItemType.item, 23 | status: PaymentItemStatus.final_price), 24 | ) 25 | .toList(); 26 | 27 | _paymentItems.add( 28 | PaymentItem( 29 | label: "Total", 30 | amount: total, 31 | type: PaymentItemType.total, 32 | status: PaymentItemStatus.final_price, 33 | ), 34 | ); 35 | 36 | void onApplePayResult(paymentResult) { 37 | debugPrint(paymentResult.toString()); 38 | } 39 | 40 | return SizedBox( 41 | width: MediaQuery.of(context).size.width - 50, 42 | child: ApplePayButton( 43 | paymentConfigurationAsset: 'payment_profile_apple_pay.json', 44 | onPaymentResult: onApplePayResult, 45 | paymentItems: _paymentItems, 46 | style: ApplePayButtonStyle.white, 47 | type: ApplePayButtonType.inStore, 48 | margin: const EdgeInsets.only(top: 10), 49 | loadingIndicator: const CircularProgressIndicator(), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/widgets/google_pay.dart: -------------------------------------------------------------------------------- 1 | import 'package:ecommerce/models/models.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pay/pay.dart'; 4 | 5 | class GooglePay extends StatelessWidget { 6 | const GooglePay({ 7 | Key? key, 8 | required this.total, 9 | required this.products, 10 | }) : super(key: key); 11 | 12 | final String total; 13 | final List products; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | var _paymentItems = products 18 | .map( 19 | (product) => PaymentItem( 20 | label: product.name, 21 | amount: product.price.toString(), 22 | type: PaymentItemType.item, 23 | status: PaymentItemStatus.final_price), 24 | ) 25 | .toList(); 26 | 27 | _paymentItems.add( 28 | PaymentItem( 29 | label: "Total", 30 | amount: total, 31 | type: PaymentItemType.total, 32 | status: PaymentItemStatus.final_price, 33 | ), 34 | ); 35 | 36 | void onGooglePayResult(paymentResult) { 37 | debugPrint(paymentResult.toString()); 38 | } 39 | 40 | return SizedBox( 41 | width: MediaQuery.of(context).size.width - 50, 42 | child: GooglePayButton( 43 | paymentConfigurationAsset: 'payment_profile_google_pay.json', 44 | onPaymentResult: onGooglePayResult, 45 | paymentItems: _paymentItems, 46 | style: GooglePayButtonStyle.white, 47 | type: GooglePayButtonType.pay, 48 | margin: const EdgeInsets.only(top: 10), 49 | loadingIndicator: const CircularProgressIndicator(), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/models/checkout_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import '/models/models.dart'; 3 | 4 | class Checkout extends Equatable { 5 | final String? fullName; 6 | final String? email; 7 | final String? address; 8 | final String? city; 9 | final String? country; 10 | final String? zipCode; 11 | final List? products; 12 | final String? subtotal; 13 | final String? deliveryFee; 14 | final String? total; 15 | 16 | const Checkout({ 17 | required this.fullName, 18 | required this.email, 19 | required this.address, 20 | required this.city, 21 | required this.country, 22 | required this.zipCode, 23 | required this.products, 24 | required this.subtotal, 25 | required this.deliveryFee, 26 | required this.total, 27 | }); 28 | 29 | @override 30 | List get props => [ 31 | fullName, 32 | email, 33 | address, 34 | city, 35 | country, 36 | zipCode, 37 | products, 38 | subtotal, 39 | deliveryFee, 40 | total, 41 | ]; 42 | 43 | Map toDocument() { 44 | Map customerAddress = Map(); 45 | customerAddress['address'] = address; 46 | customerAddress['city'] = city; 47 | customerAddress['country'] = country; 48 | customerAddress['zipCode'] = zipCode; 49 | 50 | return { 51 | 'customerAddress': customerAddress, 52 | 'customerName': fullName!, 53 | 'customerEmail': email!, 54 | 'products': products!.map((product) => product.name).toList(), 55 | 'subtotal': subtotal!, 56 | 'deliveryFee': deliveryFee!, 57 | 'total': total! 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/blocs/checkout/checkout_state.dart: -------------------------------------------------------------------------------- 1 | part of 'checkout_bloc.dart'; 2 | 3 | abstract class CheckoutState extends Equatable { 4 | const CheckoutState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CheckoutLoading extends CheckoutState {} 11 | 12 | class CheckoutLoaded extends CheckoutState { 13 | final String? fullName; 14 | final String? email; 15 | final String? address; 16 | final String? city; 17 | final String? country; 18 | final String? zipCode; 19 | final List? products; 20 | final String? subtotal; 21 | final String? deliveryFee; 22 | final String? total; 23 | final Checkout checkout; 24 | final PaymentMethod paymentMethod; 25 | 26 | CheckoutLoaded({ 27 | this.fullName, 28 | this.email, 29 | this.address, 30 | this.city, 31 | this.country, 32 | this.zipCode, 33 | this.products, 34 | this.subtotal, 35 | this.deliveryFee, 36 | this.total, 37 | this.paymentMethod = PaymentMethod.google_pay, 38 | }) : checkout = Checkout( 39 | fullName: fullName, 40 | email: email, 41 | address: address, 42 | city: city, 43 | country: country, 44 | zipCode: zipCode, 45 | products: products, 46 | subtotal: subtotal, 47 | deliveryFee: deliveryFee, 48 | total: total, 49 | ); 50 | 51 | @override 52 | List get props => [ 53 | fullName, 54 | email, 55 | address, 56 | city, 57 | country, 58 | zipCode, 59 | products, 60 | subtotal, 61 | deliveryFee, 62 | total, 63 | checkout, 64 | paymentMethod, 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | flutter_application_1 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ecommerce 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/blocs/auth/auth_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart' as auth; 6 | import '../../models/models.dart'; 7 | import '/repositories/repositories.dart'; 8 | 9 | part 'auth_event.dart'; 10 | part 'auth_state.dart'; 11 | 12 | class AuthBloc extends Bloc { 13 | final AuthRepository _authRepository; 14 | final UserRepository _userRepository; 15 | StreamSubscription? _authUserSubscription; 16 | StreamSubscription? _userSubscription; 17 | 18 | AuthBloc({ 19 | required AuthRepository authRepository, 20 | required UserRepository userRepository, 21 | }) : _authRepository = authRepository, 22 | _userRepository = userRepository, 23 | super(AuthState.unknown()) { 24 | on(_onAuthUserChanged); 25 | 26 | _authUserSubscription = _authRepository.user.listen((authUser) { 27 | print('Auth user: $authUser'); 28 | if (authUser != null) { 29 | _userRepository.getUser(authUser.uid).listen((user) { 30 | add(AuthUserChanged(authUser: authUser, user: user)); 31 | }); 32 | } else { 33 | add(AuthUserChanged(authUser: authUser)); 34 | } 35 | }); 36 | } 37 | 38 | void _onAuthUserChanged( 39 | AuthUserChanged event, 40 | Emitter emit, 41 | ) { 42 | event.authUser != null 43 | ? emit(AuthState.authenticated( 44 | authUser: event.authUser!, user: event.user!)) 45 | : emit(AuthState.unauthenticated()); 46 | } 47 | 48 | @override 49 | Future close() { 50 | _authUserSubscription?.cancel(); 51 | _userSubscription?.cancel(); 52 | return super.close(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class User extends Equatable { 5 | final String? id; 6 | final String fullName; 7 | final String email; 8 | final String address; 9 | final String city; 10 | final String country; 11 | final String zipCode; 12 | 13 | const User({ 14 | this.id, 15 | this.fullName = '', 16 | this.email = '', 17 | this.address = '', 18 | this.city = '', 19 | this.country = '', 20 | this.zipCode = '', 21 | }); 22 | 23 | User copyWith({ 24 | String? id, 25 | String? fullName, 26 | String? email, 27 | String? address, 28 | String? city, 29 | String? country, 30 | String? zipCode, 31 | }) { 32 | return User( 33 | id: id ?? this.id, 34 | fullName: fullName ?? this.fullName, 35 | email: email ?? this.email, 36 | address: address ?? this.address, 37 | city: city ?? this.city, 38 | country: country ?? this.country, 39 | zipCode: zipCode ?? this.zipCode, 40 | ); 41 | } 42 | 43 | factory User.fromSnapshot(DocumentSnapshot snap) { 44 | return User( 45 | id: snap.id, 46 | fullName: snap['fullName'], 47 | email: snap['email'], 48 | address: snap['address'], 49 | city: snap['city'], 50 | country: snap['country'], 51 | zipCode: snap['zipCode'], 52 | ); 53 | } 54 | 55 | Map toDocument() { 56 | return { 57 | 'fullName': fullName, 58 | 'email': email, 59 | 'address': address, 60 | 'city': city, 61 | 'country': country, 62 | 'zipCode': zipCode 63 | }; 64 | } 65 | 66 | @override 67 | List get props => 68 | [id, fullName, email, address, city, country, zipCode]; 69 | } 70 | -------------------------------------------------------------------------------- /lib/blocs/cart/cart_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter/material.dart'; 6 | import '/models/models.dart'; 7 | 8 | part 'cart_event.dart'; 9 | part 'cart_state.dart'; 10 | 11 | class CartBloc extends Bloc { 12 | CartBloc() : super(CartLoading()) { 13 | on(_onLoadCart); 14 | on(_onAddProduct); 15 | on(_onRemoveProduct); 16 | } 17 | 18 | void _onLoadCart( 19 | LoadCart event, 20 | Emitter emit, 21 | ) async { 22 | emit(CartLoading()); 23 | try { 24 | await Future.delayed(const Duration(seconds: 1)); 25 | emit(CartLoaded()); 26 | } catch (_) { 27 | emit(CartError()); 28 | } 29 | } 30 | 31 | void _onAddProduct( 32 | AddProduct event, 33 | Emitter emit, 34 | ) { 35 | if (this.state is CartLoaded) { 36 | try { 37 | emit( 38 | CartLoaded( 39 | cart: Cart( 40 | products: List.from((this.state as CartLoaded).cart.products) 41 | ..add(event.product), 42 | ), 43 | ), 44 | ); 45 | } on Exception { 46 | emit(CartError()); 47 | } 48 | } 49 | } 50 | 51 | void _onRemoveProduct( 52 | RemoveProduct event, 53 | Emitter emit, 54 | ) { 55 | if (this.state is CartLoaded) { 56 | try { 57 | emit( 58 | CartLoaded( 59 | cart: Cart( 60 | products: List.from((this.state as CartLoaded).cart.products) 61 | ..remove(event.product), 62 | ), 63 | ), 64 | ); 65 | } on Exception { 66 | emit(CartError()); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/screens/splash/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../blocs/blocs.dart'; 6 | 7 | class SplashScreen extends StatelessWidget { 8 | static const String routeName = '/splash'; 9 | 10 | static Route route() { 11 | return MaterialPageRoute( 12 | settings: RouteSettings(name: routeName), 13 | builder: (context) => SplashScreen(), 14 | ); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | Timer( 20 | Duration(seconds: 1), 21 | () => Navigator.pushNamed(context, '/'), 22 | ); 23 | 24 | return BlocListener( 25 | listenWhen: (previous, current) => previous.authUser != current.authUser, 26 | listener: (context, state) { 27 | print('Splash screen Auth Listener'); 28 | }, 29 | child: Scaffold( 30 | body: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Center( 34 | child: Image( 35 | image: AssetImage('assets/images/logo.png'), 36 | width: 125, 37 | height: 125, 38 | ), 39 | ), 40 | SizedBox(height: 30), 41 | Container( 42 | color: Colors.black, 43 | padding: const EdgeInsets.symmetric( 44 | vertical: 10, 45 | horizontal: 20, 46 | ), 47 | child: Text( 48 | 'Zero To Unicorn', 49 | style: Theme.of(context).textTheme.headline2!.copyWith( 50 | color: Colors.white, 51 | ), 52 | ), 53 | ) 54 | ], 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/models/product_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class ProductAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | Product read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return Product( 20 | id: fields[0] as String, 21 | name: fields[1] as String, 22 | category: fields[2] as String, 23 | imageUrl: fields[3] as String, 24 | price: fields[4] as double, 25 | isRecommended: fields[5] as bool, 26 | isPopular: fields[6] as bool, 27 | description: fields[7] as String?, 28 | ); 29 | } 30 | 31 | @override 32 | void write(BinaryWriter writer, Product obj) { 33 | writer 34 | ..writeByte(8) 35 | ..writeByte(0) 36 | ..write(obj.id) 37 | ..writeByte(1) 38 | ..write(obj.name) 39 | ..writeByte(2) 40 | ..write(obj.category) 41 | ..writeByte(3) 42 | ..write(obj.imageUrl) 43 | ..writeByte(4) 44 | ..write(obj.price) 45 | ..writeByte(5) 46 | ..write(obj.isRecommended) 47 | ..writeByte(6) 48 | ..write(obj.isPopular) 49 | ..writeByte(7) 50 | ..write(obj.description); 51 | } 52 | 53 | @override 54 | int get hashCode => typeId.hashCode; 55 | 56 | @override 57 | bool operator ==(Object other) => 58 | identical(this, other) || 59 | other is ProductAdapter && 60 | runtimeType == other.runtimeType && 61 | typeId == other.typeId; 62 | } 63 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/OutputFileList-BF2CC4323AB2AF5C34B5EA92-Pods-Runner-frameworks-Debug-output-files-ca8ff1673aaa4c5b8943c80b69bd6f4f-resolved.xcfilelist: -------------------------------------------------------------------------------- 1 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/openssl_grpc.framework 2 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/FirebaseCore.framework 3 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/FirebaseCoreDiagnostics.framework 4 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/FirebaseFirestore.framework 5 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/GoogleDataTransport.framework 6 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/GoogleUtilities.framework 7 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/FBLPromises.framework 8 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/absl.framework 9 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/grpcpp.framework 10 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/grpc.framework 11 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/leveldb.framework 12 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/nanopb.framework 13 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/Runner.app/Frameworks/pay_ios.framework 14 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Debug-iphonesimulator/Runner.build/InputFileList-BF2CC4323AB2AF5C34B5EA92-Pods-Runner-frameworks-Debug-input-files-771a2f54aa84d9907c268cc1edc2a6a4-resolved.xcfilelist: -------------------------------------------------------------------------------- 1 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh 2 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/BoringSSL-GRPC/openssl_grpc.framework 3 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/FirebaseCore/FirebaseCore.framework 4 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework 5 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/FirebaseFirestore/FirebaseFirestore.framework 6 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/GoogleDataTransport/GoogleDataTransport.framework 7 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/GoogleUtilities/GoogleUtilities.framework 8 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/PromisesObjC/FBLPromises.framework 9 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/abseil/absl.framework 10 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/gRPC-C++/grpcpp.framework 11 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/gRPC-Core/grpc.framework 12 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/leveldb-library/leveldb.framework 13 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/nanopb/nanopb.framework 14 | /Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/build/Debug-iphonesimulator/pay_ios/pay_ios.framework 15 | -------------------------------------------------------------------------------- /lib/screens/wishlist/wishlist_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import '/blocs/wishlist/wishlist_bloc.dart'; 4 | import '/widgets/widgets.dart'; 5 | 6 | class WishlistScreen extends StatelessWidget { 7 | static const String routeName = '/wishlist'; 8 | 9 | static Route route() { 10 | return MaterialPageRoute( 11 | settings: RouteSettings(name: routeName), 12 | builder: (context) => WishlistScreen(), 13 | ); 14 | } 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: CustomAppBar(title: 'Wishlist'), 20 | bottomNavigationBar: CustomNavBar(screen: routeName), 21 | body: BlocBuilder( 22 | builder: (context, state) { 23 | if (state is WishlistLoading) { 24 | return Center( 25 | child: CircularProgressIndicator( 26 | color: Colors.black, 27 | ), 28 | ); 29 | } 30 | if (state is WishlistLoaded) { 31 | return Padding( 32 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 33 | child: GridView.builder( 34 | padding: const EdgeInsets.symmetric( 35 | horizontal: 8.0, vertical: 16.0), 36 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 37 | crossAxisCount: 1, 38 | childAspectRatio: 2.25, 39 | ), 40 | itemCount: state.wishlist.products.length, 41 | itemBuilder: (BuildContext context, int index) { 42 | return Center( 43 | child: ProductCard.wishlist( 44 | product: state.wishlist.products[index], 45 | ), 46 | ); 47 | }, 48 | ), 49 | ); 50 | } 51 | return Text('Something went wrong!'); 52 | }, 53 | )); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/screens/catalog/catalog_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import '../../blocs/blocs.dart'; 4 | import '/models/models.dart'; 5 | import '/widgets/widgets.dart'; 6 | 7 | class CatalogScreen extends StatelessWidget { 8 | static const String routeName = '/catalog'; 9 | 10 | static Route route({required Category category}) { 11 | return MaterialPageRoute( 12 | settings: RouteSettings(name: routeName), 13 | builder: (context) => CatalogScreen(category: category), 14 | ); 15 | } 16 | 17 | final Category category; 18 | 19 | const CatalogScreen({ 20 | required this.category, 21 | }); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: CustomAppBar(title: category.name), 27 | bottomNavigationBar: CustomNavBar(screen: routeName), 28 | body: BlocBuilder( 29 | builder: (context, state) { 30 | if (state is ProductLoading) { 31 | return Center( 32 | child: CircularProgressIndicator(color: Colors.black), 33 | ); 34 | } 35 | if (state is ProductLoaded) { 36 | final List categoryProducts = state.products 37 | .where((product) => product.category == category.name) 38 | .toList(); 39 | 40 | return GridView.builder( 41 | padding: const EdgeInsets.symmetric( 42 | horizontal: 8.0, 43 | vertical: 16.0, 44 | ), 45 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 46 | crossAxisCount: 2, 47 | childAspectRatio: 1.15, 48 | ), 49 | itemCount: categoryProducts.length, 50 | itemBuilder: (BuildContext context, int index) { 51 | return Center( 52 | child: ProductCard.catalog( 53 | product: categoryProducts[index], 54 | ), 55 | ); 56 | }, 57 | ); 58 | } else { 59 | return Text('Something went wrong'); 60 | } 61 | }, 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply plugin: 'com.google.gms.google-services' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | android { 30 | compileSdkVersion 30 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "com.maxonflutter.ecommerce" 39 | minSdkVersion 19 40 | targetSdkVersion 30 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | multiDexEnabled true 44 | 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | implementation platform('com.google.firebase:firebase-bom:29.0.3') 63 | implementation "androidx.multidex:multidex:2.0.1" 64 | implementation 'com.google.android.gms:play-services-wallet:19.0.1' 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/widgets/hero_carousel_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/models/models.dart'; 3 | 4 | class HeroCarouselCard extends StatelessWidget { 5 | final Category? category; 6 | final Product? product; 7 | 8 | const HeroCarouselCard({ 9 | Key? key, 10 | this.category, 11 | this.product, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return InkWell( 17 | onTap: () { 18 | if (this.product == null) { 19 | Navigator.pushNamed( 20 | context, 21 | '/catalog', 22 | arguments: category, 23 | ); 24 | } 25 | }, 26 | child: Container( 27 | padding: EdgeInsets.only( 28 | left: 5, 29 | right: 5, 30 | top: 20.0, 31 | bottom: 10.0, 32 | ), 33 | child: Stack( 34 | children: [ 35 | Image.network( 36 | product == null ? category!.imageUrl : product!.imageUrl, 37 | fit: BoxFit.cover, 38 | width: 1000.0, 39 | ), 40 | this.product == null 41 | ? Positioned( 42 | bottom: 0.0, 43 | left: 0.0, 44 | right: 0.0, 45 | child: Container( 46 | decoration: BoxDecoration( 47 | gradient: LinearGradient( 48 | colors: [ 49 | Color.fromARGB(200, 0, 0, 0), 50 | Color.fromARGB(0, 0, 0, 0) 51 | ], 52 | begin: Alignment.bottomCenter, 53 | end: Alignment.topCenter, 54 | ), 55 | ), 56 | padding: EdgeInsets.symmetric( 57 | vertical: 10.0, 58 | horizontal: 20.0, 59 | ), 60 | child: Text( 61 | product == null ? category!.name : product!.name, 62 | style: TextStyle( 63 | color: Colors.white, 64 | fontSize: 20.0, 65 | fontWeight: FontWeight.bold, 66 | ), 67 | ), 68 | ), 69 | ) 70 | : SizedBox(), 71 | ], 72 | ), 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/build/XCBuildData/14dfdd0ab931d58dc68e5fe160609289-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_buildCommand2" : { 3 | "command" : "prepareForIndexing", 4 | "enableIndexBuildArena" : false, 5 | "targets" : null 6 | }, 7 | "buildCommand" : "build", 8 | "configuredTargets" : [ 9 | { 10 | "guid" : "18c1723432283e0cc55f10a6dcfd9e0288a783a885d8b0b3beb2e9f90bde3f49" 11 | } 12 | ], 13 | "containerPath" : "/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Runner.xcodeproj", 14 | "continueBuildingAfterErrors" : true, 15 | "enableIndexBuildArena" : false, 16 | "hideShellScriptEnvironment" : false, 17 | "parameters" : { 18 | "action" : "build", 19 | "activeArchitecture" : "arm64", 20 | "activeRunDestination" : { 21 | "disableOnlyActiveArch" : false, 22 | "platform" : "iphonesimulator", 23 | "sdk" : "iphonesimulator15.0", 24 | "sdkVariant" : "iphonesimulator", 25 | "supportedArchitectures" : [ 26 | "arm64", 27 | "x86_64" 28 | ], 29 | "targetArchitecture" : "arm64" 30 | }, 31 | "arenaInfo" : { 32 | "buildIntermediatesPath" : "", 33 | "buildProductsPath" : "", 34 | "derivedDataPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData", 35 | "indexDataStoreFolderPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData/Runner-epmajacmlwtqaabzntpudnqplfhx/Index/DataStore", 36 | "indexEnableDataStore" : true, 37 | "indexPCHPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData/Runner-epmajacmlwtqaabzntpudnqplfhx/Index/PrecompiledHeaders", 38 | "pchPath" : "" 39 | }, 40 | "configurationName" : "Debug", 41 | "overrides" : { 42 | "synthesized" : { 43 | "table" : { 44 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 45 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "15.0", 46 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 47 | "ENABLE_PREVIEWS" : "NO", 48 | "TARGET_DEVICE_IDENTIFIER" : "D11C168F-51ED-4AE8-A262-CF80C31D8D56", 49 | "TARGET_DEVICE_MODEL" : "iPod9,1", 50 | "TARGET_DEVICE_OS_VERSION" : "15.0", 51 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 52 | } 53 | } 54 | } 55 | }, 56 | "schemeCommand" : "launch", 57 | "schemeCommand2" : "launch", 58 | "shouldCollectMetrics" : false, 59 | "showNonLoggedProgress" : true, 60 | "useDryRun" : false, 61 | "useImplicitDependencies" : true, 62 | "useLegacyBuildLocations" : false, 63 | "useParallelTargets" : true 64 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/e6260b308c42b72f53679ddba5415d91-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_buildCommand2" : { 3 | "command" : "prepareForIndexing", 4 | "enableIndexBuildArena" : false, 5 | "targets" : null 6 | }, 7 | "buildCommand" : "build", 8 | "configuredTargets" : [ 9 | { 10 | "guid" : "18c1723432283e0cc55f10a6dcfd9e0288a783a885d8b0b3beb2e9f90bde3f49" 11 | } 12 | ], 13 | "containerPath" : "/Users/massimodelpezzo/Desktop/Flutter Apps/flutter_ecommerce_series/ios/Runner.xcodeproj", 14 | "continueBuildingAfterErrors" : true, 15 | "enableIndexBuildArena" : false, 16 | "hideShellScriptEnvironment" : false, 17 | "parameters" : { 18 | "action" : "build", 19 | "activeArchitecture" : "arm64", 20 | "activeRunDestination" : { 21 | "disableOnlyActiveArch" : false, 22 | "platform" : "iphonesimulator", 23 | "sdk" : "iphonesimulator15.0", 24 | "sdkVariant" : "iphonesimulator", 25 | "supportedArchitectures" : [ 26 | "arm64", 27 | "x86_64" 28 | ], 29 | "targetArchitecture" : "arm64" 30 | }, 31 | "arenaInfo" : { 32 | "buildIntermediatesPath" : "", 33 | "buildProductsPath" : "", 34 | "derivedDataPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData", 35 | "indexDataStoreFolderPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData/Runner-epmajacmlwtqaabzntpudnqplfhx/Index/DataStore", 36 | "indexEnableDataStore" : true, 37 | "indexPCHPath" : "/Users/massimodelpezzo/Library/Developer/Xcode/DerivedData/Runner-epmajacmlwtqaabzntpudnqplfhx/Index/PrecompiledHeaders", 38 | "pchPath" : "" 39 | }, 40 | "configurationName" : "Debug", 41 | "overrides" : { 42 | "synthesized" : { 43 | "table" : { 44 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 45 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "15.0", 46 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 47 | "ENABLE_PREVIEWS" : "NO", 48 | "TARGET_DEVICE_IDENTIFIER" : "D11C168F-51ED-4AE8-A262-CF80C31D8D56", 49 | "TARGET_DEVICE_MODEL" : "iPod9,1", 50 | "TARGET_DEVICE_OS_VERSION" : "15.0", 51 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 52 | } 53 | } 54 | } 55 | }, 56 | "qos" : "default", 57 | "schemeCommand" : "launch", 58 | "schemeCommand2" : "launch", 59 | "shouldCollectMetrics" : false, 60 | "showNonLoggedProgress" : true, 61 | "useDryRun" : true, 62 | "useImplicitDependencies" : true, 63 | "useLegacyBuildLocations" : false, 64 | "useParallelTargets" : true 65 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | .env 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | 50 | google-services.json 51 | GoogleService-Info.plist 52 | 53 | # Miscellaneous 54 | *.class 55 | *.log 56 | *.pyc 57 | *.swp 58 | .DS_Store 59 | .atom/ 60 | .buildlog/ 61 | .history 62 | .svn/ 63 | 64 | # IntelliJ related 65 | *.iml 66 | *.ipr 67 | *.iws 68 | .idea/ 69 | 70 | # Visual Studio Code related 71 | .vscode/ 72 | 73 | # Flutter/Dart/Pub related 74 | **/doc/api/ 75 | .dart_tool/ 76 | .flutter-plugins 77 | .packages 78 | .pub-cache/ 79 | .pub/ 80 | /build/ 81 | 82 | # Android related 83 | **/android/**/gradle-wrapper.jar 84 | **/android/.gradle 85 | **/android/captures/ 86 | **/android/gradlew 87 | **/android/gradlew.bat 88 | **/android/local.properties 89 | **/android/**/GeneratedPluginRegistrant.java 90 | 91 | # iOS/XCode related 92 | **/ios/**/*.mode1v3 93 | **/ios/**/*.mode2v3 94 | **/ios/**/*.moved-aside 95 | **/ios/**/*.pbxuser 96 | **/ios/**/*.perspectivev3 97 | **/ios/**/*sync/ 98 | **/ios/**/.sconsign.dblite 99 | **/ios/**/.tags* 100 | **/ios/**/.vagrant/ 101 | **/ios/**/DerivedData/ 102 | **/ios/**/Icon? 103 | **/ios/**/Pods/ 104 | **/ios/**/.symlinks/ 105 | **/ios/**/profile 106 | **/ios/**/xcuserdata 107 | **/ios/.generated/ 108 | **/ios/Flutter/App.framework 109 | **/ios/Flutter/Flutter.framework 110 | **/ios/Flutter/Generated.xcconfig 111 | **/ios/Flutter/app.flx 112 | **/ios/Flutter/app.zip 113 | **/ios/Flutter/flutter_assets/ 114 | **/ios/ServiceDefinitions.json 115 | **/ios/Runner/GeneratedPluginRegistrant.* 116 | 117 | # Exceptions to above rules. 118 | !**/ios/**/default.mode1v3 119 | !**/ios/**/default.mode2v3 120 | !**/ios/**/default.pbxuser 121 | !**/ios/**/default.perspectivev3 122 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 123 | -------------------------------------------------------------------------------- /lib/blocs/wishlist/wishlist_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:ecommerce/repositories/local_storage/local_storage_repository.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:hive_flutter/hive_flutter.dart'; 8 | import '/models/models.dart'; 9 | 10 | part 'wishlist_event.dart'; 11 | part 'wishlist_state.dart'; 12 | 13 | class WishlistBloc extends Bloc { 14 | final LocalStorageRepository _localStorageRepository; 15 | 16 | WishlistBloc({required LocalStorageRepository localStorageRepository}) 17 | : _localStorageRepository = localStorageRepository, 18 | super(WishlistLoading()) { 19 | on(_onStartWishlist); 20 | on(_onAddProductToWishlist); 21 | on(_onRemoveProductFromWishlist); 22 | } 23 | 24 | void _onStartWishlist( 25 | StartWishlist event, 26 | Emitter emit, 27 | ) async { 28 | emit(WishlistLoading()); 29 | try { 30 | Box box = await _localStorageRepository.openBox(); 31 | List products = _localStorageRepository.getWishlist(box); 32 | await Future.delayed(const Duration(seconds: 1)); 33 | emit( 34 | WishlistLoaded( 35 | wishlist: Wishlist(products: products), 36 | ), 37 | ); 38 | } catch (_) { 39 | emit(WishlistError()); 40 | } 41 | } 42 | 43 | void _onAddProductToWishlist( 44 | AddProductToWishlist event, 45 | Emitter emit, 46 | ) async { 47 | if (this.state is WishlistLoaded) { 48 | try { 49 | Box box = await _localStorageRepository.openBox(); 50 | _localStorageRepository.addProductToWishlist(box, event.product); 51 | 52 | emit( 53 | WishlistLoaded( 54 | wishlist: Wishlist( 55 | products: 56 | List.from((this.state as WishlistLoaded).wishlist.products) 57 | ..add(event.product), 58 | ), 59 | ), 60 | ); 61 | } on Exception { 62 | emit(WishlistError()); 63 | } 64 | } 65 | } 66 | 67 | void _onRemoveProductFromWishlist( 68 | RemoveProductFromWishlist event, 69 | Emitter emit, 70 | ) async { 71 | if (this.state is WishlistLoaded) { 72 | try { 73 | Box box = await _localStorageRepository.openBox(); 74 | _localStorageRepository.removeProductFromWishlist(box, event.product); 75 | 76 | emit( 77 | WishlistLoaded( 78 | wishlist: Wishlist( 79 | products: 80 | List.from((this.state as WishlistLoaded).wishlist.products) 81 | ..remove(event.product), 82 | ), 83 | ), 84 | ); 85 | } on Exception { 86 | emit(WishlistError()); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/screens/payment_selection/payment_selection_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:pay/pay.dart'; 6 | 7 | import '/blocs/blocs.dart'; 8 | import '/widgets/widgets.dart'; 9 | import '/models/models.dart'; 10 | 11 | class PaymentSelection extends StatelessWidget { 12 | static const String routeName = '/payment-selection'; 13 | 14 | static Route route() { 15 | return MaterialPageRoute( 16 | settings: RouteSettings(name: routeName), 17 | builder: (context) => PaymentSelection(), 18 | ); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: CustomAppBar(title: 'Payment Selection'), 25 | bottomNavigationBar: CustomNavBar(screen: routeName), 26 | body: BlocBuilder( 27 | builder: (context, state) { 28 | if (state is PaymentLoading) { 29 | return Center( 30 | child: CircularProgressIndicator( 31 | color: Colors.black, 32 | ), 33 | ); 34 | } 35 | if (state is PaymentLoaded) { 36 | return ListView( 37 | padding: const EdgeInsets.all(20.0), 38 | children: [ 39 | Platform.isIOS 40 | ? RawApplePayButton( 41 | style: ApplePayButtonStyle.black, 42 | type: ApplePayButtonType.inStore, 43 | onPressed: () { 44 | context.read().add( 45 | SelectPaymentMethod( 46 | paymentMethod: PaymentMethod.apple_pay), 47 | ); 48 | Navigator.pop(context); 49 | }, 50 | ) 51 | : SizedBox(), 52 | const SizedBox(height: 10), 53 | Platform.isAndroid 54 | ? RawGooglePayButton( 55 | style: GooglePayButtonStyle.black, 56 | type: GooglePayButtonType.pay, 57 | onPressed: () { 58 | context.read().add( 59 | SelectPaymentMethod( 60 | paymentMethod: PaymentMethod.google_pay), 61 | ); 62 | Navigator.pop(context); 63 | }, 64 | ) 65 | : SizedBox(), 66 | ElevatedButton( 67 | onPressed: () { 68 | context.read().add( 69 | SelectPaymentMethod( 70 | paymentMethod: PaymentMethod.credit_card), 71 | ); 72 | Navigator.pop(context); 73 | }, 74 | child: Text('Pay with Credit Card'), 75 | ), 76 | ], 77 | ); 78 | } else { 79 | return Text('Something went wrong'); 80 | } 81 | }, 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:ecommerce/blocs/auth/auth_bloc.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:firebase_core/firebase_core.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:hive_flutter/hive_flutter.dart'; 6 | 7 | import '/config/theme.dart'; 8 | import '/config/app_router.dart'; 9 | import '/blocs/blocs.dart'; 10 | import '/repositories/repositories.dart'; 11 | import '/screens/screens.dart'; 12 | import '/simple_bloc_observer.dart'; 13 | import '/models/models.dart'; 14 | 15 | Future main() async { 16 | WidgetsFlutterBinding.ensureInitialized(); 17 | await Firebase.initializeApp(); 18 | await Hive.initFlutter(); 19 | Hive.registerAdapter(ProductAdapter()); 20 | BlocOverrides.runZoned( 21 | () { 22 | runApp(MyApp()); 23 | }, 24 | blocObserver: SimpleBlocObserver(), 25 | ); 26 | } 27 | 28 | class MyApp extends StatelessWidget { 29 | @override 30 | Widget build(BuildContext context) { 31 | return MaterialApp( 32 | title: 'Zero To Unicorn', 33 | debugShowCheckedModeBanner: false, 34 | theme: theme(), 35 | home: MultiRepositoryProvider( 36 | providers: [ 37 | RepositoryProvider( 38 | create: (context) => AuthRepository(), 39 | ), 40 | RepositoryProvider( 41 | create: (context) => UserRepository(), 42 | ), 43 | ], 44 | child: MultiBlocProvider( 45 | providers: [ 46 | BlocProvider( 47 | create: (context) => AuthBloc( 48 | authRepository: context.read(), 49 | userRepository: context.read(), 50 | ), 51 | ), 52 | BlocProvider( 53 | create: (_) => CartBloc()..add(LoadCart()), 54 | ), 55 | BlocProvider( 56 | create: (_) => PaymentBloc()..add(LoadPaymentMethod()), 57 | ), 58 | BlocProvider( 59 | create: (context) => CheckoutBloc( 60 | cartBloc: context.read(), 61 | paymentBloc: context.read(), 62 | checkoutRepository: CheckoutRepository(), 63 | ), 64 | ), 65 | BlocProvider( 66 | create: (_) => WishlistBloc( 67 | localStorageRepository: LocalStorageRepository(), 68 | )..add(StartWishlist()), 69 | ), 70 | BlocProvider( 71 | create: (_) => CategoryBloc( 72 | categoryRepository: CategoryRepository(), 73 | )..add( 74 | LoadCategories(), 75 | ), 76 | ), 77 | BlocProvider( 78 | create: (_) => ProductBloc( 79 | productRepository: ProductRepository(), 80 | )..add(LoadProducts()), 81 | ), 82 | ], 83 | child: MaterialApp( 84 | title: 'Zero To Unicorn', 85 | debugShowCheckedModeBanner: false, 86 | theme: theme(), 87 | onGenerateRoute: AppRouter.onGenerateRoute, 88 | initialRoute: SplashScreen.routeName, 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/screens/cart/cart_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '/blocs/blocs.dart'; 5 | import '/widgets/widgets.dart'; 6 | 7 | class CartScreen extends StatelessWidget { 8 | static const String routeName = '/cart'; 9 | 10 | static Route route() { 11 | return MaterialPageRoute( 12 | settings: RouteSettings(name: routeName), 13 | builder: (context) => CartScreen(), 14 | ); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: CustomAppBar(title: 'Cart'), 21 | bottomNavigationBar: CustomNavBar(screen: routeName), 22 | body: BlocBuilder( 23 | builder: (context, state) { 24 | if (state is CartLoading) { 25 | return Center( 26 | child: CircularProgressIndicator( 27 | color: Colors.black, 28 | ), 29 | ); 30 | } 31 | if (state is CartLoaded) { 32 | Map cart = state.cart.productQuantity(state.cart.products); 33 | 34 | return Padding( 35 | padding: const EdgeInsets.symmetric( 36 | horizontal: 20.0, 37 | vertical: 10.0, 38 | ), 39 | child: Column( 40 | children: [ 41 | Row( 42 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 43 | children: [ 44 | Text( 45 | state.cart.freeDeliveryString, 46 | style: Theme.of(context).textTheme.headline5, 47 | ), 48 | ElevatedButton( 49 | onPressed: () { 50 | Navigator.pushNamed(context, '/'); 51 | }, 52 | style: ElevatedButton.styleFrom( 53 | primary: Colors.black, 54 | shape: RoundedRectangleBorder(), 55 | elevation: 0, 56 | ), 57 | child: Text( 58 | 'Add More Items', 59 | style: Theme.of(context) 60 | .textTheme 61 | .headline5! 62 | .copyWith(color: Colors.white), 63 | ), 64 | ), 65 | ], 66 | ), 67 | SizedBox(height: 20), 68 | SizedBox( 69 | height: 400, 70 | child: ListView.builder( 71 | itemCount: cart.keys.length, 72 | itemBuilder: (BuildContext context, int index) { 73 | return ProductCard.cart( 74 | product: cart.keys.elementAt(index), 75 | quantity: cart.values.elementAt(index), 76 | ); 77 | }, 78 | ), 79 | ), 80 | OrderSummary(), 81 | ], 82 | ), 83 | ); 84 | } 85 | return Text('Something went wrong'); 86 | }, 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ecommerce 2 | description: A new Flutter eCommerce. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.2 31 | equatable: ^2.0.3 32 | carousel_slider: ^4.0.0-nullsafety.0 33 | flutter_bloc: ^8.0.1 34 | firebase_core: ^1.14.0 35 | firebase_auth: ^3.3.13 36 | cloud_firestore: ^3.1.11 37 | flutter_svg: ^1.0.0 38 | pay: ^1.0.7 39 | hive: ^2.0.6 40 | hive_flutter: ^1.1.0 41 | 42 | dev_dependencies: 43 | flutter_test: 44 | sdk: flutter 45 | 46 | hive_generator: ^1.1.2 47 | build_runner: ^2.1.7 48 | 49 | # For information on the generic Dart part of this file, see the 50 | # following page: https://dart.dev/tools/pub/pubspec 51 | 52 | # The following section is specific to Flutter. 53 | flutter: 54 | 55 | # The following line ensures that the Material Icons font is 56 | # included with your application, so that you can use the icons in 57 | # the material Icons class. 58 | uses-material-design: true 59 | 60 | # To add assets to your application, add an assets section, like this: 61 | assets: 62 | - assets/images/ 63 | - assets/svgs/ 64 | - assets/ 65 | # - images/a_dot_ham.jpeg 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.dev/assets-and-images/#resolution-aware. 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.dev/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.dev/custom-fonts/#from-packages 92 | -------------------------------------------------------------------------------- /lib/screens/home/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:carousel_slider/carousel_slider.dart'; 4 | import '/blocs/blocs.dart'; 5 | import '/widgets/widgets.dart'; 6 | 7 | class HomeScreen extends StatelessWidget { 8 | static const String routeName = '/'; 9 | 10 | static Route route() { 11 | return MaterialPageRoute( 12 | settings: RouteSettings(name: routeName), 13 | builder: (_) => HomeScreen(), 14 | ); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return WillPopScope( 20 | onWillPop: () async => false, 21 | child: Scaffold( 22 | appBar: CustomAppBar( 23 | title: 'Zero To Unicorn', 24 | automaticallyImplyLeading: false, 25 | ), 26 | bottomNavigationBar: CustomNavBar(screen: routeName), 27 | body: Column( 28 | children: [ 29 | BlocBuilder( 30 | builder: (context, state) { 31 | if (state is CategoryLoading) { 32 | return Center( 33 | child: CircularProgressIndicator(), 34 | ); 35 | } 36 | if (state is CategoryLoaded) { 37 | return CarouselSlider( 38 | options: CarouselOptions( 39 | aspectRatio: 1.5, 40 | viewportFraction: 0.9, 41 | enlargeCenterPage: true, 42 | enlargeStrategy: CenterPageEnlargeStrategy.height, 43 | ), 44 | items: state.categories 45 | .map((category) => HeroCarouselCard(category: category)) 46 | .toList(), 47 | ); 48 | } else { 49 | return Text('Something went wrong.'); 50 | } 51 | }, 52 | ), 53 | SectionTitle(title: 'RECOMMENDED'), 54 | BlocBuilder( 55 | builder: (context, state) { 56 | if (state is ProductLoading) { 57 | return Center( 58 | child: CircularProgressIndicator(), 59 | ); 60 | } 61 | if (state is ProductLoaded) { 62 | return ProductCarousel( 63 | products: state.products 64 | .where((product) => product.isRecommended) 65 | .toList(), 66 | ); 67 | } else { 68 | return Text('Something went wrong.'); 69 | } 70 | }, 71 | ), 72 | SectionTitle(title: 'MOST POPULAR'), 73 | BlocBuilder( 74 | builder: (context, state) { 75 | if (state is ProductLoading) { 76 | return Center( 77 | child: CircularProgressIndicator(), 78 | ); 79 | } 80 | if (state is ProductLoaded) { 81 | return ProductCarousel( 82 | products: state.products 83 | .where((product) => product.isPopular) 84 | .toList(), 85 | ); 86 | } else { 87 | return Text('Something went wrong.'); 88 | } 89 | }, 90 | ), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/blocs/checkout/checkout_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:ecommerce/models/payment_method_model.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import '/blocs/blocs.dart'; 7 | import '/models/models.dart'; 8 | import '/repositories/checkout/checkout_repository.dart'; 9 | 10 | part 'checkout_event.dart'; 11 | part 'checkout_state.dart'; 12 | 13 | class CheckoutBloc extends Bloc { 14 | final CartBloc _cartBloc; 15 | final PaymentBloc _paymentBloc; 16 | final CheckoutRepository _checkoutRepository; 17 | StreamSubscription? _cartSubscription; 18 | StreamSubscription? _paymentSubscription; 19 | StreamSubscription? _checkoutSubscription; 20 | 21 | CheckoutBloc({ 22 | required CartBloc cartBloc, 23 | required PaymentBloc paymentBloc, 24 | required CheckoutRepository checkoutRepository, 25 | }) : _cartBloc = cartBloc, 26 | _paymentBloc = paymentBloc, 27 | _checkoutRepository = checkoutRepository, 28 | super( 29 | cartBloc.state is CartLoaded 30 | ? CheckoutLoaded( 31 | products: (cartBloc.state as CartLoaded).cart.products, 32 | deliveryFee: 33 | (cartBloc.state as CartLoaded).cart.deliveryFeeString, 34 | subtotal: (cartBloc.state as CartLoaded).cart.subtotalString, 35 | total: (cartBloc.state as CartLoaded).cart.totalString, 36 | ) 37 | : CheckoutLoading(), 38 | ) { 39 | on(_onUpdateCheckout); 40 | on(_onConfirmCheckout); 41 | 42 | _cartSubscription = _cartBloc.stream.listen( 43 | (state) { 44 | if (state is CartLoaded) 45 | add( 46 | UpdateCheckout(cart: state.cart), 47 | ); 48 | }, 49 | ); 50 | 51 | _paymentSubscription = _paymentBloc.stream.listen((state) { 52 | if (state is PaymentLoaded) { 53 | add( 54 | UpdateCheckout(paymentMethod: state.paymentMethod), 55 | ); 56 | } 57 | }); 58 | } 59 | 60 | void _onUpdateCheckout( 61 | UpdateCheckout event, 62 | Emitter emit, 63 | ) { 64 | if (this.state is CheckoutLoaded) { 65 | final state = this.state as CheckoutLoaded; 66 | emit( 67 | CheckoutLoaded( 68 | email: event.email ?? state.email, 69 | fullName: event.fullName ?? state.fullName, 70 | products: event.cart?.products ?? state.products, 71 | deliveryFee: event.cart?.deliveryFeeString ?? state.deliveryFee, 72 | subtotal: event.cart?.subtotalString ?? state.subtotal, 73 | total: event.cart?.totalString ?? state.total, 74 | address: event.address ?? state.address, 75 | city: event.city ?? state.city, 76 | country: event.country ?? state.country, 77 | zipCode: event.zipCode ?? state.zipCode, 78 | paymentMethod: event.paymentMethod ?? state.paymentMethod, 79 | ), 80 | ); 81 | } 82 | } 83 | 84 | void _onConfirmCheckout( 85 | ConfirmCheckout event, 86 | Emitter emit, 87 | ) async { 88 | _checkoutSubscription?.cancel(); 89 | if (this.state is CheckoutLoaded) { 90 | try { 91 | await _checkoutRepository.addCheckout(event.checkout); 92 | print('Done'); 93 | emit(CheckoutLoading()); 94 | } catch (_) {} 95 | } 96 | } 97 | 98 | @override 99 | Future close() { 100 | _cartSubscription?.cancel(); 101 | return super.close(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/widgets/order_summary.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import '/blocs/cart/cart_bloc.dart'; 4 | 5 | class OrderSummary extends StatelessWidget { 6 | const OrderSummary({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BlocBuilder( 13 | builder: (context, state) { 14 | if (state is CartLoaded) { 15 | return Column( 16 | children: [ 17 | Divider(thickness: 2), 18 | Padding( 19 | padding: 20 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 21 | child: Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 23 | children: [ 24 | Text('SUBTOTAL', 25 | style: Theme.of(context).textTheme.headline5), 26 | Text('\$${state.cart.subtotalString}', 27 | style: Theme.of(context).textTheme.headline5), 28 | ], 29 | ), 30 | ), 31 | Padding( 32 | padding: 33 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 34 | child: Row( 35 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 36 | children: [ 37 | Text('DELIVERY FEE', 38 | style: Theme.of(context).textTheme.headline5), 39 | Text('\$${state.cart.deliveryFeeString}', 40 | style: Theme.of(context).textTheme.headline5), 41 | ], 42 | ), 43 | ), 44 | SizedBox(height: 10), 45 | Stack( 46 | children: [ 47 | Container( 48 | width: MediaQuery.of(context).size.width, 49 | height: 60, 50 | alignment: Alignment.bottomCenter, 51 | decoration: BoxDecoration( 52 | color: Colors.black.withAlpha(50), 53 | ), 54 | ), 55 | Container( 56 | margin: EdgeInsets.all(5.0), 57 | width: MediaQuery.of(context).size.width - 10, 58 | height: 50, 59 | decoration: BoxDecoration( 60 | color: Colors.black, 61 | ), 62 | child: Padding( 63 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 64 | child: Row( 65 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 66 | crossAxisAlignment: CrossAxisAlignment.center, 67 | children: [ 68 | Text( 69 | 'TOTAL', 70 | style: Theme.of(context) 71 | .textTheme 72 | .headline5! 73 | .copyWith(color: Colors.white), 74 | ), 75 | Text( 76 | '\$${state.cart.totalString}', 77 | style: Theme.of(context) 78 | .textTheme 79 | .headline5! 80 | .copyWith(color: Colors.white), 81 | ), 82 | ], 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ], 89 | ); 90 | } else { 91 | return Text('Something went wrong.'); 92 | } 93 | }, 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/screens/order_confirmation/order_confirmation_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import '/models/models.dart'; 4 | import '/widgets/widgets.dart'; 5 | 6 | class OrderConfirmation extends StatelessWidget { 7 | static const String routeName = '/order-confirmation'; 8 | 9 | static Route route() { 10 | return MaterialPageRoute( 11 | settings: RouteSettings(name: routeName), 12 | builder: (context) => OrderConfirmation(), 13 | ); 14 | } 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: CustomAppBar(title: 'Order Confirmation'), 20 | bottomNavigationBar: CustomNavBar(screen: routeName), 21 | extendBodyBehindAppBar: true, 22 | body: SingleChildScrollView( 23 | child: Column( 24 | children: [ 25 | Stack( 26 | children: [ 27 | Container( 28 | color: Colors.black, 29 | width: double.infinity, 30 | height: 300, 31 | ), 32 | Positioned( 33 | left: (MediaQuery.of(context).size.width - 100) / 2, 34 | top: 125, 35 | child: SvgPicture.asset( 36 | 'assets/svgs/garlands.svg', 37 | height: 100, 38 | width: 100, 39 | ), 40 | ), 41 | Positioned( 42 | top: 250, 43 | height: 100, 44 | width: MediaQuery.of(context).size.width, 45 | child: Text( 46 | 'Your order is complete!', 47 | textAlign: TextAlign.center, 48 | style: Theme.of(context) 49 | .textTheme 50 | .headline3! 51 | .copyWith(color: Colors.white), 52 | ), 53 | ), 54 | ], 55 | ), 56 | Padding( 57 | padding: const EdgeInsets.all(20.0), 58 | child: Column( 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | children: [ 61 | Text( 62 | 'Hi Massimo,', 63 | style: Theme.of(context).textTheme.headline5, 64 | ), 65 | SizedBox(height: 10), 66 | Text( 67 | 'Thank you for purchasing on Zero To Unicorn.', 68 | style: Theme.of(context).textTheme.headline6, 69 | ), 70 | SizedBox(height: 20), 71 | Text( 72 | 'ORDER CODE: #k321-ekd3', 73 | style: Theme.of(context).textTheme.headline5, 74 | ), 75 | OrderSummary(), 76 | SizedBox(height: 20), 77 | Text( 78 | 'ORDER DETAILS', 79 | style: Theme.of(context).textTheme.headline5, 80 | ), 81 | Divider(thickness: 2), 82 | SizedBox(height: 5), 83 | ListView( 84 | shrinkWrap: true, 85 | padding: EdgeInsets.zero, 86 | physics: NeverScrollableScrollPhysics(), 87 | children: [ 88 | ProductCard.summary( 89 | product: Product.products[0], 90 | quantity: 2, 91 | ), 92 | ProductCard.summary( 93 | product: Product.products[0], 94 | quantity: 2, 95 | ), 96 | ], 97 | ), 98 | ], 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/screens/product/product_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:carousel_slider/carousel_slider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '/models/models.dart'; 4 | import '/widgets/widgets.dart'; 5 | 6 | class ProductScreen extends StatelessWidget { 7 | static const String routeName = '/product'; 8 | 9 | static Route route({required Product product}) { 10 | return MaterialPageRoute( 11 | settings: RouteSettings(name: routeName), 12 | builder: (context) => ProductScreen(product: product), 13 | ); 14 | } 15 | 16 | final Product product; 17 | 18 | const ProductScreen({ 19 | required this.product, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: CustomAppBar(title: product.name), 26 | bottomNavigationBar: CustomNavBar( 27 | screen: routeName, 28 | product: product, 29 | ), 30 | body: ListView( 31 | children: [ 32 | CarouselSlider( 33 | options: CarouselOptions( 34 | aspectRatio: 1.5, 35 | viewportFraction: 0.9, 36 | enlargeCenterPage: true, 37 | enlargeStrategy: CenterPageEnlargeStrategy.height, 38 | ), 39 | items: [HeroCarouselCard(product: product)], 40 | ), 41 | Padding( 42 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 43 | child: Column( 44 | children: [ 45 | Stack( 46 | children: [ 47 | Container( 48 | width: MediaQuery.of(context).size.width, 49 | height: 60, 50 | alignment: Alignment.bottomCenter, 51 | decoration: BoxDecoration( 52 | color: Colors.black.withAlpha(50), 53 | ), 54 | ), 55 | Container( 56 | margin: EdgeInsets.all(5.0), 57 | width: MediaQuery.of(context).size.width - 10, 58 | height: 50, 59 | decoration: BoxDecoration( 60 | color: Colors.black, 61 | ), 62 | child: Padding( 63 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 64 | child: Row( 65 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 66 | crossAxisAlignment: CrossAxisAlignment.center, 67 | children: [ 68 | Text( 69 | product.name, 70 | style: Theme.of(context) 71 | .textTheme 72 | .headline5! 73 | .copyWith(color: Colors.white), 74 | ), 75 | Text( 76 | '\$${product.price}', 77 | style: Theme.of(context) 78 | .textTheme 79 | .headline5! 80 | .copyWith(color: Colors.white), 81 | ), 82 | ], 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ExpansionTile( 89 | initiallyExpanded: true, 90 | title: Text( 91 | "Product Information", 92 | style: Theme.of(context).textTheme.headline3, 93 | ), 94 | children: [ 95 | ListTile( 96 | title: Text( 97 | product.description ?? '', 98 | style: Theme.of(context).textTheme.bodyText1, 99 | ), 100 | ) 101 | ], 102 | ), 103 | ExpansionTile( 104 | title: Text( 105 | "Delivery Information", 106 | style: Theme.of(context).textTheme.headline3, 107 | ), 108 | children: [ 109 | ListTile( 110 | title: Text( 111 | 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga.', 112 | style: Theme.of(context).textTheme.bodyText1, 113 | ), 114 | ) 115 | ], 116 | ), 117 | ], 118 | ), 119 | ), 120 | ], 121 | ), 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/screens/checkout/checkout_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '/blocs/blocs.dart'; 5 | import '/widgets/widgets.dart'; 6 | 7 | class CheckoutScreen extends StatelessWidget { 8 | static const String routeName = '/checkout'; 9 | 10 | static Route route() { 11 | return MaterialPageRoute( 12 | settings: RouteSettings(name: routeName), 13 | builder: (context) => CheckoutScreen(), 14 | ); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: CustomAppBar(title: 'Checkout'), 21 | bottomNavigationBar: CustomNavBar(screen: routeName), 22 | body: Padding( 23 | padding: const EdgeInsets.all(20.0), 24 | child: BlocBuilder( 25 | builder: (context, state) { 26 | if (state is CheckoutLoading) { 27 | return Center( 28 | child: CircularProgressIndicator(), 29 | ); 30 | } 31 | if (state is CheckoutLoaded) { 32 | return Column( 33 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | Text( 37 | 'CUSTOMER INFORMATION', 38 | style: Theme.of(context).textTheme.headline3, 39 | ), 40 | CustomTextFormField( 41 | title: 'Email', 42 | onChanged: (value) { 43 | context 44 | .read() 45 | .add(UpdateCheckout(email: value)); 46 | }, 47 | ), 48 | CustomTextFormField( 49 | title: 'Full Name', 50 | onChanged: (value) { 51 | context 52 | .read() 53 | .add(UpdateCheckout(fullName: value)); 54 | }, 55 | ), 56 | SizedBox(height: 20), 57 | Text( 58 | 'DELIVERY INFORMATION', 59 | style: Theme.of(context).textTheme.headline3, 60 | ), 61 | CustomTextFormField( 62 | title: 'Address', 63 | onChanged: (value) { 64 | context 65 | .read() 66 | .add(UpdateCheckout(address: value)); 67 | }, 68 | ), 69 | CustomTextFormField( 70 | title: 'City', 71 | onChanged: (value) { 72 | context 73 | .read() 74 | .add(UpdateCheckout(city: value)); 75 | }, 76 | ), 77 | CustomTextFormField( 78 | title: 'Country', 79 | onChanged: (value) { 80 | context 81 | .read() 82 | .add(UpdateCheckout(country: value)); 83 | }, 84 | ), 85 | CustomTextFormField( 86 | title: 'ZIP Code', 87 | onChanged: (value) { 88 | context 89 | .read() 90 | .add(UpdateCheckout(zipCode: value)); 91 | }, 92 | ), 93 | SizedBox(height: 20), 94 | Container( 95 | height: 60, 96 | alignment: Alignment.bottomCenter, 97 | decoration: BoxDecoration(color: Colors.black), 98 | child: Row( 99 | mainAxisAlignment: MainAxisAlignment.spaceAround, 100 | children: [ 101 | Center( 102 | child: TextButton( 103 | onPressed: () { 104 | Navigator.pushNamed( 105 | context, 106 | '/payment-selection', 107 | ); 108 | }, 109 | child: Text( 110 | 'SELECT A PAYMENT METHOD', 111 | style: Theme.of(context) 112 | .textTheme 113 | .headline3! 114 | .copyWith(color: Colors.white), 115 | ), 116 | ), 117 | ), 118 | IconButton( 119 | onPressed: () {}, 120 | icon: Icon( 121 | Icons.arrow_forward, 122 | color: Colors.white, 123 | ), 124 | ) 125 | ], 126 | ), 127 | ), 128 | SizedBox(height: 20), 129 | Text( 130 | 'ORDER SUMMARY', 131 | style: Theme.of(context).textTheme.headline3, 132 | ), 133 | OrderSummary() 134 | ], 135 | ); 136 | } else { 137 | return Text('Something went wrong'); 138 | } 139 | }, 140 | ), 141 | ), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/models/product_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:hive/hive.dart'; 4 | 5 | part 'product_model.g.dart'; 6 | 7 | @HiveType(typeId: 0) 8 | class Product extends Equatable { 9 | @HiveField(0) 10 | final String id; 11 | @HiveField(1) 12 | final String name; 13 | @HiveField(2) 14 | final String category; 15 | @HiveField(3) 16 | final String imageUrl; 17 | @HiveField(4) 18 | final double price; 19 | @HiveField(5) 20 | final bool isRecommended; 21 | @HiveField(6) 22 | final bool isPopular; 23 | @HiveField(7) 24 | final String? description; 25 | 26 | const Product({ 27 | required this.id, 28 | required this.name, 29 | required this.category, 30 | required this.imageUrl, 31 | required this.price, 32 | required this.isRecommended, 33 | required this.isPopular, 34 | this.description, 35 | }); 36 | 37 | static Product fromSnapshot(DocumentSnapshot snap) { 38 | Product product = Product( 39 | id: snap.id, 40 | name: snap['name'], 41 | category: snap['category'], 42 | imageUrl: snap['imageUrl'], 43 | price: snap['price'], 44 | isRecommended: snap['isRecommended'], 45 | isPopular: snap['isPopular'], 46 | description: snap['description'], 47 | ); 48 | return product; 49 | } 50 | 51 | @override 52 | List get props => [ 53 | id, 54 | name, 55 | category, 56 | imageUrl, 57 | price, 58 | isRecommended, 59 | isPopular, 60 | description, 61 | ]; 62 | 63 | static List products = [ 64 | Product( 65 | id: '1', 66 | name: 'Soft Drink #1', 67 | category: 'Soft Drinks', 68 | imageUrl: 69 | 'https://images.unsplash.com/photo-1598614187854-26a60e982dc4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/dO9A6mhSZZY 70 | price: 2.99, 71 | isRecommended: true, 72 | isPopular: false, 73 | ), 74 | Product( 75 | id: '2', 76 | name: 'Soft Drink #2', 77 | category: 'Soft Drinks', 78 | imageUrl: 79 | 'https://images.unsplash.com/photo-1610873167013-2dd675d30ef4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=488&q=80', //https://unsplash.com/photos/Viy_8zHEznk 80 | price: 2.99, 81 | isRecommended: false, 82 | isPopular: true, 83 | ), 84 | Product( 85 | id: '3', 86 | name: 'Soft Drink #3', 87 | category: 'Soft Drinks', 88 | imageUrl: 89 | 'https://images.unsplash.com/photo-1603833797131-3c0a18fcb6b1?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/5LIInaqRp5s 90 | price: 2.99, 91 | isRecommended: true, 92 | isPopular: true, 93 | ), 94 | Product( 95 | id: '4', 96 | name: 'Smoothies #1', 97 | category: 'Smoothies', 98 | imageUrl: 99 | 'https://images.unsplash.com/photo-1526424382096-74a93e105682?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80', //https://unsplash.com/photos/kcYXj4tBtes 100 | price: 2.99, 101 | isRecommended: true, 102 | isPopular: false, 103 | ), 104 | Product( 105 | id: '5', 106 | name: 'Smoothies #2', 107 | category: 'Smoothies', 108 | imageUrl: 109 | 'https://images.unsplash.com/photo-1505252585461-04db1eb84625?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1552&q=80', //https://unsplash.com/photos/CrK843Pl9a4 110 | price: 2.99, 111 | isRecommended: false, 112 | isPopular: false, 113 | ), 114 | Product( 115 | id: '6', 116 | name: 'Soft Drink #1', 117 | category: 'Soft Drinks', 118 | imageUrl: 119 | 'https://images.unsplash.com/photo-1598614187854-26a60e982dc4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/dO9A6mhSZZY 120 | price: 2.99, 121 | isRecommended: true, 122 | isPopular: false, 123 | ), 124 | Product( 125 | id: '7', 126 | name: 'Soft Drink #2', 127 | category: 'Soft Drinks', 128 | imageUrl: 129 | 'https://images.unsplash.com/photo-1610873167013-2dd675d30ef4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=488&q=80', //https://unsplash.com/photos/Viy_8zHEznk 130 | price: 2.99, 131 | isRecommended: false, 132 | isPopular: true, 133 | ), 134 | Product( 135 | id: '8', 136 | name: 'Soft Drink #3', 137 | category: 'Soft Drinks', 138 | imageUrl: 139 | 'https://images.unsplash.com/photo-1603833797131-3c0a18fcb6b1?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/5LIInaqRp5s 140 | price: 2.99, 141 | isRecommended: true, 142 | isPopular: true, 143 | ), 144 | Product( 145 | id: '9', 146 | name: 'Smoothies #1', 147 | category: 'Smoothies', 148 | imageUrl: 149 | 'https://images.unsplash.com/photo-1526424382096-74a93e105682?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80', //https://unsplash.com/photos/kcYXj4tBtes 150 | price: 2.99, 151 | isRecommended: true, 152 | isPopular: false, 153 | ), 154 | Product( 155 | id: '10', 156 | name: 'Smoothies #2', 157 | category: 'Smoothies', 158 | imageUrl: 159 | 'https://images.unsplash.com/photo-1505252585461-04db1eb84625?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1552&q=80', //https://unsplash.com/photos/CrK843Pl9a4 160 | price: 2.99, 161 | isRecommended: false, 162 | isPopular: false, 163 | ), 164 | Product( 165 | id: '11', 166 | name: 'Soft Drink #1', 167 | category: 'Soft Drinks', 168 | imageUrl: 169 | 'https://images.unsplash.com/photo-1598614187854-26a60e982dc4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/dO9A6mhSZZY 170 | price: 2.99, 171 | isRecommended: true, 172 | isPopular: false, 173 | ), 174 | Product( 175 | id: '12', 176 | name: 'Soft Drink #2', 177 | category: 'Soft Drinks', 178 | imageUrl: 179 | 'https://images.unsplash.com/photo-1610873167013-2dd675d30ef4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=488&q=80', //https://unsplash.com/photos/Viy_8zHEznk 180 | price: 2.99, 181 | isRecommended: false, 182 | isPopular: true, 183 | ), 184 | Product( 185 | id: '13', 186 | name: 'Soft Drink #3', 187 | category: 'Soft Drinks', 188 | imageUrl: 189 | 'https://images.unsplash.com/photo-1603833797131-3c0a18fcb6b1?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80', //https://unsplash.com/photos/5LIInaqRp5s 190 | price: 2.99, 191 | isRecommended: true, 192 | isPopular: true, 193 | ), 194 | Product( 195 | id: '14', 196 | name: 'Smoothies #2', 197 | category: 'Smoothies', 198 | imageUrl: 199 | 'https://images.unsplash.com/photo-1505252585461-04db1eb84625?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1552&q=80', //https://unsplash.com/photos/CrK843Pl9a4 200 | price: 2.99, 201 | isRecommended: false, 202 | isPopular: false, 203 | ), 204 | ]; 205 | } 206 | -------------------------------------------------------------------------------- /lib/widgets/custom_navbar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '/widgets/widgets.dart'; 7 | import '/blocs/blocs.dart'; 8 | import '/models/models.dart'; 9 | 10 | class CustomNavBar extends StatelessWidget { 11 | final String screen; 12 | final Product? product; 13 | 14 | const CustomNavBar({ 15 | Key? key, 16 | required this.screen, 17 | this.product, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return BottomAppBar( 23 | color: Colors.black, 24 | child: Container( 25 | height: 70, 26 | child: (screen == '/product') 27 | ? AddToCartNavBar(product: product!) 28 | : (screen == '/cart') 29 | ? GoToCheckoutNavBar() 30 | : (screen == '/checkout') 31 | ? OrderNowNavBar() 32 | : HomeNavBar(), 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | class HomeNavBar extends StatelessWidget { 39 | const HomeNavBar({ 40 | Key? key, 41 | }) : super(key: key); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Row( 46 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 47 | crossAxisAlignment: CrossAxisAlignment.center, 48 | children: [ 49 | IconButton( 50 | icon: Icon(Icons.home, color: Colors.white), 51 | onPressed: () { 52 | Navigator.pushNamed(context, '/'); 53 | }, 54 | ), 55 | IconButton( 56 | icon: Icon(Icons.shopping_cart, color: Colors.white), 57 | onPressed: () { 58 | Navigator.pushNamed(context, '/cart'); 59 | }, 60 | ), 61 | IconButton( 62 | icon: Icon(Icons.person, color: Colors.white), 63 | onPressed: () { 64 | Navigator.pushNamed(context, '/user'); 65 | }, 66 | ) 67 | ], 68 | ); 69 | } 70 | } 71 | 72 | class AddToCartNavBar extends StatelessWidget { 73 | const AddToCartNavBar({ 74 | Key? key, 75 | required this.product, 76 | }) : super(key: key); 77 | 78 | final Product product; 79 | @override 80 | Widget build(BuildContext context) { 81 | return Row( 82 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 83 | crossAxisAlignment: CrossAxisAlignment.center, 84 | children: [ 85 | IconButton( 86 | icon: Icon(Icons.share, color: Colors.white), 87 | onPressed: () {}, 88 | ), 89 | BlocBuilder( 90 | builder: (context, state) { 91 | if (state is WishlistLoading) { 92 | return CircularProgressIndicator(); 93 | } 94 | if (state is WishlistLoaded) { 95 | return IconButton( 96 | icon: Icon(Icons.favorite, color: Colors.white), 97 | onPressed: () { 98 | ScaffoldMessenger.of(context).showSnackBar( 99 | SnackBar( 100 | content: Text('Added to your Wishlist!'), 101 | ), 102 | ); 103 | context 104 | .read() 105 | .add(AddProductToWishlist(product)); 106 | }, 107 | ); 108 | } 109 | return Text('Something went wrong!'); 110 | }, 111 | ), 112 | BlocBuilder( 113 | builder: (context, state) { 114 | if (state is CartLoading) { 115 | return CircularProgressIndicator(); 116 | } 117 | if (state is CartLoaded) { 118 | return ElevatedButton( 119 | onPressed: () { 120 | context.read().add(AddProduct(product)); 121 | Navigator.pushNamed(context, '/cart'); 122 | }, 123 | style: ElevatedButton.styleFrom( 124 | primary: Colors.white, 125 | shape: RoundedRectangleBorder(), 126 | ), 127 | child: Text( 128 | 'ADD TO CART', 129 | style: Theme.of(context).textTheme.headline3, 130 | ), 131 | ); 132 | } 133 | return Text('Something went wrong!'); 134 | }, 135 | ), 136 | ], 137 | ); 138 | } 139 | } 140 | 141 | class GoToCheckoutNavBar extends StatelessWidget { 142 | const GoToCheckoutNavBar({ 143 | Key? key, 144 | }) : super(key: key); 145 | 146 | @override 147 | Widget build(BuildContext context) { 148 | return Row( 149 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 150 | crossAxisAlignment: CrossAxisAlignment.center, 151 | children: [ 152 | ElevatedButton( 153 | onPressed: () { 154 | Navigator.pushNamed(context, '/checkout'); 155 | }, 156 | style: ElevatedButton.styleFrom( 157 | primary: Colors.white, 158 | shape: RoundedRectangleBorder(), 159 | ), 160 | child: Text( 161 | 'GO TO CHECKOUT', 162 | style: Theme.of(context).textTheme.headline3, 163 | ), 164 | ), 165 | ], 166 | ); 167 | } 168 | } 169 | 170 | class OrderNowNavBar extends StatelessWidget { 171 | const OrderNowNavBar({ 172 | Key? key, 173 | }) : super(key: key); 174 | 175 | @override 176 | Widget build(BuildContext context) { 177 | return Row( 178 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 179 | crossAxisAlignment: CrossAxisAlignment.center, 180 | children: [ 181 | BlocBuilder( 182 | builder: (context, state) { 183 | if (state is CheckoutLoading) { 184 | return Center( 185 | child: CircularProgressIndicator( 186 | color: Colors.white, 187 | ), 188 | ); 189 | } 190 | if (state is CheckoutLoaded) { 191 | if (state.paymentMethod == PaymentMethod.credit_card) { 192 | return Container( 193 | child: Text( 194 | 'Pay with Credit Card', 195 | style: Theme.of(context) 196 | .textTheme 197 | .headline4! 198 | .copyWith(color: Colors.white), 199 | ), 200 | ); 201 | } 202 | if (Platform.isAndroid && 203 | state.paymentMethod == PaymentMethod.google_pay) { 204 | return GooglePay( 205 | products: state.products!, 206 | total: state.total!, 207 | ); 208 | } 209 | if (Platform.isIOS && 210 | state.paymentMethod == PaymentMethod.apple_pay) { 211 | return ApplePay( 212 | products: state.products!, 213 | total: state.total!, 214 | ); 215 | } else { 216 | return ElevatedButton( 217 | onPressed: () { 218 | Navigator.pushNamed(context, '/payment-selection'); 219 | }, 220 | style: ElevatedButton.styleFrom(primary: Colors.white), 221 | child: Text( 222 | 'CHOOSE PAYMENT', 223 | style: Theme.of(context).textTheme.headline3, 224 | ), 225 | ); 226 | } 227 | } else { 228 | return Text('Something went wrong'); 229 | } 230 | }, 231 | ), 232 | ], 233 | ); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /lib/widgets/product_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '/blocs/blocs.dart'; 5 | import '/models/models.dart'; 6 | 7 | class ProductCard extends StatelessWidget { 8 | const ProductCard.catalog({ 9 | Key? key, 10 | required this.product, 11 | this.quantity, 12 | this.widthFactor = 2.25, 13 | this.height = 150, 14 | this.isCatalog = true, 15 | this.isWishlist = false, 16 | this.isCart = false, 17 | this.isSummary = false, 18 | this.iconColor = Colors.white, 19 | this.fontColor = Colors.white, 20 | }) : super(key: key); 21 | 22 | const ProductCard.wishlist({ 23 | Key? key, 24 | required this.product, 25 | this.quantity, 26 | this.widthFactor = 1.1, 27 | this.height = 150, 28 | this.isCatalog = false, 29 | this.isWishlist = true, 30 | this.isCart = false, 31 | this.isSummary = false, 32 | this.iconColor = Colors.white, 33 | this.fontColor = Colors.white, 34 | }) : super(key: key); 35 | 36 | const ProductCard.cart({ 37 | Key? key, 38 | required this.product, 39 | this.quantity, 40 | this.widthFactor = 2.25, 41 | this.height = 80, 42 | this.isCatalog = false, 43 | this.isWishlist = false, 44 | this.isCart = true, 45 | this.isSummary = false, 46 | this.iconColor = Colors.black, 47 | this.fontColor = Colors.black, 48 | }) : super(key: key); 49 | 50 | const ProductCard.summary({ 51 | Key? key, 52 | required this.product, 53 | this.quantity, 54 | this.widthFactor = 2.25, 55 | this.height = 80, 56 | this.isCatalog = false, 57 | this.isWishlist = false, 58 | this.isCart = false, 59 | this.isSummary = true, 60 | this.iconColor = Colors.black, 61 | this.fontColor = Colors.black, 62 | }) : super(key: key); 63 | 64 | final Product product; 65 | final int? quantity; 66 | final double widthFactor; 67 | final double height; 68 | final bool isCatalog; 69 | final bool isWishlist; 70 | final bool isCart; 71 | final bool isSummary; 72 | final Color iconColor; 73 | final Color fontColor; 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | final double width = MediaQuery.of(context).size.width; 78 | final double adjWidth = width / widthFactor; 79 | 80 | return InkWell( 81 | onTap: () { 82 | if (isCatalog || isWishlist) 83 | Navigator.pushNamed( 84 | context, 85 | '/product', 86 | arguments: product, 87 | ); 88 | }, 89 | child: isCart || isSummary 90 | ? Padding( 91 | padding: const EdgeInsets.only(bottom: 10.0), 92 | child: Row( 93 | children: [ 94 | ProductImage( 95 | adjWidth: 100, 96 | height: height, 97 | product: product, 98 | ), 99 | const SizedBox(width: 10), 100 | Expanded( 101 | child: ProductInformation( 102 | product: product, 103 | fontColor: fontColor, 104 | quantity: quantity, 105 | isOrderSummary: isSummary ? true : false, 106 | ), 107 | ), 108 | const SizedBox(width: 10), 109 | ProductActions( 110 | product: product, 111 | isCatalog: isCatalog, 112 | isWishlist: isWishlist, 113 | isCart: isCart, 114 | iconColor: iconColor, 115 | quantity: quantity, 116 | ) 117 | ], 118 | ), 119 | ) 120 | : Stack( 121 | alignment: Alignment.bottomCenter, 122 | children: [ 123 | ProductImage( 124 | adjWidth: adjWidth, 125 | height: height, 126 | product: product, 127 | ), 128 | ProductBackground( 129 | adjWidth: adjWidth, 130 | widgets: [ 131 | ProductInformation( 132 | product: product, 133 | fontColor: fontColor, 134 | ), 135 | ProductActions( 136 | product: product, 137 | isCatalog: isCatalog, 138 | isWishlist: isWishlist, 139 | isCart: isCart, 140 | iconColor: iconColor, 141 | ) 142 | ], 143 | ), 144 | ], 145 | ), 146 | ); 147 | } 148 | } 149 | 150 | class ProductImage extends StatelessWidget { 151 | const ProductImage({ 152 | Key? key, 153 | required this.adjWidth, 154 | required this.height, 155 | required this.product, 156 | }) : super(key: key); 157 | 158 | final double adjWidth; 159 | final double height; 160 | final Product product; 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | return Container( 165 | width: adjWidth, 166 | height: height, 167 | child: Image.network( 168 | product.imageUrl, 169 | fit: BoxFit.cover, 170 | ), 171 | ); 172 | } 173 | } 174 | 175 | class ProductInformation extends StatelessWidget { 176 | const ProductInformation({ 177 | Key? key, 178 | required this.product, 179 | required this.fontColor, 180 | this.isOrderSummary = false, 181 | this.quantity, 182 | }) : super(key: key); 183 | 184 | final Product product; 185 | final Color fontColor; 186 | final bool isOrderSummary; 187 | final int? quantity; 188 | 189 | @override 190 | Widget build(BuildContext context) { 191 | return Row( 192 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 193 | children: [ 194 | Column( 195 | mainAxisAlignment: MainAxisAlignment.center, 196 | crossAxisAlignment: CrossAxisAlignment.start, 197 | children: [ 198 | SizedBox( 199 | width: 85, 200 | child: Text( 201 | product.name, 202 | maxLines: 1, 203 | style: Theme.of(context) 204 | .textTheme 205 | .headline5! 206 | .copyWith(color: fontColor), 207 | ), 208 | ), 209 | Text( 210 | '\$${product.price}', 211 | style: Theme.of(context) 212 | .textTheme 213 | .headline6! 214 | .copyWith(color: fontColor), 215 | ), 216 | ], 217 | ), 218 | isOrderSummary 219 | ? Text( 220 | 'Qty. $quantity', 221 | style: Theme.of(context) 222 | .textTheme 223 | .headline4! 224 | .copyWith(color: fontColor), 225 | ) 226 | : const SizedBox(), 227 | ], 228 | ); 229 | } 230 | } 231 | 232 | class ProductActions extends StatelessWidget { 233 | const ProductActions({ 234 | Key? key, 235 | required this.product, 236 | required this.isCatalog, 237 | required this.isWishlist, 238 | required this.isCart, 239 | required this.iconColor, 240 | this.quantity, 241 | }) : super(key: key); 242 | 243 | final Product product; 244 | final bool isCatalog; 245 | final bool isWishlist; 246 | final bool isCart; 247 | final Color iconColor; 248 | final int? quantity; 249 | 250 | @override 251 | Widget build(BuildContext context) { 252 | return BlocBuilder( 253 | builder: (context, state) { 254 | if (state is CartLoading) { 255 | return Center( 256 | child: CircularProgressIndicator(color: Colors.white), 257 | ); 258 | } 259 | if (state is CartLoaded) { 260 | IconButton addProduct = IconButton( 261 | icon: Icon( 262 | Icons.add_circle, 263 | color: iconColor, 264 | ), 265 | onPressed: () { 266 | ScaffoldMessenger.of(context).showSnackBar( 267 | SnackBar( 268 | content: Text('Added to your Cart!'), 269 | ), 270 | ); 271 | context.read().add(AddProduct(product)); 272 | }, 273 | ); 274 | 275 | IconButton removeProduct = IconButton( 276 | icon: Icon( 277 | Icons.remove_circle, 278 | color: iconColor, 279 | ), 280 | onPressed: () { 281 | ScaffoldMessenger.of(context).showSnackBar( 282 | SnackBar( 283 | content: Text('Removed from your Cart!'), 284 | ), 285 | ); 286 | context.read().add(RemoveProduct(product)); 287 | }, 288 | ); 289 | 290 | IconButton removeFromWishlist = IconButton( 291 | icon: Icon( 292 | Icons.delete, 293 | color: iconColor, 294 | ), 295 | onPressed: () { 296 | ScaffoldMessenger.of(context).showSnackBar( 297 | SnackBar( 298 | content: Text('Removed from your Wishlist!'), 299 | ), 300 | ); 301 | context 302 | .read() 303 | .add(RemoveProductFromWishlist(product)); 304 | }, 305 | ); 306 | 307 | Text productQuantity = Text( 308 | '$quantity', 309 | style: Theme.of(context).textTheme.headline4, 310 | ); 311 | 312 | if (isCatalog) { 313 | return Row(children: [addProduct]); 314 | } else if (isWishlist) { 315 | return Row(children: [addProduct, removeFromWishlist]); 316 | } else if (isCart) { 317 | return Row(children: [removeProduct, productQuantity, addProduct]); 318 | } else { 319 | return SizedBox(); 320 | } 321 | } else { 322 | return Text('Something went wrong.'); 323 | } 324 | }, 325 | ); 326 | } 327 | } 328 | 329 | class ProductBackground extends StatelessWidget { 330 | const ProductBackground({ 331 | Key? key, 332 | required this.adjWidth, 333 | required this.widgets, 334 | }) : super(key: key); 335 | 336 | final double adjWidth; 337 | final List widgets; 338 | 339 | @override 340 | Widget build(BuildContext context) { 341 | return Container( 342 | width: adjWidth - 10, 343 | height: 80, 344 | margin: const EdgeInsets.only(bottom: 5), 345 | alignment: Alignment.bottomCenter, 346 | decoration: BoxDecoration(color: Colors.black.withAlpha(50)), 347 | child: Container( 348 | width: adjWidth - 20, 349 | height: 70, 350 | margin: const EdgeInsets.only(bottom: 5), 351 | alignment: Alignment.bottomCenter, 352 | decoration: BoxDecoration(color: Colors.black), 353 | child: Padding( 354 | padding: const EdgeInsets.all(8.0), 355 | child: Row( 356 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 357 | children: [...widgets], 358 | ), 359 | ), 360 | ), 361 | ); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /assets/svgs/garlands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | garlands 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------