├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── 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
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ └── project.pbxproj
└── .gitignore
├── assets
├── fonts
│ ├── Lato-Bold.ttf
│ ├── Anton-Regular.ttf
│ └── Lato-Regular.ttf
└── images
│ └── product-placeholder.png
├── android
├── gradle.properties
├── .gitignore
├── 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
│ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── shop_app
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── build.gradle
├── lib
├── models
│ ├── http_exception.dart
│ ├── cart_item.dart
│ └── order_item.dart
├── screens
│ ├── splash_screen.dart
│ ├── orders_screen.dart
│ ├── product_detail_screen.dart
│ ├── user_products_screen.dart
│ ├── products_overview_screen.dart
│ ├── cart_screen.dart
│ ├── auth_screen.dart
│ └── edit_product_screen.dart
├── widgets
│ ├── products_grid.dart
│ ├── badge.dart
│ ├── app_drawer.dart
│ ├── user_product_item.dart
│ ├── cart_item_widget.dart
│ ├── order_item_widget.dart
│ └── product_item.dart
├── providers
│ ├── product.dart
│ ├── cart_provider.dart
│ ├── orders.dart
│ ├── auth.dart
│ └── products_provider.dart
└── main.dart
├── .metadata
├── .gitignore
├── test
└── widget_test.dart
├── README.md
├── pubspec.yaml
└── pubspec.lock
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/assets/fonts/Lato-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/assets/fonts/Lato-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Anton-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/assets/fonts/Anton-Regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/Lato-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/assets/fonts/Lato-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/product-placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/assets/images/product-placeholder.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivy-walobwa/shop_app/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/ivy-walobwa/shop_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/shop_app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.shop_app
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/models/http_exception.dart:
--------------------------------------------------------------------------------
1 | class HttpException implements Exception {
2 | final String message;
3 |
4 | HttpException(this.message);
5 |
6 | @override
7 | String toString() {
8 | return message;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
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-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/screens/splash_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SplashScreen extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Scaffold(
7 | body: Center(
8 | child: Text('loading...'),
9 | ),
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/models/cart_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | class CartItem {
4 | final String id;
5 | final double price;
6 | final String title;
7 | final int quantity;
8 |
9 | CartItem({
10 | @required this.price,
11 | @required this.title,
12 | @required this.id,
13 | @required this.quantity,});
14 | }
--------------------------------------------------------------------------------
/.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: 8af6b2f038c1172e61d418869363a28dffec3cb4
8 | channel: stable
9 |
10 | project_type: app
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/models/order_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | import '../models/cart_item.dart';
4 |
5 | class OrderItem {
6 | final String id;
7 | final double amount;
8 | final List products;
9 | final DateTime dateTime;
10 |
11 | OrderItem({
12 | @required this.dateTime,
13 | @required this.id,
14 | @required this.amount,
15 | @required this.products,
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | include ':app'
6 |
7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
8 | def properties = new Properties()
9 |
10 | assert localPropertiesFile.exists()
11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
12 |
13 | def flutterSdkPath = properties.getProperty("flutter.sdk")
14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
16 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Exceptions to above rules.
43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
44 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/widgets/products_grid.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import '../providers/products_provider.dart';
5 | import 'product_item.dart';
6 |
7 | class ProductsGrid extends StatelessWidget {
8 | final bool showFavorites;
9 |
10 | ProductsGrid(this.showFavorites);
11 | @override
12 | Widget build(BuildContext context) {
13 | final productsData = Provider.of(context);
14 | final loadedProducts = showFavorites ? productsData.favoriteItems : productsData.itemsList;
15 | return GridView.builder(
16 | padding: EdgeInsets.all(20.0),
17 | itemCount: loadedProducts.length,
18 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
19 | crossAxisCount: 2,
20 | crossAxisSpacing: 10,
21 | mainAxisSpacing: 10,
22 | childAspectRatio: 2/2.5
23 | ),
24 | itemBuilder: (ctx, index) => ChangeNotifierProvider.value(
25 | value: loadedProducts[index],
26 | child: ProductItem(),
27 | ),
28 | );
29 | }
30 | }
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:shop_app/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # shop_app
2 |
3 | A simple eCommerce app as I learn flutter
4 |
5 | ## Preview
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/lib/providers/product.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:http/http.dart' as http;
5 |
6 | class Product with ChangeNotifier {
7 | final String id;
8 | final String title;
9 | final String description;
10 | final String imageUrl;
11 | final double price;
12 | bool isFavorite;
13 |
14 | Product({
15 | @required this.price,
16 | @required this.id,
17 | @required this.title,
18 | @required this.imageUrl,
19 | @required this.description,
20 | this.isFavorite = false,
21 | });
22 |
23 | Future toggleIsFavoriteState(String token, String userId) async {
24 | final url =
25 | 'https://flutter-shop-app-4d183.firebaseio.com/userFavorites/$userId/$id.json?auth=$token';
26 | final oldStatus = isFavorite;
27 |
28 | isFavorite = !isFavorite;
29 | notifyListeners();
30 |
31 | try {
32 | final response = await http.put(url,
33 | body: json.encode(
34 | isFavorite,
35 | ));
36 | if (response.statusCode >= 400) {
37 | isFavorite = oldStatus;
38 | notifyListeners();
39 | }
40 | } catch (err) {
41 | isFavorite = oldStatus;
42 | notifyListeners();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/widgets/badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Badge extends StatelessWidget {
4 | const Badge({
5 | Key key,
6 | @required this.child,
7 | @required this.value,
8 | this.color,
9 | }) : super(key: key);
10 |
11 | final Widget child;
12 | final String value;
13 | final Color color;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Stack(
18 | alignment: Alignment.center,
19 | children: [
20 | child,
21 | Positioned(
22 | right: 8,
23 | top: 8,
24 | child: Container(
25 | padding: EdgeInsets.all(2.0),
26 | // color: Theme.of(context).accentColor,
27 | decoration: BoxDecoration(
28 | borderRadius: BorderRadius.circular(10.0),
29 | color: color != null ? color : Theme.of(context).accentColor,
30 | ),
31 | constraints: BoxConstraints(
32 | minWidth: 16,
33 | minHeight: 16,
34 | ),
35 | child: Text(
36 | value,
37 | textAlign: TextAlign.center,
38 | style: TextStyle(
39 | fontSize: 10,
40 | ),
41 | ),
42 | ),
43 | )
44 | ],
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: shop_app
2 | description: A new Flutter application.
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 | version: 1.0.0+1
9 |
10 | environment:
11 | sdk: ">=2.7.0 <3.0.0"
12 |
13 | dependencies:
14 | flutter:
15 | sdk: flutter
16 | cupertino_icons: ^0.1.3
17 | provider: ^4.3.2+1
18 | intl: ^0.16.1
19 | http: ^0.12.2
20 | shared_preferences: ^0.5.12
21 |
22 | dev_dependencies:
23 | flutter_test:
24 | sdk: flutter
25 |
26 | # For information on the generic Dart part of this file, see the
27 | # following page: https://dart.dev/tools/pub/pubspec
28 |
29 | # The following section is specific to Flutter.
30 | flutter:
31 | uses-material-design: true
32 |
33 | assets:
34 | - assets/images/product-placeholder.png
35 |
36 | fonts:
37 | - family: Lato
38 | fonts:
39 | - asset: assets/fonts/Lato-Regular.ttf
40 | - asset: assets/fonts/Lato-Bold.ttf
41 | weight: 700
42 | - family: Anton
43 | fonts:
44 | - asset: assets/fonts/Anton-Regular.ttf
45 |
46 |
47 | # For details regarding fonts from package dependencies,
48 | # see https://flutter.dev/custom-fonts/#from-packages
49 |
--------------------------------------------------------------------------------
/lib/screens/orders_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/orders.dart';
4 | import 'package:shop_app/widgets/order_item_widget.dart';
5 |
6 | class OrdersScreen extends StatelessWidget {
7 | static const routeName = '/orders';
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(
13 | title: Text(
14 | 'Your Orders',
15 | ),
16 | ),
17 | body: FutureBuilder(future: Provider.of(context, listen: false).fetchOrdersFromDB(), builder: (ctx, snapshot){
18 | if(snapshot.connectionState == ConnectionState.waiting){
19 | return Center(child: CircularProgressIndicator());
20 | }else{
21 | if(snapshot.error != null){
22 | return Center(child: Text('An error occurred'),);
23 | }else{
24 | return Consumer(builder: (ctx, orderData, child)=>
25 | ListView.builder(
26 | itemBuilder: (ctx, i) =>
27 | OrderItemWidget(
28 | order: orderData.orders[i],
29 | ),
30 | itemCount: orderData.orders.length,
31 | )
32 | ,);
33 | }
34 | }
35 | },)
36 | );
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | shop_app
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/widgets/app_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/auth.dart';
4 | import 'package:shop_app/screens/orders_screen.dart';
5 | import 'package:shop_app/screens/user_products_screen.dart';
6 |
7 | class AppDrawer extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | return Drawer(
11 | child: Column(
12 | children: [
13 | AppBar(
14 | title: Text('Hello Friend'),
15 | automaticallyImplyLeading: false,
16 | ),
17 | Divider(),
18 | ListTile(
19 | leading: Icon(Icons.shop),
20 | title: Text('Shop'),
21 | onTap: (){
22 | Navigator.of(context).pushNamed('/');
23 | },
24 | ),
25 | Divider(),
26 | ListTile(
27 | leading: Icon(Icons.payment),
28 | title: Text('Your Orders'),
29 | onTap: (){
30 | Navigator.of(context).pushNamed(OrdersScreen.routeName);
31 | },
32 | ),
33 | Divider(),
34 | ListTile(
35 | leading: Icon(Icons.edit),
36 | title: Text('Manage Products'),
37 | onTap: (){
38 | Navigator.of(context).pushNamed(UserProductsScreen.routeName);
39 | },
40 | ),
41 | Divider(),
42 | ListTile(
43 | leading: Icon(Icons.exit_to_app),
44 | title: Text('Logout'),
45 | onTap: (){
46 | Navigator.of(context).pop();
47 | Navigator.of(context).pushReplacementNamed('/');
48 | Provider.of(context, listen: false).logout();
49 | },
50 | ),
51 | ],
52 | ),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/providers/cart_provider.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:flutter/material.dart';
3 |
4 | import '../models/cart_item.dart';
5 |
6 | class CartProvider with ChangeNotifier {
7 | Map _items = {};
8 |
9 | Map get items {
10 | return {..._items};
11 | }
12 |
13 | int get itemsInCart {
14 | return _items.length;
15 | }
16 |
17 | double get totalAmount {
18 | var total = 0.0;
19 | _items.forEach((key, value) {
20 | total += value.price * value.quantity;
21 | });
22 | return total;
23 | }
24 |
25 | void addItems(String productId, double price, String title) {
26 |
27 | if (_items.containsKey(productId)) {
28 | _items.update(
29 | productId,
30 | (value) => CartItem(
31 | price: value.price,
32 | title: value.title,
33 | id: value.id,
34 | quantity: value.quantity + 1),
35 | );
36 | } else {
37 | _items.putIfAbsent(
38 | productId,
39 | () => CartItem(
40 | price: price,
41 | title: title,
42 | id: DateTime.now().toString(),
43 | quantity: 1),
44 | );
45 | }
46 | notifyListeners();
47 | }
48 |
49 | void removeItem(String productId) {
50 | _items.remove(productId);
51 | notifyListeners();
52 | }
53 |
54 | void clearCart() {
55 | _items = {};
56 | notifyListeners();
57 | }
58 |
59 | void removeSingleItem(String productId) {
60 | if (!_items.containsKey(productId)) {
61 | return;
62 | }
63 | if (_items[productId].quantity > 1) {
64 | _items.update(
65 | productId,
66 | (value) =>
67 | CartItem(price: value.price, title: value.title, id: value.id, quantity: value.quantity - 1),
68 | );
69 | }else{
70 | _items.remove(productId);
71 | }
72 | notifyListeners();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/widgets/user_product_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/screens/edit_product_screen.dart';
4 | import '../providers/products_provider.dart';
5 |
6 | class UserProductItem extends StatelessWidget {
7 | final String id;
8 | final String title;
9 | final String imageUrl;
10 |
11 | UserProductItem({this.title, this.imageUrl, this.id});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final scaffold = Scaffold.of(context);
16 | return ListTile(
17 | title: Text(title),
18 | leading: CircleAvatar(
19 | backgroundImage: NetworkImage(imageUrl),
20 | ),
21 | trailing: Container(
22 | width: 100,
23 | child: Row(
24 | children: [
25 | IconButton(
26 | icon: Icon(
27 | Icons.edit,
28 | color: Theme.of(context).accentColor,
29 | ),
30 | onPressed: () {
31 | Navigator.of(context)
32 | .pushNamed(EditProductScreen.routeName, arguments: id);
33 | }),
34 | IconButton(
35 | icon: Icon(
36 | Icons.delete,
37 | color: Theme.of(context).errorColor,
38 | ),
39 | onPressed: () async{
40 | try {
41 | await Provider.of(context, listen: false)
42 | .deleteProduct(id);
43 | } catch (err) {
44 | scaffold.showSnackBar(
45 | SnackBar(
46 | content: Text('Deleting failed'),
47 | ),
48 | );
49 | }
50 | }),
51 | ],
52 | ),
53 | ),
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.example.shop_app"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/lib/screens/product_detail_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/products_provider.dart';
4 |
5 | class ProductDetailScreen extends StatelessWidget {
6 | static const routeName = '/detail';
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | final productId = ModalRoute.of(context).settings.arguments as String;
11 | final loadedProduct = Provider.of(context, listen: false)
12 | .findById(productId);
13 | return Scaffold(
14 | // appBar: AppBar(
15 | // title: Text(
16 | // loadedProduct.title,
17 | // ),
18 | // ),
19 | body: CustomScrollView(
20 | slivers: [
21 | SliverAppBar(
22 | expandedHeight: 300,
23 | pinned: true,
24 | flexibleSpace: FlexibleSpaceBar(
25 | title: Text(loadedProduct.title),
26 | background: Hero(
27 | tag: loadedProduct.id,
28 | child: Image.network(
29 | loadedProduct.imageUrl,
30 | fit: BoxFit.cover,
31 | ),
32 | ),
33 | ),
34 | ),
35 | SliverList(
36 | delegate: SliverChildListDelegate([
37 | SizedBox(
38 | height: 10,
39 | ),
40 | Text(
41 | '\$ ${loadedProduct.price}',
42 | style: TextStyle(color: Colors.grey, fontSize: 20),
43 | textAlign: TextAlign.center,
44 | ),
45 | SizedBox(
46 | height: 10,
47 | ),
48 | Container(
49 | padding: EdgeInsets.symmetric(horizontal: 10),
50 | width: double.infinity,
51 | child: Text(
52 | loadedProduct.description,
53 | textAlign: TextAlign.center,
54 | softWrap: true,
55 | ),
56 | ),
57 | SizedBox(
58 | height: 800,
59 | ),
60 | ])),
61 | ],
62 | ),
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/screens/user_products_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/screens/edit_product_screen.dart';
4 | import '../providers/products_provider.dart';
5 | import '../widgets/app_drawer.dart';
6 | import '../widgets/user_product_item.dart';
7 |
8 | class UserProductsScreen extends StatelessWidget {
9 | static const routeName = '/user-product';
10 |
11 | Future _onRefresh(BuildContext context) async {
12 | await Provider.of(context, listen: false)
13 | .getProductsFromDB(true);
14 | }
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | // final productsData = Provider.of(context);
19 | return Scaffold(
20 | appBar: AppBar(
21 | title: Text('Your Products'),
22 | actions: [
23 | IconButton(
24 | icon: Icon(Icons.add),
25 | onPressed: () {
26 | Navigator.of(context).pushNamed(EditProductScreen.routeName);
27 | },
28 | ),
29 | ],
30 | ),
31 | drawer: AppDrawer(),
32 | body: FutureBuilder(
33 | future: _onRefresh(context),
34 | builder: (ctx, snapshot) =>
35 | snapshot.connectionState == ConnectionState.waiting
36 | ? Center(
37 | child: CircularProgressIndicator(),
38 | )
39 | : RefreshIndicator(
40 | onRefresh: () => _onRefresh(context),
41 | child: Consumer(
42 | builder: (ctx,productsData,_)=> Padding(
43 | padding: EdgeInsets.all(8),
44 | child: ListView.builder(
45 | itemCount: productsData.itemsList.length,
46 | itemBuilder: (_, i) => Column(
47 | children: [
48 | UserProductItem(
49 | id: productsData.itemsList[i].id,
50 | title: productsData.itemsList[i].title,
51 | imageUrl: productsData.itemsList[i].imageUrl,
52 | ),
53 | Divider()
54 | ],
55 | ),
56 | ),
57 | ),
58 | ),
59 | ),
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/widgets/cart_item_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/cart_provider.dart';
4 |
5 | class CartItemWidget extends StatelessWidget {
6 | final String id;
7 | final double price;
8 | final int quantity;
9 | final String title;
10 | final String productId;
11 |
12 | CartItemWidget(
13 | {this.quantity, this.price, this.id, this.title, this.productId});
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | final cart = Provider.of(context, listen: false);
18 | return Dismissible(
19 | key: ValueKey(id),
20 | background: Container(
21 | margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
22 | alignment: Alignment.centerRight,
23 | padding: EdgeInsets.only(right: 20),
24 | color: Theme.of(context).errorColor,
25 | child: Icon(
26 | Icons.delete,
27 | color: Colors.white,
28 | size: 40,
29 | ),
30 | ),
31 | direction: DismissDirection.endToStart,
32 | onDismissed: (direction) {
33 | cart.removeItem(productId);
34 | },
35 | confirmDismiss: (direction) {
36 | return showDialog(
37 | context: context,
38 | builder: (ctx) => AlertDialog(
39 | title: Text('Are you sure?'),
40 | content: Text('Do you want to remove Item from Cart?'),
41 | actions: [
42 | FlatButton(
43 | onPressed: () {
44 | Navigator.of(ctx).pop(false);
45 | },
46 | child: Text('NO'),
47 | ),
48 | FlatButton(
49 | onPressed: () {
50 | Navigator.of(ctx).pop(true);
51 | },
52 | child: Text('YES'),
53 | ),
54 | ],
55 | ),
56 | );
57 | },
58 | child: Card(
59 | margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
60 | child: Padding(
61 | padding: const EdgeInsets.all(8.0),
62 | child: ListTile(
63 | leading: CircleAvatar(
64 | child: Padding(
65 | padding: const EdgeInsets.all(5.0),
66 | child: FittedBox(child: Text('\$ $price')),
67 | ),
68 | ),
69 | title: Text(title),
70 | subtitle: Text('Total \$ ${price * quantity}'),
71 | trailing: Text('$quantity x'),
72 | ),
73 | ),
74 | ),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/providers/orders.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:http/http.dart' as http;
3 | import 'dart:convert';
4 |
5 | import '../models/cart_item.dart';
6 | import '../models/order_item.dart';
7 |
8 | class Orders with ChangeNotifier {
9 | List _orders = [];
10 |
11 | final String token;
12 | final String userId;
13 |
14 | Orders(this.token, this.userId, this._orders);
15 |
16 | List get orders {
17 | return [..._orders];
18 | }
19 |
20 | Future addOrder(List cartProducts, double total) async {
21 | final url = 'https://flutter-shop-app-4d183.firebaseio.com/orders/$userId.json?auth=$token';
22 | final timestamp = DateTime.now();
23 |
24 | final response = await http.post(
25 | url,
26 | body: json.encode({
27 | 'amount': total,
28 | 'dateTime': timestamp.toIso8601String(),
29 | 'products': cartProducts
30 | .map((cp) => {
31 | 'price': cp.price,
32 | 'id': cp.id,
33 | 'title': cp.title,
34 | 'quantity': cp.quantity,
35 | })
36 | .toList(),
37 | }),
38 | );
39 |
40 | _orders.insert(
41 | 0,
42 | OrderItem(
43 | dateTime: timestamp,
44 | id: json.decode(response.body)['name'],
45 | amount: total,
46 | products: cartProducts,
47 | ),
48 | );
49 | notifyListeners();
50 | }
51 |
52 | Future fetchOrdersFromDB() async {
53 | final url = 'https://flutter-shop-app-4d183.firebaseio.com/orders/$userId.json?auth=$token';
54 | final response = await http.get(url);
55 | final List loadedOrders = [];
56 | final extractedData = json.decode(response.body) as Map;
57 | if(extractedData == null){
58 | return;
59 | }
60 | extractedData.forEach((orderId, orderData) {
61 | loadedOrders.add(
62 | OrderItem(
63 | dateTime: DateTime.parse(orderData['dateTime']),
64 | id: orderId,
65 | amount: orderData['amount'],
66 | products: (orderData['products'] as List)
67 | .map(
68 | (item) => CartItem(
69 | price: item['price'],
70 | title: item['title'],
71 | id: item['id'],
72 | quantity: item['quantity'],
73 | ),
74 | )
75 | .toList(),
76 | ),
77 | );
78 | });
79 | _orders = loadedOrders.reversed.toList();
80 | notifyListeners();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/lib/widgets/order_item_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:shop_app/models/order_item.dart';
7 |
8 | class OrderItemWidget extends StatefulWidget {
9 | final OrderItem order;
10 |
11 | OrderItemWidget({this.order});
12 |
13 | @override
14 | _OrderItemWidgetState createState() => _OrderItemWidgetState();
15 | }
16 |
17 | class _OrderItemWidgetState extends State {
18 | var _expanded = false;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return AnimatedContainer(
23 | duration: Duration(milliseconds: 300),
24 | height: _expanded ? min(widget.order.products.length * 20.0 + 110, 200) : 95,
25 | child: Card(
26 | margin: EdgeInsets.all(10),
27 | child: Column(
28 | children: [
29 | ListTile(
30 | title: Text('\$ ${widget.order.amount}'),
31 | subtitle: Text(
32 | DateFormat('dd/MM/yyyy hh:mm').format(widget.order.dateTime),
33 | ),
34 | trailing: IconButton(
35 | icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
36 | onPressed: () {
37 | setState(() {
38 | _expanded = !_expanded;
39 | });
40 | },
41 | ),
42 | ),
43 | AnimatedContainer(
44 | duration: Duration(milliseconds: 300),
45 | padding: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
46 | height: _expanded ? min(widget.order.products.length * 20.0 + 10, 100) : 0,
47 | child: ListView(
48 | children: widget.order.products
49 | .map((prod) => Row(
50 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
51 | children: [
52 | Text(
53 | prod.title,
54 | style: TextStyle(
55 | fontSize: 18, fontWeight: FontWeight.bold),
56 | ),
57 | Text(
58 | '${prod.quantity}x \$${prod.price}',
59 | style: TextStyle(fontSize: 18, color: Colors.grey),
60 | )
61 | ],
62 | ))
63 | .toList(),
64 | ),
65 | )
66 | ],
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/screens/products_overview_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/products_provider.dart';
4 | import 'package:shop_app/screens/cart_screen.dart';
5 | import 'package:shop_app/widgets/app_drawer.dart';
6 |
7 | import '../providers/cart_provider.dart';
8 | import '../widgets/badge.dart';
9 | import '../widgets/products_grid.dart';
10 |
11 | enum FilterOptions { Favorites, All }
12 |
13 | class ProductsOverviewScreen extends StatefulWidget {
14 | @override
15 | _ProductsOverviewScreenState createState() => _ProductsOverviewScreenState();
16 | }
17 |
18 | class _ProductsOverviewScreenState extends State {
19 | var _showFavorites = false;
20 | var _isInit = true;
21 | var _isLoading = false;
22 |
23 | @override
24 | void didChangeDependencies() {
25 | if (_isInit) {
26 | _isLoading = true;
27 | Provider.of(context).getProductsFromDB().then((value) {
28 | _isLoading= false;
29 | });
30 | }
31 | _isInit = false;
32 | super.didChangeDependencies();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Scaffold(
38 | backgroundColor: Colors.white,
39 | appBar: AppBar(
40 | title: Text('MyShop'),
41 | actions: [
42 | PopupMenuButton(
43 | onSelected: (FilterOptions selectedVal) {
44 | setState(() {
45 | if (selectedVal == FilterOptions.All) {
46 | _showFavorites = false;
47 | } else {
48 | _showFavorites = true;
49 | }
50 | });
51 | },
52 | icon: Icon(Icons.more_vert),
53 | itemBuilder: (_) => [
54 | PopupMenuItem(
55 | child: Text('Only Favorites'),
56 | value: FilterOptions.Favorites,
57 | ),
58 | PopupMenuItem(
59 | child: Text('All'),
60 | value: FilterOptions.All,
61 | )
62 | ],
63 | ),
64 | Consumer(
65 | builder: (_, cart, ch) => Badge(
66 | child: ch,
67 | value: cart.itemsInCart.toString(),
68 | ),
69 | child: IconButton(
70 | icon: Icon(Icons.shopping_cart),
71 | onPressed: () {
72 | Navigator.pushNamed(context, CartScreen.routeName);
73 | }),
74 | )
75 | ],
76 | ),
77 | drawer: AppDrawer(),
78 | body:_isLoading ? Center(child: CircularProgressIndicator(),) : ProductsGrid(_showFavorites),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
23 |
27 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/lib/widgets/product_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:shop_app/providers/auth.dart';
5 |
6 | import '../providers/product.dart';
7 | import '../screens/product_detail_screen.dart';
8 | import '../providers/cart_provider.dart';
9 |
10 | class ProductItem extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | final product = Provider.of(context, listen: false);
14 | final cart = Provider.of(context, listen: false);
15 | final authData = Provider.of(context, listen: false);
16 | return ClipRRect(
17 | borderRadius: BorderRadius.circular(10),
18 | child: GridTile(
19 | child: GestureDetector(
20 | onTap: () {
21 | Navigator.pushNamed(context, ProductDetailScreen.routeName,
22 | arguments: product.id);
23 | },
24 | child: Hero(
25 | tag: product.id,
26 | child: FadeInImage(
27 | placeholder: AssetImage('assets/images/product-placeholder.png'),
28 | image: NetworkImage(product.imageUrl),
29 | fit: BoxFit.cover,
30 | ),
31 | ),
32 | ),
33 | header: Row(
34 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
35 | children: [
36 | SizedBox(),
37 | Consumer(
38 | builder: (ctx, product, child) => IconButton(
39 | icon: Icon(product.isFavorite
40 | ? Icons.favorite
41 | : Icons.favorite_border),
42 | onPressed: () {
43 | product.toggleIsFavoriteState(
44 | authData.token, authData.userId);
45 | },
46 | ),
47 | ),
48 | ],
49 | ),
50 | footer: GridTileBar(
51 | backgroundColor: Colors.black54,
52 | title: Text(product.title),
53 | subtitle: Text('\$${product.price}'),
54 | trailing: IconButton(
55 | icon: Icon(Icons.shopping_cart),
56 | onPressed: () {
57 | cart.addItems(product.id, product.price, product.title);
58 | Scaffold.of(context).hideCurrentSnackBar();
59 | Scaffold.of(context).showSnackBar(
60 | SnackBar(
61 | content: Text('Added on item to cart'),
62 | duration: Duration(seconds: 2),
63 | action: SnackBarAction(
64 | label: 'UNDO',
65 | onPressed: () {
66 | cart.removeItem(product.id);
67 | }),
68 | ),
69 | );
70 | },
71 | ),
72 | ),
73 | ),
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import './providers/auth.dart';
5 | import './providers/cart_provider.dart';
6 | import './providers/orders.dart';
7 | import './screens/auth_screen.dart';
8 | import './screens/cart_screen.dart';
9 | import './screens/edit_product_screen.dart';
10 | import './screens/orders_screen.dart';
11 | import './screens/splash_screen.dart';
12 | import './screens/user_products_screen.dart';
13 | import 'providers/products_provider.dart';
14 | import './screens/product_detail_screen.dart';
15 | import './screens/products_overview_screen.dart';
16 |
17 | void main() {
18 | runApp(MyApp());
19 | }
20 |
21 | class MyApp extends StatelessWidget {
22 | // This widget is the root of your application.
23 | @override
24 | Widget build(BuildContext context) {
25 | return MultiProvider(
26 | providers: [
27 | ChangeNotifierProvider.value(
28 | value: Auth(),
29 | ),
30 | ChangeNotifierProxyProvider(
31 | create: null,
32 | update: (ctx, auth, oldProducts) => ProductsProvider(auth.token,
33 | auth.userId, oldProducts == null ? [] : oldProducts.items),
34 | ),
35 | ChangeNotifierProvider(
36 | create: (_) => CartProvider(),
37 | ),
38 | ChangeNotifierProxyProvider(
39 | create: null,
40 | update: (ctx, auth, oldOrders) => Orders(
41 | auth.token,
42 | auth.userId,
43 | oldOrders == null ? [] : oldOrders.orders,
44 | ),
45 | ),
46 | ],
47 | child: Consumer(
48 | builder: (ctx, auth, _) => MaterialApp(
49 | title: 'Flutter Demo',
50 | theme: ThemeData(
51 | primarySwatch: Colors.purple,
52 | accentColor: Colors.orange,
53 | iconTheme: IconThemeData(color: Colors.orange),
54 | appBarTheme: AppBarTheme(
55 | elevation: 0,
56 | ),
57 | visualDensity: VisualDensity.adaptivePlatformDensity,
58 | fontFamily: 'Lato',
59 | ),
60 | home: auth.isAuth
61 | ? ProductsOverviewScreen()
62 | : FutureBuilder(
63 | future: auth.tryAutoLogin(),
64 | builder: (ctx, snapshot) =>
65 | snapshot.connectionState == ConnectionState.waiting
66 | ? SplashScreen()
67 | : AuthScreen(),
68 | ),
69 | routes: {
70 | ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
71 | CartScreen.routeName: (ctx) => CartScreen(),
72 | OrdersScreen.routeName: (ctx) => OrdersScreen(),
73 | UserProductsScreen.routeName: (ctx) => UserProductsScreen(),
74 | EditProductScreen.routeName: (ctx) => EditProductScreen(),
75 | AuthScreen.routeName: (_) => AuthScreen()
76 | },
77 | ),
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/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/cart_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:shop_app/providers/cart_provider.dart';
4 | import 'package:shop_app/providers/orders.dart';
5 | import 'package:shop_app/widgets/cart_item_widget.dart';
6 |
7 | class CartScreen extends StatelessWidget {
8 | static const routeName = '/cart';
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | final cart = Provider.of(context);
13 | return Scaffold(
14 | appBar: AppBar(
15 | title: Text('Your Cart'),
16 | ),
17 | body: Column(
18 | children: [
19 | Card(
20 | margin: EdgeInsets.all(15),
21 | child: Padding(
22 | padding: const EdgeInsets.all(8.0),
23 | child: Row(
24 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
25 | children: [
26 | Text('Total', style: TextStyle(fontSize: 20)),
27 | Spacer(),
28 | Chip(
29 | label: Text(
30 | '\$${cart.totalAmount.toStringAsFixed(2)}',
31 | style: TextStyle(color: Colors.white),
32 | ),
33 | backgroundColor: Theme.of(context).accentColor,
34 | ),
35 | OrderButton(cart: cart)
36 | ],
37 | ),
38 | ),
39 | ),
40 | SizedBox(
41 | height: 10,
42 | ),
43 | Expanded(
44 | child: ListView.builder(
45 | itemBuilder: (ctx, index) => CartItemWidget(
46 | id: cart.items.values.toList()[index].id,
47 | title: cart.items.values.toList()[index].title,
48 | price: cart.items.values.toList()[index].price,
49 | quantity: cart.items.values.toList()[index].quantity,
50 | productId: cart.items.keys.toList()[index],
51 | ),
52 | itemCount: cart.itemsInCart,
53 | ),
54 | ),
55 | ],
56 | ),
57 | );
58 | }
59 | }
60 |
61 | class OrderButton extends StatefulWidget {
62 | const OrderButton({
63 | @required this.cart,
64 | });
65 |
66 | final CartProvider cart;
67 |
68 | @override
69 | _OrderButtonState createState() => _OrderButtonState();
70 | }
71 |
72 | class _OrderButtonState extends State {
73 | var _isLoading = false;
74 | @override
75 | Widget build(BuildContext context) {
76 | return FlatButton(
77 | onPressed:(widget.cart.itemsInCart <= 0 || _isLoading)? null: () async{
78 | setState(() {
79 | _isLoading = true;
80 | });
81 | await Provider.of(context, listen: false).addOrder(
82 | widget.cart.items.values.toList(),
83 | widget.cart.totalAmount,
84 | );
85 | setState(() {
86 | _isLoading = false;
87 | });
88 | widget.cart.clearCart();
89 | },
90 | child:_isLoading ? CircularProgressIndicator(): Text('ORDER NOW',
91 | style: TextStyle(color: Colors.orange)));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/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/providers/auth.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:http/http.dart' as http;
6 | import 'package:shared_preferences/shared_preferences.dart';
7 |
8 | import '../models/http_exception.dart';
9 |
10 | class Auth with ChangeNotifier {
11 | String _token;
12 | DateTime _expiryDate;
13 | String _userId;
14 | Timer _authTimer;
15 |
16 | bool get isAuth {
17 | return _token != null;
18 | }
19 |
20 | String get userId{
21 | return _userId;
22 | }
23 |
24 | String get token {
25 | if (_expiryDate != null &&
26 | _expiryDate.isAfter(DateTime.now()) &&
27 | _token != null) {
28 | return _token;
29 | } else {
30 | return null;
31 | }
32 | }
33 |
34 | Future _authenticate(
35 | String email, String password, String urlSegment) async {
36 | final url =
37 | 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyDIB62fNUOhwDK8N7GhLjbMWBVkzSMh_hw';
38 |
39 | try {
40 | final response = await http.post(url,
41 | body: json.encode({
42 | 'email': email,
43 | 'password': password,
44 | 'returnSecureToken': true,
45 | }));
46 | final responseData = json.decode(response.body);
47 | if (responseData['error'] != null) {
48 | throw HttpException(responseData['error']['message']);
49 | }
50 | _token = responseData['idToken'];
51 | _userId = responseData['localId'];
52 | _expiryDate = DateTime.now().add(
53 | Duration(
54 | seconds: int.parse(responseData['expiresIn']),
55 | ),
56 | );
57 | _autoLogout();
58 | notifyListeners();
59 | final prefs = await SharedPreferences.getInstance();
60 | final userData = json.encode({
61 | 'token': _token,
62 | 'userId': _userId,
63 | 'expiryDate': _expiryDate.toIso8601String(),
64 | });
65 | prefs.setString('userData', userData);
66 |
67 | } catch (err) {
68 | throw err;
69 | }
70 | }
71 |
72 | Future signUp(String email, String password) async {
73 | return _authenticate(email, password, 'signUp');
74 | }
75 |
76 | Future logIn(String email, String password) async {
77 | return _authenticate(email, password, 'signInWithPassword');
78 | }
79 |
80 | Future tryAutoLogin() async{
81 | final prefs = await SharedPreferences.getInstance();
82 | if(!prefs.containsKey('userData')){
83 | return false;
84 | }
85 | final extractedUserData = json.decode(prefs.getString('userData')) as Map;
86 | final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
87 |
88 | if(expiryDate.isBefore(DateTime.now())){
89 | return false;
90 | }
91 | _token = extractedUserData['token'];
92 | _userId = extractedUserData['userId'];
93 | _expiryDate = expiryDate;
94 | notifyListeners();
95 | _autoLogout();
96 | return true;
97 | }
98 |
99 | void logout() async{
100 | _token = null;
101 | _userId = null;
102 | _expiryDate = null;
103 | if(_authTimer != null){
104 | _authTimer.cancel();
105 | _authTimer = null;
106 | }
107 | notifyListeners();
108 | final prefs = await SharedPreferences.getInstance();
109 | prefs.clear();
110 | }
111 |
112 | void _autoLogout(){
113 | if(_authTimer != null){
114 | _authTimer.cancel();
115 | }
116 | final timeToExpire = _expiryDate.difference(DateTime.now()).inSeconds;
117 | _authTimer = Timer(Duration(seconds:timeToExpire), logout);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/lib/providers/products_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:shop_app/models/http_exception.dart';
5 | import 'product.dart';
6 | import 'package:http/http.dart' as http;
7 |
8 | class ProductsProvider with ChangeNotifier {
9 | final String token;
10 | final String userId;
11 |
12 | List items = [
13 | // Product(
14 | // id: 'p4',
15 | // title: 'A Pan',
16 | // description: 'Prepare any meal you want.',
17 | // price: 49.99,
18 | // imageUrl:
19 | // 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
20 | // ),
21 | ];
22 |
23 | ProductsProvider(this.token,this.userId, this.items);
24 |
25 |
26 | List get itemsList {
27 | return [...items];
28 | }
29 |
30 | List get favoriteItems {
31 | return items.where((item) => item.isFavorite == true).toList();
32 | }
33 |
34 | Product findById(String id) {
35 | return items.firstWhere((prod) => prod.id == id);
36 | }
37 |
38 | Future getProductsFromDB([bool filter = false]) async {
39 | final filterString = filter ? 'orderBy="creatorId"&equalTo="$userId"' : '';
40 | var url = 'https://flutter-shop-app-4d183.firebaseio.com/products.json?auth=$token&$filterString';
41 |
42 | try {
43 | final response = await http.get(url);
44 | final loadedData = json.decode(response.body) as Map;
45 | final List fetchedProducts = [];
46 | if (loadedData == null) {
47 | return;
48 | }
49 | url = 'https://flutter-shop-app-4d183.firebaseio.com/userFavorites/$userId.json?auth=$token';
50 | final favoriteResponse = await http.get(url);
51 | final favoriteData = json.decode(favoriteResponse.body);
52 |
53 | loadedData.forEach((prodId, prodData) {
54 | fetchedProducts.add(Product(
55 | price: prodData['price'],
56 | id: prodId,
57 | title: prodData['title'],
58 | imageUrl: prodData['imageUrl'],
59 | description: prodData['description'],
60 | isFavorite: favoriteData== null ? false : favoriteData[prodId] ?? false,
61 | ));
62 | });
63 | items = fetchedProducts;
64 | notifyListeners();
65 | } catch (err) {
66 | throw err;
67 | }
68 | }
69 |
70 | Future addProducts(Product product) async {
71 | var url = 'https://flutter-shop-app-4d183.firebaseio.com/products.json?auth=$token';
72 |
73 | try {
74 | var response = await http.post(url,
75 | body: json.encode({
76 | 'title': product.title,
77 | 'description': product.description,
78 | 'imageUrl': product.imageUrl,
79 | 'price': product.price,
80 | 'creatorId': userId,
81 | }));
82 | final _newProduct = Product(
83 | price: product.price,
84 | id: json.decode(response.body)['name'],
85 | title: product.title,
86 | imageUrl: product.imageUrl,
87 | description: product.imageUrl,
88 | );
89 |
90 | items.add(_newProduct);
91 |
92 | notifyListeners();
93 | } catch (err) {
94 | print(err);
95 | throw err;
96 | }
97 | }
98 |
99 | Future updateProduct(String id, Product newProduct) async {
100 | final url =
101 | 'https://flutter-shop-app-4d183.firebaseio.com/products/$id.json?auth=$token';
102 |
103 | final prodIndex = items.indexWhere((element) => element.id == id);
104 | if (prodIndex >= 0) {
105 | await http.patch(url,
106 | body: json.encode({
107 | 'title': newProduct.title,
108 | 'description': newProduct.description,
109 | 'imageUrl': newProduct.imageUrl,
110 | 'price': newProduct.price,
111 | }));
112 | items[prodIndex] = newProduct;
113 | notifyListeners();
114 | }
115 | }
116 |
117 | Future deleteProduct(String productId) async {
118 | final url =
119 | 'https://flutter-shop-app-4d183.firebaseio.com/products/$productId.json?auth=$token';
120 |
121 | final prodIndex = items.indexWhere((element) => element.id == productId);
122 | var existingProd = items[prodIndex];
123 |
124 | items.removeAt(prodIndex);
125 | notifyListeners();
126 |
127 | final response = await http.delete(url);
128 | if (response.statusCode >= 400) {
129 | items.insert(prodIndex, existingProd);
130 | notifyListeners();
131 | throw HttpException('Could not delete product');
132 | }
133 | existingProd = null;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | archive:
5 | dependency: transitive
6 | description:
7 | name: archive
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.0.13"
11 | args:
12 | dependency: transitive
13 | description:
14 | name: args
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "1.6.0"
18 | async:
19 | dependency: transitive
20 | description:
21 | name: async
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.4.1"
25 | boolean_selector:
26 | dependency: transitive
27 | description:
28 | name: boolean_selector
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "2.0.0"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.1.3"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.14.12"
46 | convert:
47 | dependency: transitive
48 | description:
49 | name: convert
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "2.1.1"
53 | crypto:
54 | dependency: transitive
55 | description:
56 | name: crypto
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "2.1.4"
60 | cupertino_icons:
61 | dependency: "direct main"
62 | description:
63 | name: cupertino_icons
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "0.1.3"
67 | ffi:
68 | dependency: transitive
69 | description:
70 | name: ffi
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "0.1.3"
74 | file:
75 | dependency: transitive
76 | description:
77 | name: file
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "5.2.1"
81 | flutter:
82 | dependency: "direct main"
83 | description: flutter
84 | source: sdk
85 | version: "0.0.0"
86 | flutter_test:
87 | dependency: "direct dev"
88 | description: flutter
89 | source: sdk
90 | version: "0.0.0"
91 | flutter_web_plugins:
92 | dependency: transitive
93 | description: flutter
94 | source: sdk
95 | version: "0.0.0"
96 | http:
97 | dependency: "direct main"
98 | description:
99 | name: http
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "0.12.2"
103 | http_parser:
104 | dependency: transitive
105 | description:
106 | name: http_parser
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "3.1.4"
110 | image:
111 | dependency: transitive
112 | description:
113 | name: image
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "2.1.12"
117 | intl:
118 | dependency: "direct main"
119 | description:
120 | name: intl
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "0.16.1"
124 | matcher:
125 | dependency: transitive
126 | description:
127 | name: matcher
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "0.12.6"
131 | meta:
132 | dependency: transitive
133 | description:
134 | name: meta
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "1.1.8"
138 | nested:
139 | dependency: transitive
140 | description:
141 | name: nested
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "0.0.4"
145 | path:
146 | dependency: transitive
147 | description:
148 | name: path
149 | url: "https://pub.dartlang.org"
150 | source: hosted
151 | version: "1.6.4"
152 | path_provider_linux:
153 | dependency: transitive
154 | description:
155 | name: path_provider_linux
156 | url: "https://pub.dartlang.org"
157 | source: hosted
158 | version: "0.0.1+2"
159 | path_provider_platform_interface:
160 | dependency: transitive
161 | description:
162 | name: path_provider_platform_interface
163 | url: "https://pub.dartlang.org"
164 | source: hosted
165 | version: "1.0.3"
166 | path_provider_windows:
167 | dependency: transitive
168 | description:
169 | name: path_provider_windows
170 | url: "https://pub.dartlang.org"
171 | source: hosted
172 | version: "0.0.4+1"
173 | pedantic:
174 | dependency: transitive
175 | description:
176 | name: pedantic
177 | url: "https://pub.dartlang.org"
178 | source: hosted
179 | version: "1.9.0"
180 | petitparser:
181 | dependency: transitive
182 | description:
183 | name: petitparser
184 | url: "https://pub.dartlang.org"
185 | source: hosted
186 | version: "2.4.0"
187 | platform:
188 | dependency: transitive
189 | description:
190 | name: platform
191 | url: "https://pub.dartlang.org"
192 | source: hosted
193 | version: "2.2.1"
194 | plugin_platform_interface:
195 | dependency: transitive
196 | description:
197 | name: plugin_platform_interface
198 | url: "https://pub.dartlang.org"
199 | source: hosted
200 | version: "1.0.3"
201 | process:
202 | dependency: transitive
203 | description:
204 | name: process
205 | url: "https://pub.dartlang.org"
206 | source: hosted
207 | version: "3.0.13"
208 | provider:
209 | dependency: "direct main"
210 | description:
211 | name: provider
212 | url: "https://pub.dartlang.org"
213 | source: hosted
214 | version: "4.3.2+1"
215 | quiver:
216 | dependency: transitive
217 | description:
218 | name: quiver
219 | url: "https://pub.dartlang.org"
220 | source: hosted
221 | version: "2.1.3"
222 | shared_preferences:
223 | dependency: "direct main"
224 | description:
225 | name: shared_preferences
226 | url: "https://pub.dartlang.org"
227 | source: hosted
228 | version: "0.5.12"
229 | shared_preferences_linux:
230 | dependency: transitive
231 | description:
232 | name: shared_preferences_linux
233 | url: "https://pub.dartlang.org"
234 | source: hosted
235 | version: "0.0.2+2"
236 | shared_preferences_macos:
237 | dependency: transitive
238 | description:
239 | name: shared_preferences_macos
240 | url: "https://pub.dartlang.org"
241 | source: hosted
242 | version: "0.0.1+10"
243 | shared_preferences_platform_interface:
244 | dependency: transitive
245 | description:
246 | name: shared_preferences_platform_interface
247 | url: "https://pub.dartlang.org"
248 | source: hosted
249 | version: "1.0.4"
250 | shared_preferences_web:
251 | dependency: transitive
252 | description:
253 | name: shared_preferences_web
254 | url: "https://pub.dartlang.org"
255 | source: hosted
256 | version: "0.1.2+7"
257 | shared_preferences_windows:
258 | dependency: transitive
259 | description:
260 | name: shared_preferences_windows
261 | url: "https://pub.dartlang.org"
262 | source: hosted
263 | version: "0.0.1+1"
264 | sky_engine:
265 | dependency: transitive
266 | description: flutter
267 | source: sdk
268 | version: "0.0.99"
269 | source_span:
270 | dependency: transitive
271 | description:
272 | name: source_span
273 | url: "https://pub.dartlang.org"
274 | source: hosted
275 | version: "1.7.0"
276 | stack_trace:
277 | dependency: transitive
278 | description:
279 | name: stack_trace
280 | url: "https://pub.dartlang.org"
281 | source: hosted
282 | version: "1.9.3"
283 | stream_channel:
284 | dependency: transitive
285 | description:
286 | name: stream_channel
287 | url: "https://pub.dartlang.org"
288 | source: hosted
289 | version: "2.0.0"
290 | string_scanner:
291 | dependency: transitive
292 | description:
293 | name: string_scanner
294 | url: "https://pub.dartlang.org"
295 | source: hosted
296 | version: "1.0.5"
297 | term_glyph:
298 | dependency: transitive
299 | description:
300 | name: term_glyph
301 | url: "https://pub.dartlang.org"
302 | source: hosted
303 | version: "1.1.0"
304 | test_api:
305 | dependency: transitive
306 | description:
307 | name: test_api
308 | url: "https://pub.dartlang.org"
309 | source: hosted
310 | version: "0.2.15"
311 | typed_data:
312 | dependency: transitive
313 | description:
314 | name: typed_data
315 | url: "https://pub.dartlang.org"
316 | source: hosted
317 | version: "1.1.6"
318 | vector_math:
319 | dependency: transitive
320 | description:
321 | name: vector_math
322 | url: "https://pub.dartlang.org"
323 | source: hosted
324 | version: "2.0.8"
325 | win32:
326 | dependency: transitive
327 | description:
328 | name: win32
329 | url: "https://pub.dartlang.org"
330 | source: hosted
331 | version: "1.7.3"
332 | xdg_directories:
333 | dependency: transitive
334 | description:
335 | name: xdg_directories
336 | url: "https://pub.dartlang.org"
337 | source: hosted
338 | version: "0.1.0"
339 | xml:
340 | dependency: transitive
341 | description:
342 | name: xml
343 | url: "https://pub.dartlang.org"
344 | source: hosted
345 | version: "3.6.1"
346 | sdks:
347 | dart: ">=2.7.0 <3.0.0"
348 | flutter: ">=1.16.0 <2.0.0"
349 |
--------------------------------------------------------------------------------
/lib/screens/auth_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:shop_app/models/http_exception.dart';
5 |
6 | import '../providers/auth.dart';
7 |
8 | enum AuthMode { Signup, Login }
9 |
10 | class AuthScreen extends StatelessWidget {
11 | static const routeName = '/auth';
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final deviceSize = MediaQuery.of(context).size;
16 | // final transformConfig = Matrix4.rotationZ(-8 * pi / 180);
17 | // transformConfig.translate(-10.0);
18 | return Scaffold(
19 | // resizeToAvoidBottomInset: false,
20 | body: Stack(
21 | children: [
22 | Container(
23 | decoration: BoxDecoration(
24 | gradient: LinearGradient(
25 | colors: [
26 | Color.fromRGBO(215, 117, 255, 1).withOpacity(0.5),
27 | Color.fromRGBO(255, 188, 117, 1).withOpacity(0.9),
28 | ],
29 | begin: Alignment.topLeft,
30 | end: Alignment.bottomRight,
31 | stops: [0, 1],
32 | ),
33 | ),
34 | ),
35 | SingleChildScrollView(
36 | child: Container(
37 | height: deviceSize.height,
38 | width: deviceSize.width,
39 | child: Column(
40 | mainAxisAlignment: MainAxisAlignment.center,
41 | crossAxisAlignment: CrossAxisAlignment.center,
42 | children: [
43 | Flexible(
44 | child: Container(
45 | margin: EdgeInsets.only(bottom: 20.0),
46 | padding:
47 | EdgeInsets.symmetric(vertical: 8.0, horizontal: 94.0),
48 | transform: Matrix4.rotationZ(-8 * pi / 180)
49 | ..translate(-10.0),
50 | // ..translate(-10.0),
51 | decoration: BoxDecoration(
52 | borderRadius: BorderRadius.circular(20),
53 | color: Colors.deepOrange.shade900,
54 | boxShadow: [
55 | BoxShadow(
56 | blurRadius: 8,
57 | color: Colors.black26,
58 | offset: Offset(0, 2),
59 | )
60 | ],
61 | ),
62 | child: Text(
63 | 'MyShop',
64 | style: TextStyle(
65 | color: Theme.of(context).accentTextTheme.title.color,
66 | fontSize: 50,
67 | fontFamily: 'Anton',
68 | fontWeight: FontWeight.normal,
69 | ),
70 | ),
71 | ),
72 | ),
73 | Flexible(
74 | flex: deviceSize.width > 600 ? 2 : 1,
75 | child: AuthCard(),
76 | ),
77 | ],
78 | ),
79 | ),
80 | ),
81 | ],
82 | ),
83 | );
84 | }
85 | }
86 |
87 | class AuthCard extends StatefulWidget {
88 | const AuthCard({
89 | Key key,
90 | }) : super(key: key);
91 |
92 | @override
93 | _AuthCardState createState() => _AuthCardState();
94 | }
95 |
96 | class _AuthCardState extends State
97 | with SingleTickerProviderStateMixin {
98 | final GlobalKey _formKey = GlobalKey();
99 | AuthMode _authMode = AuthMode.Login;
100 | Map _authData = {
101 | 'email': '',
102 | 'password': '',
103 | };
104 | var _isLoading = false;
105 | final _passwordController = TextEditingController();
106 |
107 | AnimationController _controller;
108 | Animation _opacityAnimation;
109 |
110 | @override
111 | void initState() {
112 | _controller =
113 | AnimationController(vsync: this, duration: Duration(milliseconds: 300));
114 | _opacityAnimation = Tween(
115 | begin:0.0,
116 | end:1.0,
117 | ).animate(
118 | CurvedAnimation(
119 | parent: _controller,
120 | curve: Curves.easeIn,
121 | ),
122 | );
123 | super.initState();
124 | }
125 |
126 | @override
127 | void dispose() {
128 | _controller.dispose();
129 | super.dispose();
130 | }
131 |
132 | void showErrorDialog(String message) {
133 | showDialog(
134 | context: context,
135 | builder: (ctx) => AlertDialog(
136 | title: Text('An error occurred'),
137 | content: Text(message),
138 | actions: [
139 | FlatButton(
140 | onPressed: () {
141 | Navigator.of(ctx).pop();
142 | },
143 | child: Text('OK'),
144 | ),
145 | ],
146 | ),
147 | );
148 | }
149 |
150 | void _submit() async {
151 | if (!_formKey.currentState.validate()) {
152 | // Invalid!
153 | return;
154 | }
155 | _formKey.currentState.save();
156 | setState(() {
157 | _isLoading = true;
158 | });
159 | try {
160 | if (_authMode == AuthMode.Login) {
161 | // Log user in
162 | await Provider.of(context, listen: false)
163 | .logIn(_authData['email'], _authData['password']);
164 | } else {
165 | // Sign user up
166 | await Provider.of(context, listen: false)
167 | .signUp(_authData['email'], _authData['password']);
168 | }
169 | } on HttpException catch (err) {
170 | var errorMessage = 'Authentication failed';
171 | if (err.toString().contains('EMAIL_EXISTS')) {
172 | errorMessage = 'A user with that email already exists';
173 | } else if (err.toString().contains('INVALID_EMAIL')) {
174 | errorMessage = 'This is not a valid email address';
175 | } else if (err.toString().contains('WEAK_PASSWORD')) {
176 | errorMessage = 'This password is too weak';
177 | } else if (err.toString().contains('EMAIL_NOT_FOUND')) {
178 | errorMessage = 'Could not find a user with that email address';
179 | } else if (err.toString().contains('INVALID_PASSWORD')) {
180 | errorMessage = 'Invalid password';
181 | }
182 | showErrorDialog(errorMessage);
183 | } catch (err) {
184 | const errorMessage = 'Could not authenticate you. Please try again later';
185 | showErrorDialog(errorMessage);
186 | }
187 | setState(() {
188 | _isLoading = false;
189 | });
190 | }
191 |
192 | void _switchAuthMode() {
193 | if (_authMode == AuthMode.Login) {
194 | setState(() {
195 | _authMode = AuthMode.Signup;
196 | _controller.forward();
197 | });
198 | } else {
199 | setState(() {
200 | _authMode = AuthMode.Login;
201 | _controller.reverse();
202 | });
203 | }
204 | }
205 |
206 | @override
207 | Widget build(BuildContext context) {
208 | final deviceSize = MediaQuery.of(context).size;
209 | return Card(
210 | shape: RoundedRectangleBorder(
211 | borderRadius: BorderRadius.circular(10.0),
212 | ),
213 | elevation: 8.0,
214 | child: AnimatedContainer(
215 | duration: Duration(milliseconds: 300),
216 | curve: Curves.easeIn,
217 | height: _authMode == AuthMode.Signup ? 320 : 260,
218 | constraints:
219 | BoxConstraints(minHeight: _authMode == AuthMode.Signup ? 320 : 260),
220 | width: deviceSize.width * 0.75,
221 | padding: EdgeInsets.all(16.0),
222 | child: Form(
223 | key: _formKey,
224 | child: SingleChildScrollView(
225 | child: Column(
226 | children: [
227 | TextFormField(
228 | decoration: InputDecoration(labelText: 'E-Mail'),
229 | keyboardType: TextInputType.emailAddress,
230 | validator: (value) {
231 | if (value.isEmpty || !value.contains('@')) {
232 | return 'Invalid email!';
233 | }
234 | },
235 | onSaved: (value) {
236 | _authData['email'] = value;
237 | },
238 | ),
239 | TextFormField(
240 | decoration: InputDecoration(labelText: 'Password'),
241 | obscureText: true,
242 | controller: _passwordController,
243 | validator: (value) {
244 | if (value.isEmpty || value.length < 5) {
245 | return 'Password is too short!';
246 | }
247 | },
248 | onSaved: (value) {
249 | _authData['password'] = value;
250 | },
251 | ),
252 | AnimatedContainer(
253 | constraints: BoxConstraints(minHeight: _authMode == AuthMode.Signup ? 60 : 0,maxHeight: _authMode == AuthMode.Signup ? 120 : 0),
254 | duration: Duration(milliseconds: 300),
255 | curve: Curves.easeIn,
256 | child: FadeTransition(
257 | opacity: _opacityAnimation,
258 | child: TextFormField(
259 | enabled: _authMode == AuthMode.Signup,
260 | decoration: InputDecoration(labelText: 'Confirm Password'),
261 | obscureText: true,
262 | validator: _authMode == AuthMode.Signup
263 | ? (value) {
264 | if (value != _passwordController.text) {
265 | return 'Passwords do not match!';
266 | }
267 | }
268 | : null,
269 | ),
270 | ),
271 | ),
272 | SizedBox(
273 | height: 20,
274 | ),
275 | if (_isLoading)
276 | CircularProgressIndicator()
277 | else
278 | RaisedButton(
279 | child:
280 | Text(_authMode == AuthMode.Login ? 'LOGIN' : 'SIGN UP'),
281 | onPressed: _submit,
282 | shape: RoundedRectangleBorder(
283 | borderRadius: BorderRadius.circular(30),
284 | ),
285 | padding:
286 | EdgeInsets.symmetric(horizontal: 30.0, vertical: 8.0),
287 | color: Theme.of(context).primaryColor,
288 | textColor: Theme.of(context).primaryTextTheme.button.color,
289 | ),
290 | FlatButton(
291 | child: Text(
292 | '${_authMode == AuthMode.Login ? 'SIGNUP' : 'LOGIN'} INSTEAD'),
293 | onPressed: _switchAuthMode,
294 | padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 4),
295 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
296 | textColor: Theme.of(context).primaryColor,
297 | ),
298 | ],
299 | ),
300 | ),
301 | ),
302 | ),
303 | );
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/lib/screens/edit_product_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:shop_app/providers/product.dart';
5 | import 'package:shop_app/providers/products_provider.dart';
6 |
7 | class EditProductScreen extends StatefulWidget {
8 | static const routeName = '/edit-product';
9 |
10 | @override
11 | _EditProductScreenState createState() => _EditProductScreenState();
12 | }
13 |
14 | class _EditProductScreenState extends State {
15 | final _priceFocusNode = FocusNode();
16 | final _descriptionFocusNode = FocusNode();
17 | final _imageController = TextEditingController();
18 | final _imageFocusNode = FocusNode();
19 | final _form = GlobalKey();
20 | var _isInit = true;
21 | var _isLoading = false;
22 | Product _editProduct = Product(
23 | price: 0,
24 | id: null,
25 | title: '',
26 | imageUrl: '',
27 | description: '',
28 | );
29 |
30 | var _initValues = {
31 | 'title': '',
32 | 'description': '',
33 | 'price': '',
34 | 'imageUrl': '',
35 | };
36 |
37 | @override
38 | void initState() {
39 | _imageFocusNode.addListener(_updateImage);
40 | super.initState();
41 | }
42 |
43 | @override
44 | void didChangeDependencies() {
45 | if (_isInit) {
46 | final productId = ModalRoute.of(context).settings.arguments as String;
47 | if (productId != null) {
48 | _editProduct = Provider.of(context, listen: false)
49 | .findById(productId);
50 | _initValues = {
51 | 'title': _editProduct.title,
52 | 'description': _editProduct.description,
53 | 'price': _editProduct.price.toString(),
54 | 'imageUrl': '',
55 | };
56 | _imageController.text = _editProduct.imageUrl;
57 | }
58 | }
59 | _isInit = false;
60 | super.didChangeDependencies();
61 | }
62 |
63 | @override
64 | void dispose() {
65 | _imageFocusNode.removeListener(_updateImage);
66 | _descriptionFocusNode.dispose();
67 | _priceFocusNode.dispose();
68 | _imageController.dispose();
69 | _imageFocusNode.dispose();
70 | super.dispose();
71 | }
72 |
73 | void _updateImage() {
74 | if (!_imageFocusNode.hasFocus) {
75 | if ((!_imageController.text.startsWith('http') &&
76 | !_imageController.text.startsWith('htpps')) |
77 | (!_imageController.text.endsWith('.png') &&
78 | !_imageController.text.endsWith('.jpg') &&
79 | !_imageController.text.endsWith('.jpeg'))) {
80 | return;
81 | }
82 |
83 | setState(() {});
84 | }
85 | }
86 |
87 | void _saveForm() async {
88 | final _isValid = _form.currentState.validate();
89 | if (!_isValid) {
90 | return;
91 | }
92 | _form.currentState.save();
93 | setState(() {
94 | _isLoading = true;
95 | });
96 |
97 | if (_editProduct.id != null) {
98 | await Provider.of(context, listen: false)
99 | .updateProduct(_editProduct.id, _editProduct);
100 | } else {
101 | try {
102 | await Provider.of(context, listen: false)
103 | .addProducts(_editProduct);
104 | } catch (err) {
105 | await showDialog(
106 | context: context,
107 | builder: (ctx) =>
108 | AlertDialog(
109 | title: Text('error occurred!'),
110 | content: Text('Something went wrong'),
111 | actions: [
112 | FlatButton(
113 | onPressed: () {
114 | Navigator.of(ctx).pop();
115 | },
116 | child: Text('Okay'),
117 | ),
118 | ],
119 | ));
120 | // }finally{
121 | // setState(() {
122 | // _isLoading = false;
123 | // });
124 | // Navigator.of(context).pop();
125 | // }
126 |
127 |
128 | }
129 | }
130 | setState(() {
131 | _isLoading = false;
132 | });
133 | Navigator.of(context).pop();
134 | }
135 | @override
136 | Widget build(BuildContext context) {
137 | return Scaffold(
138 | appBar: AppBar(
139 | title: Text('Edit Product'),
140 | actions: [
141 | IconButton(icon: Icon(Icons.save), onPressed: _saveForm)
142 | ],
143 | ),
144 | body: _isLoading
145 | ? Center(
146 | child: CircularProgressIndicator(),
147 | )
148 | : Padding(
149 | padding: const EdgeInsets.all(15.0),
150 | child: Form(
151 | key: _form,
152 | child: ListView(
153 | children: [
154 | TextFormField(
155 | initialValue: _initValues['title'],
156 | decoration: InputDecoration(labelText: 'Title'),
157 | textInputAction: TextInputAction.next,
158 | onFieldSubmitted: (_) {
159 | FocusScope.of(context).requestFocus(_priceFocusNode);
160 | },
161 | onSaved: (value) {
162 | _editProduct = Product(
163 | price: _editProduct.price,
164 | id: _editProduct.id,
165 | isFavorite: _editProduct.isFavorite,
166 | title: value,
167 | imageUrl: _editProduct.imageUrl,
168 | description: _editProduct.description,
169 | );
170 | },
171 | validator: (value) {
172 | if (value.isEmpty) {
173 | return 'Field cannot be empty';
174 | }
175 | return null;
176 | },
177 | ),
178 | TextFormField(
179 | initialValue: _initValues['price'],
180 | decoration: InputDecoration(labelText: 'Price'),
181 | textInputAction: TextInputAction.next,
182 | keyboardType: TextInputType.number,
183 | focusNode: _priceFocusNode,
184 | onSaved: (value) {
185 | _editProduct = Product(
186 | price: double.parse(value),
187 | id: _editProduct.id,
188 | isFavorite: _editProduct.isFavorite,
189 | title: _editProduct.title,
190 | imageUrl: _editProduct.imageUrl,
191 | description: _editProduct.description,
192 | );
193 | },
194 | onFieldSubmitted: (_) {
195 | FocusScope.of(context)
196 | .requestFocus(_descriptionFocusNode);
197 | },
198 | validator: (value) {
199 | if (value.isEmpty) {
200 | return 'Please enter a price';
201 | }
202 | if (double.tryParse(value) == null) {
203 | return 'Please enter a valid number';
204 | }
205 | if (double.parse(value) <= 0) {
206 | return 'Please enter a number greater than zero';
207 | }
208 | return null;
209 | },
210 | ),
211 | TextFormField(
212 | initialValue: _initValues['description'],
213 | decoration: InputDecoration(labelText: 'Description'),
214 | maxLines: 3,
215 | keyboardType: TextInputType.multiline,
216 | focusNode: _descriptionFocusNode,
217 | onSaved: (value) {
218 | _editProduct = Product(
219 | price: _editProduct.price,
220 | id: _editProduct.id,
221 | isFavorite: _editProduct.isFavorite,
222 | title: _editProduct.title,
223 | imageUrl: _editProduct.imageUrl,
224 | description: value,
225 | );
226 | },
227 | validator: (value) {
228 | if (value.isEmpty) {
229 | return 'Description cannot be empty';
230 | }
231 | if (value.length < 10) {
232 | return 'Description should be atleast 10 characters';
233 | }
234 | return null;
235 | },
236 | ),
237 | Row(
238 | crossAxisAlignment: CrossAxisAlignment.end,
239 | children: [
240 | Container(
241 | height: 100,
242 | width: 100,
243 | margin: EdgeInsets.only(
244 | top: 8,
245 | right: 10,
246 | ),
247 | decoration: BoxDecoration(
248 | border: Border.all(color: Colors.grey, width: 1),
249 | ),
250 | child: _imageController.text.isEmpty
251 | ? Text(
252 | "Empty Url",
253 | )
254 | : FittedBox(
255 | child: Image.network(_imageController.text),
256 | fit: BoxFit.cover,
257 | ),
258 | ),
259 | Expanded(
260 | child: TextFormField(
261 | decoration:
262 | InputDecoration(labelText: 'Image Url'),
263 | keyboardType: TextInputType.url,
264 | textInputAction: TextInputAction.done,
265 | controller: _imageController,
266 | focusNode: _imageFocusNode,
267 | onSaved: (value) {
268 | _editProduct = Product(
269 | price: _editProduct.price,
270 | id: _editProduct.id,
271 | isFavorite: _editProduct.isFavorite,
272 | title: _editProduct.title,
273 | imageUrl: value,
274 | description: _editProduct.description,
275 | );
276 | },
277 | onFieldSubmitted: (_) {
278 | _saveForm();
279 | },
280 | validator: (value) {
281 | if (value.isEmpty) {
282 | return 'URL cannot be empty';
283 | }
284 | if (!value.startsWith('http') &&
285 | !value.startsWith('https')) {
286 | return 'Please enter a valid url';
287 | }
288 | if (!value.endsWith('png') &&
289 | !value.endsWith('jpg') &&
290 | !value.endsWith('jpeg')) {
291 | return 'Not a valid image URL';
292 | }
293 | return null;
294 | },
295 | ),
296 | ),
297 | ],
298 | ),
299 | ],
300 | )),
301 | ),
302 | );
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 97C146F11CF9000F007C117D /* Supporting Files */,
94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
98 | );
99 | path = Runner;
100 | sourceTree = "";
101 | };
102 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
103 | isa = PBXGroup;
104 | children = (
105 | );
106 | name = "Supporting Files";
107 | sourceTree = "";
108 | };
109 | /* End PBXGroup section */
110 |
111 | /* Begin PBXNativeTarget section */
112 | 97C146ED1CF9000F007C117D /* Runner */ = {
113 | isa = PBXNativeTarget;
114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
115 | buildPhases = (
116 | 9740EEB61CF901F6004384FC /* Run Script */,
117 | 97C146EA1CF9000F007C117D /* Sources */,
118 | 97C146EB1CF9000F007C117D /* Frameworks */,
119 | 97C146EC1CF9000F007C117D /* Resources */,
120 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
122 | );
123 | buildRules = (
124 | );
125 | dependencies = (
126 | );
127 | name = Runner;
128 | productName = Runner;
129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
130 | productType = "com.apple.product-type.application";
131 | };
132 | /* End PBXNativeTarget section */
133 |
134 | /* Begin PBXProject section */
135 | 97C146E61CF9000F007C117D /* Project object */ = {
136 | isa = PBXProject;
137 | attributes = {
138 | LastUpgradeCheck = 1020;
139 | ORGANIZATIONNAME = "";
140 | TargetAttributes = {
141 | 97C146ED1CF9000F007C117D = {
142 | CreatedOnToolsVersion = 7.3.1;
143 | LastSwiftMigration = 1100;
144 | };
145 | };
146 | };
147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
148 | compatibilityVersion = "Xcode 9.3";
149 | developmentRegion = en;
150 | hasScannedForEncodings = 0;
151 | knownRegions = (
152 | en,
153 | Base,
154 | );
155 | mainGroup = 97C146E51CF9000F007C117D;
156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
157 | projectDirPath = "";
158 | projectRoot = "";
159 | targets = (
160 | 97C146ED1CF9000F007C117D /* Runner */,
161 | );
162 | };
163 | /* End PBXProject section */
164 |
165 | /* Begin PBXResourcesBuildPhase section */
166 | 97C146EC1CF9000F007C117D /* Resources */ = {
167 | isa = PBXResourcesBuildPhase;
168 | buildActionMask = 2147483647;
169 | files = (
170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | };
177 | /* End PBXResourcesBuildPhase section */
178 |
179 | /* Begin PBXShellScriptBuildPhase section */
180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
181 | isa = PBXShellScriptBuildPhase;
182 | buildActionMask = 2147483647;
183 | files = (
184 | );
185 | inputPaths = (
186 | );
187 | name = "Thin Binary";
188 | outputPaths = (
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | shellPath = /bin/sh;
192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
193 | };
194 | 9740EEB61CF901F6004384FC /* Run Script */ = {
195 | isa = PBXShellScriptBuildPhase;
196 | buildActionMask = 2147483647;
197 | files = (
198 | );
199 | inputPaths = (
200 | );
201 | name = "Run Script";
202 | outputPaths = (
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | shellPath = /bin/sh;
206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
207 | };
208 | /* End PBXShellScriptBuildPhase section */
209 |
210 | /* Begin PBXSourcesBuildPhase section */
211 | 97C146EA1CF9000F007C117D /* Sources */ = {
212 | isa = PBXSourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
217 | );
218 | runOnlyForDeploymentPostprocessing = 0;
219 | };
220 | /* End PBXSourcesBuildPhase section */
221 |
222 | /* Begin PBXVariantGroup section */
223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C146FB1CF9000F007C117D /* Base */,
227 | );
228 | name = Main.storyboard;
229 | sourceTree = "";
230 | };
231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
232 | isa = PBXVariantGroup;
233 | children = (
234 | 97C147001CF9000F007C117D /* Base */,
235 | );
236 | name = LaunchScreen.storyboard;
237 | sourceTree = "";
238 | };
239 | /* End PBXVariantGroup section */
240 |
241 | /* Begin XCBuildConfiguration section */
242 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
243 | isa = XCBuildConfiguration;
244 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
245 | buildSettings = {
246 | ALWAYS_SEARCH_USER_PATHS = NO;
247 | CLANG_ANALYZER_NONNULL = YES;
248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
249 | CLANG_CXX_LIBRARY = "libc++";
250 | CLANG_ENABLE_MODULES = YES;
251 | CLANG_ENABLE_OBJC_ARC = YES;
252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
253 | CLANG_WARN_BOOL_CONVERSION = YES;
254 | CLANG_WARN_COMMA = YES;
255 | CLANG_WARN_CONSTANT_CONVERSION = YES;
256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
258 | CLANG_WARN_EMPTY_BODY = YES;
259 | CLANG_WARN_ENUM_CONVERSION = YES;
260 | CLANG_WARN_INFINITE_RECURSION = YES;
261 | CLANG_WARN_INT_CONVERSION = YES;
262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
267 | CLANG_WARN_STRICT_PROTOTYPES = YES;
268 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
272 | COPY_PHASE_STRIP = NO;
273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
274 | ENABLE_NS_ASSERTIONS = NO;
275 | ENABLE_STRICT_OBJC_MSGSEND = YES;
276 | GCC_C_LANGUAGE_STANDARD = gnu99;
277 | GCC_NO_COMMON_BLOCKS = YES;
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
285 | MTL_ENABLE_DEBUG_INFO = NO;
286 | SDKROOT = iphoneos;
287 | SUPPORTED_PLATFORMS = iphoneos;
288 | TARGETED_DEVICE_FAMILY = "1,2";
289 | VALIDATE_PRODUCT = YES;
290 | };
291 | name = Profile;
292 | };
293 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
294 | isa = XCBuildConfiguration;
295 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
296 | buildSettings = {
297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
298 | CLANG_ENABLE_MODULES = YES;
299 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
300 | ENABLE_BITCODE = NO;
301 | FRAMEWORK_SEARCH_PATHS = (
302 | "$(inherited)",
303 | "$(PROJECT_DIR)/Flutter",
304 | );
305 | INFOPLIST_FILE = Runner/Info.plist;
306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
307 | LIBRARY_SEARCH_PATHS = (
308 | "$(inherited)",
309 | "$(PROJECT_DIR)/Flutter",
310 | );
311 | PRODUCT_BUNDLE_IDENTIFIER = com.example.shopApp;
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
314 | SWIFT_VERSION = 5.0;
315 | VERSIONING_SYSTEM = "apple-generic";
316 | };
317 | name = Profile;
318 | };
319 | 97C147031CF9000F007C117D /* Debug */ = {
320 | isa = XCBuildConfiguration;
321 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
322 | buildSettings = {
323 | ALWAYS_SEARCH_USER_PATHS = NO;
324 | CLANG_ANALYZER_NONNULL = YES;
325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
326 | CLANG_CXX_LIBRARY = "libc++";
327 | CLANG_ENABLE_MODULES = YES;
328 | CLANG_ENABLE_OBJC_ARC = YES;
329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
330 | CLANG_WARN_BOOL_CONVERSION = YES;
331 | CLANG_WARN_COMMA = YES;
332 | CLANG_WARN_CONSTANT_CONVERSION = YES;
333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
335 | CLANG_WARN_EMPTY_BODY = YES;
336 | CLANG_WARN_ENUM_CONVERSION = YES;
337 | CLANG_WARN_INFINITE_RECURSION = YES;
338 | CLANG_WARN_INT_CONVERSION = YES;
339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = dwarf;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_NO_COMMON_BLOCKS = YES;
356 | GCC_OPTIMIZATION_LEVEL = 0;
357 | GCC_PREPROCESSOR_DEFINITIONS = (
358 | "DEBUG=1",
359 | "$(inherited)",
360 | );
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | TARGETED_DEVICE_FAMILY = "1,2";
372 | };
373 | name = Debug;
374 | };
375 | 97C147041CF9000F007C117D /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
378 | buildSettings = {
379 | ALWAYS_SEARCH_USER_PATHS = NO;
380 | CLANG_ANALYZER_NONNULL = YES;
381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
382 | CLANG_CXX_LIBRARY = "libc++";
383 | CLANG_ENABLE_MODULES = YES;
384 | CLANG_ENABLE_OBJC_ARC = YES;
385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
386 | CLANG_WARN_BOOL_CONVERSION = YES;
387 | CLANG_WARN_COMMA = YES;
388 | CLANG_WARN_CONSTANT_CONVERSION = YES;
389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
391 | CLANG_WARN_EMPTY_BODY = YES;
392 | CLANG_WARN_ENUM_CONVERSION = YES;
393 | CLANG_WARN_INFINITE_RECURSION = YES;
394 | CLANG_WARN_INT_CONVERSION = YES;
395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
400 | CLANG_WARN_STRICT_PROTOTYPES = YES;
401 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
402 | CLANG_WARN_UNREACHABLE_CODE = YES;
403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
405 | COPY_PHASE_STRIP = NO;
406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
407 | ENABLE_NS_ASSERTIONS = NO;
408 | ENABLE_STRICT_OBJC_MSGSEND = YES;
409 | GCC_C_LANGUAGE_STANDARD = gnu99;
410 | GCC_NO_COMMON_BLOCKS = YES;
411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
413 | GCC_WARN_UNDECLARED_SELECTOR = YES;
414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
415 | GCC_WARN_UNUSED_FUNCTION = YES;
416 | GCC_WARN_UNUSED_VARIABLE = YES;
417 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
418 | MTL_ENABLE_DEBUG_INFO = NO;
419 | SDKROOT = iphoneos;
420 | SUPPORTED_PLATFORMS = iphoneos;
421 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
422 | TARGETED_DEVICE_FAMILY = "1,2";
423 | VALIDATE_PRODUCT = YES;
424 | };
425 | name = Release;
426 | };
427 | 97C147061CF9000F007C117D /* Debug */ = {
428 | isa = XCBuildConfiguration;
429 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
430 | buildSettings = {
431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
432 | CLANG_ENABLE_MODULES = YES;
433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
434 | ENABLE_BITCODE = NO;
435 | FRAMEWORK_SEARCH_PATHS = (
436 | "$(inherited)",
437 | "$(PROJECT_DIR)/Flutter",
438 | );
439 | INFOPLIST_FILE = Runner/Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
441 | LIBRARY_SEARCH_PATHS = (
442 | "$(inherited)",
443 | "$(PROJECT_DIR)/Flutter",
444 | );
445 | PRODUCT_BUNDLE_IDENTIFIER = com.example.shopApp;
446 | PRODUCT_NAME = "$(TARGET_NAME)";
447 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
449 | SWIFT_VERSION = 5.0;
450 | VERSIONING_SYSTEM = "apple-generic";
451 | };
452 | name = Debug;
453 | };
454 | 97C147071CF9000F007C117D /* Release */ = {
455 | isa = XCBuildConfiguration;
456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
457 | buildSettings = {
458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
459 | CLANG_ENABLE_MODULES = YES;
460 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
461 | ENABLE_BITCODE = NO;
462 | FRAMEWORK_SEARCH_PATHS = (
463 | "$(inherited)",
464 | "$(PROJECT_DIR)/Flutter",
465 | );
466 | INFOPLIST_FILE = Runner/Info.plist;
467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
468 | LIBRARY_SEARCH_PATHS = (
469 | "$(inherited)",
470 | "$(PROJECT_DIR)/Flutter",
471 | );
472 | PRODUCT_BUNDLE_IDENTIFIER = com.example.shopApp;
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
475 | SWIFT_VERSION = 5.0;
476 | VERSIONING_SYSTEM = "apple-generic";
477 | };
478 | name = Release;
479 | };
480 | /* End XCBuildConfiguration section */
481 |
482 | /* Begin XCConfigurationList section */
483 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
484 | isa = XCConfigurationList;
485 | buildConfigurations = (
486 | 97C147031CF9000F007C117D /* Debug */,
487 | 97C147041CF9000F007C117D /* Release */,
488 | 249021D3217E4FDB00AE95B9 /* Profile */,
489 | );
490 | defaultConfigurationIsVisible = 0;
491 | defaultConfigurationName = Release;
492 | };
493 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
494 | isa = XCConfigurationList;
495 | buildConfigurations = (
496 | 97C147061CF9000F007C117D /* Debug */,
497 | 97C147071CF9000F007C117D /* Release */,
498 | 249021D4217E4FDB00AE95B9 /* Profile */,
499 | );
500 | defaultConfigurationIsVisible = 0;
501 | defaultConfigurationName = Release;
502 | };
503 | /* End XCConfigurationList section */
504 | };
505 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
506 | }
507 |
--------------------------------------------------------------------------------