├── .gitignore
├── .metadata
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── fullstack_flutter_firebase_workshop
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── coffeback.jpg
├── coffee.svg
├── coffee_break.svg
├── coffelogin.jpg
├── coffeshop.jpg
├── empty.jpg
├── hangout.svg
├── hotbeverage.svg
├── logo.png
└── ng_coffee.svg
├── fonts
├── MyFlutterApp.ttf
└── Raleway-Medium.ttf
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── 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-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── Runner-Bridging-Header.h
│ └── Runner.entitlements
├── lib
├── app.dart
├── const.dart
├── enums
│ ├── enums.dart
│ └── src
│ │ ├── activity.dart
│ │ ├── additions.dart
│ │ ├── cup_size.dart
│ │ ├── order_status.dart
│ │ ├── role.dart
│ │ └── sugar_cube.dart
├── helpers
│ ├── helpers.dart
│ └── src
│ │ ├── cart_item_total.dart
│ │ ├── coffee_tabs.dart
│ │ ├── handle_background_message.dart
│ │ └── validators.dart
├── icons
│ └── my_flutter_app_icons.dart
├── main.dart
├── models
│ ├── models.dart
│ └── src
│ │ ├── cart_item.dart
│ │ ├── cart_item.g.dart
│ │ ├── coffee.dart
│ │ ├── coffee.g.dart
│ │ ├── firestore_user.dart
│ │ ├── firestore_user.g.dart
│ │ ├── order.dart
│ │ ├── order.g.dart
│ │ ├── user_log.dart
│ │ └── user_log.g.dart
├── router.dart
├── screens
│ ├── cart.dart
│ ├── coffee_item.dart
│ ├── forgot_password.dart
│ ├── home.dart
│ ├── login.dart
│ ├── login_email.dart
│ ├── logout.dart
│ ├── menu.dart
│ ├── menu_detail.dart
│ ├── menu_list.dart
│ ├── orders.dart
│ ├── profile.dart
│ ├── register.dart
│ └── splash.dart
├── services
│ ├── services.dart
│ └── src
│ │ ├── analytics.dart
│ │ ├── auth.dart
│ │ ├── firestore.dart
│ │ ├── in_app_messaging.dart
│ │ └── messaging.dart
├── theme.dart
└── widgets
│ ├── src
│ ├── alert_dialog.dart
│ ├── button.dart
│ ├── cart_badge.dart
│ ├── coffee_additions.dart
│ ├── coffee_cart_extra_info.dart
│ ├── coffee_count.dart
│ ├── coffee_size.dart
│ ├── coffee_sugar.dart
│ ├── create_account.dart
│ ├── loading_snack_bar.dart
│ ├── login_inputs.dart
│ ├── no_items.dart
│ ├── social_button.dart
│ └── total_amount.dart
│ └── widgets.dart
├── linux
├── .gitignore
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── main.cc
├── my_application.cc
└── my_application.h
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── app_icon_1024.png
│ │ ├── app_icon_128.png
│ │ ├── app_icon_16.png
│ │ ├── app_icon_256.png
│ │ ├── app_icon_32.png
│ │ ├── app_icon_512.png
│ │ └── app_icon_64.png
│ ├── Base.lproj
│ └── MainMenu.xib
│ ├── Configs
│ ├── AppInfo.xcconfig
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
├── privacy.md
├── pubspec.lock
├── pubspec.yaml
├── terms.md
├── test
├── validators_test.dart
└── widget_test.dart
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── index.html
└── manifest.json
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.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 | .fvm
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 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 | wiredbraincoffee-a1386-4485fb96c69f.json
43 | android/key.properties
44 | # firebase
45 | **/GoogleService-Info.plist
46 | **/google-services.json
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.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: 1aafb3a8b9b0c36241c5f5b34ee914770f015818
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "App Debug",
9 | "request": "launch",
10 | "flutterMode": "debug",
11 | "type": "dart"
12 | },
13 | {
14 | "name": "App Profile",
15 | "request": "launch",
16 | "flutterMode": "profile",
17 | "type": "dart"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.flutterSdkPaths": ["/Users/majid/fvm/versions", "/Users/majid/flutter"],
3 | "search.exclude": {
4 | "**/.fvm": true
5 | },
6 | "dart.flutterSdkPath": "/Users/majid/flutter",
7 | "dart.sdkPath": "/Users/majid/flutter/bin/cache/dart-sdk"
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Full-stack app with Flutter and Firebase Workshop
2 |
3 | The repository is supposed to be used for the my workshops.
4 |
5 | ## Agenda
6 |
7 | - Part 1
8 |
9 | - Introduction to Flutter & Dart
10 | - Basics of Dart (All student must be on the same page)
11 | - Basics of Flutter (All student must be comfortable with Flutter)
12 | - Stateless vs Stateful widgets
13 | - Show tips and tricks to speed up the development within a few minutes
14 | - Creating MJ Coffee app
15 | - Introduction to Flutter CLI
16 | - Scaffold the project
17 | - Add SVG support
18 | - Adding Google Fonts
19 | - Adding Navigator (go_router and why to use it)
20 | - Adding Screen
21 | - Homepage
22 | - Adding Assets
23 | - Adding Texts
24 | - Adding Buttons
25 | - Adding onTap action
26 | - Menu
27 | - Adding List
28 | - Adding onTap action to redirect to details page
29 | - Add Buy button
30 | - Tips and Tricks
31 | - Profile
32 | - Add logout button
33 |
34 | ---
35 |
36 | - Part 2
37 |
38 | - Add Firebase
39 | - Create project in Firebase
40 | - Setup Firebase for both Android and iOS, possibly (Web)
41 | - Add Firebase analytics
42 | - Add FlutterFire Core
43 | - Add FlutterFire Analytics
44 | - Explain why Analytics is important
45 | - Tips and Tricks
46 | - Securing Flutter
47 | - Adding authentication to Flutter
48 | - Adding Firebase Authentication plugin
49 | - Setup Social buttons
50 |
51 | ---
52 |
53 | - Part 3
54 |
55 | - Add FireStore
56 | - Add Cloud_FireStore plugin
57 | - Introduction to NoSQL
58 | - Basic of Cloud_FireStore
59 | - Add list of coffees to fireStore from firebase dashboard
60 | - Read List of coffees
61 | - Introduction to json_serializable
62 | - Introduction to FutureBuilder and StreamBuilder
63 | - Write to firebase
64 | - Implement buy coffee
65 | - Implement order history
66 | - Introduction to permission in FireStore
67 | - securing write and read data
68 |
69 | ---
70 |
71 | - Part 4
72 |
73 | - Cloud Functions
74 |
75 | - Write your backend code in the cloud (Serverless)
76 | - Prepare your project
77 | - Add your first function
78 | - Deploy
79 | - Integrate Cloud function with FireStore
80 | - Trigger function on certain events
81 | - Add Cloud Functions intro Flutter
82 | - call functions from Flutter
83 |
84 | - Cloud Storage
85 | - Adding Cloud Storage to Flutter
86 | - Upload photo
87 | - Read photo
88 |
89 | ---
90 |
91 | Part 5
92 |
93 | - Cloud messaging
94 | - Handling push messages
95 | - In-app messaging
96 | - handling in-app messages
97 | - Remote Config
98 | - Adding Remote config as feature flag feature
99 | - Adding crashlytics
100 | - handle crashes and bugs
101 |
102 | ---
103 |
104 | Part 6
105 |
106 | - Adding Unit tests
107 | - Introduction to Flutter Test
108 | - Stubbing and Mocking
109 | - Adding Widget Tests
110 | - Introduction to writing widgets test in Flutter
111 | - Adding Integration test
112 | - Introduction to integration_test package
113 | - Firebase Test lab
114 | - Run Test in the cloud in different devices
115 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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: 'com.google.gms.google-services'
26 | apply plugin: 'kotlin-android'
27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
28 |
29 | def keystoreProperties = new Properties()
30 | def keystorePropertiesFile = rootProject.file('key.properties')
31 | if (keystorePropertiesFile.exists()) {
32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
33 | }
34 |
35 | android {
36 | compileSdkVersion 29
37 |
38 | sourceSets {
39 | main.java.srcDirs += 'src/main/kotlin'
40 | }
41 |
42 | lintOptions {
43 | disable 'InvalidPackage'
44 | }
45 |
46 | defaultConfig {
47 | applicationId "io.wiredbrain.app"
48 | minSdkVersion 21
49 | targetSdkVersion 29
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | multiDexEnabled true
53 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
54 | }
55 | signingConfigs {
56 | release {
57 | keyAlias keystoreProperties['keyAlias']
58 | keyPassword keystoreProperties['keyPassword']
59 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
60 | storePassword keystoreProperties['storePassword']
61 | }
62 | }
63 | buildTypes {
64 | release {
65 | signingConfig signingConfigs.release
66 | }
67 | }
68 | }
69 |
70 | flutter {
71 | source '../..'
72 | }
73 |
74 | dependencies {
75 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
76 | implementation 'com.android.support:multidex:1.0.3'
77 |
78 | testImplementation 'junit:junit:4.12'
79 | androidTestImplementation 'androidx.test:runner:1.2.0'
80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
81 | }
82 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
13 |
17 |
21 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/fullstack_flutter_firebase_workshop/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.wiredbrain.app
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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:4.2.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath 'com.google.gms:google-services:4.3.8'
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | project.evaluationDependsOn(':app')
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/assets/coffeback.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/assets/coffeback.jpg
--------------------------------------------------------------------------------
/assets/coffelogin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/assets/coffelogin.jpg
--------------------------------------------------------------------------------
/assets/coffeshop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/assets/coffeshop.jpg
--------------------------------------------------------------------------------
/assets/empty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/assets/empty.jpg
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/assets/logo.png
--------------------------------------------------------------------------------
/fonts/MyFlutterApp.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/fonts/MyFlutterApp.ttf
--------------------------------------------------------------------------------
/fonts/Raleway-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/fonts/Raleway-Medium.ttf
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 | # $FirebaseAnalyticsWithoutAdIdSupport = true
7 |
8 | project 'Runner', {
9 | 'Debug' => :debug,
10 | 'Profile' => :release,
11 | 'Release' => :release,
12 | }
13 |
14 | def flutter_root
15 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
16 | unless File.exist?(generated_xcode_build_settings_path)
17 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
18 | end
19 |
20 | File.foreach(generated_xcode_build_settings_path) do |line|
21 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
22 | return matches[1].strip if matches
23 | end
24 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
25 | end
26 |
27 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
28 |
29 | flutter_ios_podfile_setup
30 |
31 | target 'Runner' do
32 | use_frameworks!
33 | use_modular_headers!
34 |
35 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
36 | end
37 |
38 | post_install do |installer|
39 | installer.pods_project.targets.each do |target|
40 | flutter_additional_ios_build_settings(target)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.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/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 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/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/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/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/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/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 | CFBundleDisplayName
8 | MJ Coffee
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | fullstack_flutter_firebase_workshop
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CFBundleURLTypes
47 |
48 |
49 | CFBundleTypeRole
50 | Editor
51 | CFBundleURLSchemes
52 |
53 | com.googleusercontent.apps.1054514175563-nkaval8ajevaut5dektbtivhuaasgsad
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.applesignin
6 |
7 | Default
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/lib/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:mjcoffee/router.dart';
4 | import 'package:mjcoffee/theme.dart';
5 |
6 | class CoffeeApp extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return MaterialApp.router(
10 | routeInformationParser: router.routeInformationParser,
11 | routerDelegate: router.routerDelegate,
12 | debugShowCheckedModeBanner: false,
13 | themeMode: ThemeMode.system,
14 | theme: getTheme(),
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/const.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4 |
5 | import 'models/models.dart';
6 |
7 | final Color darkBrown = Color(0xFFA26E47);
8 | final Color lightBrown = Color(0xFFF9E8D4);
9 | final Color brown = Color(0xFF9C5700);
10 | final Color facebookColor = Color(0xFF4867AA);
11 |
12 | class ApiPath {
13 | static String get coffees => 'Coffees';
14 | static String coffee(String id) => 'Coffees/$id';
15 | static String get users => 'Users';
16 | static String user(String uid) => 'Users/$uid';
17 | static String userCart(String uid) => 'Users/$uid/Cart';
18 | static String userCartItem(String uid, String cid) => 'Users/$uid/Cart/$cid';
19 | static String get orders => 'Orders';
20 | static String get logs => 'Logs';
21 | static String userTokens(String uid) => 'Users/$uid/Tokens';
22 | static String userToken(String uid, String tokenId) =>
23 | 'Users/$uid/Tokens/$tokenId';
24 | }
25 |
26 | final List coffees = [
27 | Coffee(
28 | id: '1',
29 | icon: FontAwesomeIcons.coffee.codePoint,
30 | name: "Espresso ",
31 | price: 8),
32 | Coffee(
33 | id: '1',
34 | icon: FontAwesomeIcons.mugHot.codePoint,
35 | name: "Cappuccino",
36 | price: 10),
37 | Coffee(
38 | id: '1',
39 | icon: FontAwesomeIcons.cocktail.codePoint,
40 | name: "Mocha",
41 | price: 12),
42 | Coffee(
43 | id: '1',
44 | icon: FontAwesomeIcons.beer.codePoint,
45 | name: "Americano",
46 | price: 7),
47 | Coffee(
48 | id: '1',
49 | icon: FontAwesomeIcons.cocktail.codePoint,
50 | name: "Italian Macchiato",
51 | price: 5,
52 | ),
53 | Coffee(
54 | id: '1',
55 | icon: FontAwesomeIcons.coffee.codePoint,
56 | name: "Flat White",
57 | price: 3),
58 | Coffee(
59 | id: '1',
60 | icon: FontAwesomeIcons.mugHot.codePoint,
61 | name: "American Affogato",
62 | price: 11,
63 | ),
64 | Coffee(
65 | id: '1',
66 | icon: FontAwesomeIcons.coffee.codePoint,
67 | name: "Long Black",
68 | price: 4),
69 | Coffee(
70 | id: '1',
71 | icon: FontAwesomeIcons.mugHot.codePoint,
72 | name: "Latte",
73 | price: 12),
74 | Coffee(
75 | id: '1',
76 | icon: FontAwesomeIcons.cocktail.codePoint,
77 | name: "American Espresso",
78 | price: 9,
79 | ),
80 | Coffee(
81 | id: '1',
82 | icon: FontAwesomeIcons.beer.codePoint,
83 | name: "CAFÈ AU LAIT.",
84 | price: 10),
85 | Coffee(
86 | id: '1',
87 | icon: FontAwesomeIcons.coffee.codePoint,
88 | name: "AFFÈ MOCHA.",
89 | price: 12),
90 | Coffee(
91 | id: '1',
92 | icon: FontAwesomeIcons.beer.codePoint,
93 | name: "Americano",
94 | price: 7),
95 | Coffee(
96 | id: '1',
97 | icon: FontAwesomeIcons.cocktail.codePoint,
98 | name: "Double Exspersso",
99 | price: 5,
100 | ),
101 | ];
102 |
--------------------------------------------------------------------------------
/lib/enums/enums.dart:
--------------------------------------------------------------------------------
1 | export 'src/activity.dart';
2 | export 'src/additions.dart';
3 | export 'src/cup_size.dart';
4 | export 'src/order_status.dart';
5 | export 'src/role.dart';
6 | export 'src/sugar_cube.dart';
7 |
--------------------------------------------------------------------------------
/lib/enums/src/activity.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | enum Activity {
5 | login,
6 | addToCart,
7 | placeOrder,
8 | logout,
9 | }
10 |
11 | extension ActivityExtension on Activity {
12 | String get name {
13 | return describeEnum(this);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/enums/src/additions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
3 | import 'package:json_annotation/json_annotation.dart';
4 |
5 | enum CoffeeAddition {
6 | cake,
7 | icecream,
8 | cheese,
9 | }
10 |
11 | extension CoffeeAdditionExtension on CoffeeAddition {
12 | IconData get iconData {
13 | switch (this) {
14 | case CoffeeAddition.cake:
15 | return Icons.cake;
16 | case CoffeeAddition.icecream:
17 | return FontAwesomeIcons.iceCream;
18 | case CoffeeAddition.cheese:
19 | return FontAwesomeIcons.cheese;
20 | default:
21 | return Icons.close;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/enums/src/cup_size.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | enum CoffeeCupSize {
4 | small,
5 | medium,
6 | large,
7 | }
8 |
9 | extension CoffeeCupSizeExtension on CoffeeCupSize {
10 | double get iconSize {
11 | switch (this) {
12 | case CoffeeCupSize.small:
13 | return 24;
14 | case CoffeeCupSize.medium:
15 | return 30;
16 | case CoffeeCupSize.large:
17 | return 36;
18 | default:
19 | return 24;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/enums/src/order_status.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4 | import 'package:json_annotation/json_annotation.dart';
5 |
6 | enum OrderStatus {
7 | pending,
8 | preparing,
9 | ready,
10 | delivered,
11 | canceled,
12 | }
13 |
14 | extension OrderStatuseExtension on OrderStatus {
15 | String get name {
16 | return describeEnum(this);
17 | }
18 |
19 | IconData get iconData {
20 | switch (this) {
21 | case OrderStatus.pending:
22 | return FontAwesomeIcons.truckLoading;
23 | case OrderStatus.preparing:
24 | return FontAwesomeIcons.clock;
25 | case OrderStatus.ready:
26 | return FontAwesomeIcons.truckPickup;
27 | case OrderStatus.delivered:
28 | return FontAwesomeIcons.userCheck;
29 | case OrderStatus.canceled:
30 | return FontAwesomeIcons.windowClose;
31 | default:
32 | return Icons.close;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/enums/src/role.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | enum UserRole {
5 | customer,
6 | admin,
7 | unknown,
8 | }
9 |
10 | extension UserRoleExtension on UserRole {
11 | String get name {
12 | return describeEnum(this);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/enums/src/sugar_cube.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | enum CoffeeSugarCube {
5 | no,
6 | one,
7 | two,
8 | }
9 |
10 | extension CoffeeSugarCubeExtension on CoffeeSugarCube {
11 | IconData get iconData {
12 | switch (this) {
13 | case CoffeeSugarCube.no:
14 | return Icons.close;
15 | case CoffeeSugarCube.one:
16 | return Icons.check_box_outline_blank;
17 | case CoffeeSugarCube.two:
18 | return Icons.check_box_outline_blank;
19 | default:
20 | return Icons.close;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/helpers/helpers.dart:
--------------------------------------------------------------------------------
1 | export 'src/cart_item_total.dart';
2 | export 'src/coffee_tabs.dart';
3 | export 'src/validators.dart';
4 |
--------------------------------------------------------------------------------
/lib/helpers/src/cart_item_total.dart:
--------------------------------------------------------------------------------
1 | num getCartItemTotal({
2 | required int count,
3 | required num price,
4 | required int additions,
5 | required int size,
6 | required int sugar,
7 | }) {
8 | return count * price + additions * 3 + size * 1 + sugar * 0.5;
9 | }
10 |
--------------------------------------------------------------------------------
/lib/helpers/src/coffee_tabs.dart:
--------------------------------------------------------------------------------
1 | abstract class CoffeeTabs {
2 | static String routeName = '';
3 | }
4 |
--------------------------------------------------------------------------------
/lib/helpers/src/handle_background_message.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_messaging/firebase_messaging.dart';
2 |
3 | Future firebaseMessagingBackgroundHandler(
4 | RemoteMessage message,
5 | ) async {
6 | // we must handle our message here!
7 | // for example to update UI or to do something in background silently
8 | print("Handling a background message: ${message.messageId}");
9 | }
10 |
--------------------------------------------------------------------------------
/lib/helpers/src/validators.dart:
--------------------------------------------------------------------------------
1 | class Validators {
2 | static String? validateEmail(String? value) {
3 | final regx = RegExp(r'^[^@]+@[^@]+\.[^@]+');
4 | if (value == null || value.isEmpty) {
5 | return 'Email can\'t be empty';
6 | } else if (!regx.hasMatch(value)) {
7 | return 'Email is not valid';
8 | }
9 | return null;
10 | }
11 |
12 | static String? validatePassword(String? value) {
13 | return value!.isEmpty ? 'Password can\'t be empty' : null;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/icons/my_flutter_app_icons.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class MyFlutterApp {
4 | MyFlutterApp._();
5 |
6 | static const _kFontFam = 'MyFlutterApp';
7 | static const _kFontPkg = null;
8 |
9 | static const IconData cup = IconData(
10 | 0xe800,
11 | fontFamily: _kFontFam,
12 | fontPackage: _kFontPkg,
13 | );
14 | static const IconData beer = IconData(
15 | 0xe801,
16 | fontFamily: _kFontFam,
17 | fontPackage: _kFontPkg,
18 | );
19 | static const IconData coffeeCup = IconData(
20 | 0xe84a,
21 | fontFamily: _kFontFam,
22 | fontPackage: _kFontPkg,
23 | );
24 | static const IconData coffeeMug = IconData(
25 | 0xe96e,
26 | fontFamily: _kFontFam,
27 | fontPackage: _kFontPkg,
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:firebase_messaging/firebase_messaging.dart';
3 | import 'package:firebase_core/firebase_core.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:mjcoffee/app.dart';
7 | import 'helpers/src/handle_background_message.dart';
8 |
9 | bool get isInDebugMode {
10 | bool inDebugMode = false;
11 | // assert(inDebugMode = true);
12 | return inDebugMode;
13 | }
14 |
15 | Future main() async {
16 | // You only need to call this method if you need the binding to be initialized before calling runApp.
17 | WidgetsFlutterBinding.ensureInitialized();
18 |
19 | runZonedGuarded>(() async {
20 | await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
21 |
22 | await Firebase.initializeApp();
23 |
24 | FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
25 |
26 | runApp(CoffeeApp());
27 |
28 | //
29 | }, (error, stackTrace) async {
30 | if (isInDebugMode) {
31 | // in development, print error and stack trace
32 | // ignore: avoid_print
33 | print('$error');
34 | // ignore: avoid_print
35 | print('$stackTrace');
36 | } else {
37 | // report to a error tracking system in production
38 | }
39 | });
40 |
41 | // You only need to call this method if you need the binding to be initialized before calling runApp.
42 | WidgetsFlutterBinding.ensureInitialized();
43 |
44 | // This captures errors reported by the Flutter framework.
45 | FlutterError.onError = (FlutterErrorDetails details) async {
46 | final dynamic exception = details.exception;
47 | final StackTrace? stackTrace = details.stack;
48 | if (isInDebugMode) {
49 | // In development mode simply print to console.
50 | FlutterError.dumpErrorToConsole(details);
51 | } else {
52 | // In production mode report to the application zone
53 | if (stackTrace != null) {
54 | Zone.current.handleUncaughtError(exception, stackTrace);
55 | }
56 | }
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/lib/models/models.dart:
--------------------------------------------------------------------------------
1 | export 'src/cart_item.dart';
2 | export 'src/coffee.dart';
3 | export 'src/firestore_user.dart';
4 | export 'src/order.dart';
5 | export 'src/user_log.dart';
6 |
--------------------------------------------------------------------------------
/lib/models/src/cart_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 | import 'package:mjcoffee/helpers/helpers.dart';
3 |
4 | import '../../enums/enums.dart';
5 | import 'coffee.dart';
6 | part 'cart_item.g.dart';
7 |
8 | @JsonSerializable(explicitToJson: true)
9 | class CartItem {
10 | CartItem({
11 | this.id,
12 | required this.coffee,
13 | required this.size,
14 | required this.quantity,
15 | required this.sugar,
16 | required this.additions,
17 | });
18 |
19 | // firebase document ID
20 | final String? id;
21 | final Coffee coffee;
22 | final CoffeeCupSize size;
23 | final CoffeeSugarCube sugar;
24 | final int quantity;
25 | final List additions;
26 |
27 | num get total => getCartItemTotal(
28 | count: quantity,
29 | price: coffee.price,
30 | additions: additions.length,
31 | size: size.index,
32 | sugar: sugar.index,
33 | );
34 |
35 | factory CartItem.fromJson(Map json) =>
36 | _$CartItemFromJson(json);
37 | Map toJson() => _$CartItemToJson(this);
38 | }
39 |
--------------------------------------------------------------------------------
/lib/models/src/cart_item.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'cart_item.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | CartItem _$CartItemFromJson(Map json) => CartItem(
10 | id: json['id'] as String?,
11 | coffee: Coffee.fromJson(json['coffee'] as Map),
12 | size: _$enumDecode(_$CoffeeCupSizeEnumMap, json['size']),
13 | quantity: json['quantity'] as int,
14 | sugar: _$enumDecode(_$CoffeeSugarCubeEnumMap, json['sugar']),
15 | additions: (json['additions'] as List)
16 | .map((e) => _$enumDecode(_$CoffeeAdditionEnumMap, e))
17 | .toList(),
18 | );
19 |
20 | Map _$CartItemToJson(CartItem instance) => {
21 | 'id': instance.id,
22 | 'coffee': instance.coffee.toJson(),
23 | 'size': _$CoffeeCupSizeEnumMap[instance.size],
24 | 'sugar': _$CoffeeSugarCubeEnumMap[instance.sugar],
25 | 'quantity': instance.quantity,
26 | 'additions':
27 | instance.additions.map((e) => _$CoffeeAdditionEnumMap[e]).toList(),
28 | };
29 |
30 | K _$enumDecode(
31 | Map enumValues,
32 | Object? source, {
33 | K? unknownValue,
34 | }) {
35 | if (source == null) {
36 | throw ArgumentError(
37 | 'A value must be provided. Supported values: '
38 | '${enumValues.values.join(', ')}',
39 | );
40 | }
41 |
42 | return enumValues.entries.singleWhere(
43 | (e) => e.value == source,
44 | orElse: () {
45 | if (unknownValue == null) {
46 | throw ArgumentError(
47 | '`$source` is not one of the supported values: '
48 | '${enumValues.values.join(', ')}',
49 | );
50 | }
51 | return MapEntry(unknownValue, enumValues.values.first);
52 | },
53 | ).key;
54 | }
55 |
56 | const _$CoffeeCupSizeEnumMap = {
57 | CoffeeCupSize.small: 'small',
58 | CoffeeCupSize.medium: 'medium',
59 | CoffeeCupSize.large: 'large',
60 | };
61 |
62 | const _$CoffeeSugarCubeEnumMap = {
63 | CoffeeSugarCube.no: 'no',
64 | CoffeeSugarCube.one: 'one',
65 | CoffeeSugarCube.two: 'two',
66 | };
67 |
68 | const _$CoffeeAdditionEnumMap = {
69 | CoffeeAddition.cake: 'cake',
70 | CoffeeAddition.icecream: 'icecream',
71 | CoffeeAddition.cheese: 'cheese',
72 | };
73 |
--------------------------------------------------------------------------------
/lib/models/src/coffee.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | part 'coffee.g.dart';
5 |
6 | @JsonSerializable()
7 | class Coffee {
8 | const Coffee({
9 | required this.icon,
10 | required this.id,
11 | required this.name,
12 | required this.price,
13 | });
14 |
15 | IconData get iconData => IconData(
16 | icon,
17 | fontFamily: 'FontAwesomeSolid',
18 | fontPackage: 'font_awesome_flutter',
19 | );
20 |
21 | final String id;
22 | final int icon;
23 | final String name;
24 | final int price;
25 |
26 | factory Coffee.fromJson(Map json) => _$CoffeeFromJson(json);
27 | Map toJson() => _$CoffeeToJson(this);
28 | }
29 |
--------------------------------------------------------------------------------
/lib/models/src/coffee.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'coffee.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Coffee _$CoffeeFromJson(Map json) => Coffee(
10 | icon: json['icon'] as int,
11 | id: json['id'] as String,
12 | name: json['name'] as String,
13 | price: json['price'] as int,
14 | );
15 |
16 | Map _$CoffeeToJson(Coffee instance) => {
17 | 'id': instance.id,
18 | 'icon': instance.icon,
19 | 'name': instance.name,
20 | 'price': instance.price,
21 | };
22 |
--------------------------------------------------------------------------------
/lib/models/src/firestore_user.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | import '../../enums/enums.dart';
4 | part 'firestore_user.g.dart';
5 |
6 | @JsonSerializable()
7 | class FirestoreUser {
8 | const FirestoreUser({
9 | required this.roles,
10 | });
11 |
12 | final List roles;
13 |
14 | bool get isCustomer => roles.contains(UserRole.customer);
15 | bool get isAdmin => roles.contains(UserRole.admin);
16 |
17 | factory FirestoreUser.fromJson(Map json) =>
18 | _$FirestoreUserFromJson(json);
19 | Map toJson() => _$FirestoreUserToJson(this);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/models/src/firestore_user.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'firestore_user.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | FirestoreUser _$FirestoreUserFromJson(Map json) =>
10 | FirestoreUser(
11 | roles: (json['roles'] as List)
12 | .map((e) => _$enumDecode(_$UserRoleEnumMap, e))
13 | .toList(),
14 | );
15 |
16 | Map _$FirestoreUserToJson(FirestoreUser instance) =>
17 | {
18 | 'roles': instance.roles.map((e) => _$UserRoleEnumMap[e]).toList(),
19 | };
20 |
21 | K _$enumDecode(
22 | Map enumValues,
23 | Object? source, {
24 | K? unknownValue,
25 | }) {
26 | if (source == null) {
27 | throw ArgumentError(
28 | 'A value must be provided. Supported values: '
29 | '${enumValues.values.join(', ')}',
30 | );
31 | }
32 |
33 | return enumValues.entries.singleWhere(
34 | (e) => e.value == source,
35 | orElse: () {
36 | if (unknownValue == null) {
37 | throw ArgumentError(
38 | '`$source` is not one of the supported values: '
39 | '${enumValues.values.join(', ')}',
40 | );
41 | }
42 | return MapEntry(unknownValue, enumValues.values.first);
43 | },
44 | ).key;
45 | }
46 |
47 | const _$UserRoleEnumMap = {
48 | UserRole.customer: 'customer',
49 | UserRole.admin: 'admin',
50 | UserRole.unknown: 'unknown',
51 | };
52 |
--------------------------------------------------------------------------------
/lib/models/src/order.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 | import 'package:mjcoffee/helpers/helpers.dart';
4 |
5 | import 'cart_item.dart';
6 | import '../../enums/enums.dart';
7 | part 'order.g.dart';
8 |
9 | @JsonSerializable(explicitToJson: true)
10 | class Order {
11 | const Order({
12 | required this.items,
13 | this.id,
14 | required this.status,
15 | required this.userId,
16 | required this.created,
17 | required this.updated,
18 | });
19 |
20 | final List items;
21 |
22 | final OrderStatus status;
23 | // order id
24 | final String? id;
25 | // user id
26 | final String userId;
27 |
28 | @JsonKey(fromJson: _fromJson, toJson: _toJson)
29 | final DateTime created;
30 |
31 | @JsonKey(fromJson: _fromJson, toJson: _toJson)
32 | final DateTime updated;
33 |
34 | bool get isReady => status == OrderStatus.ready;
35 |
36 | num get total => items
37 | .map(
38 | (item) => getCartItemTotal(
39 | count: item.quantity,
40 | price: item.coffee.price,
41 | additions: item.additions.length,
42 | size: item.size.index,
43 | sugar: item.sugar.index,
44 | ),
45 | )
46 | .reduce((value, element) => value + element);
47 |
48 | factory Order.fromJson(Map json) => _$OrderFromJson(json);
49 | Map toJson() => _$OrderToJson(this);
50 |
51 | static DateTime _fromJson(Timestamp timestamp) => timestamp.toDate();
52 | static FieldValue _toJson(DateTime time) => FieldValue.serverTimestamp();
53 | }
54 |
--------------------------------------------------------------------------------
/lib/models/src/order.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'order.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Order _$OrderFromJson(Map json) => Order(
10 | items: (json['items'] as List)
11 | .map((e) => CartItem.fromJson(e as Map))
12 | .toList(),
13 | id: json['id'] as String?,
14 | status: _$enumDecode(_$OrderStatusEnumMap, json['status']),
15 | userId: json['userId'] as String,
16 | created: Order._fromJson(json['created'] as Timestamp),
17 | updated: Order._fromJson(json['updated'] as Timestamp),
18 | );
19 |
20 | Map _$OrderToJson(Order instance) => {
21 | 'items': instance.items.map((e) => e.toJson()).toList(),
22 | 'status': _$OrderStatusEnumMap[instance.status],
23 | 'id': instance.id,
24 | 'userId': instance.userId,
25 | 'created': Order._toJson(instance.created),
26 | 'updated': Order._toJson(instance.updated),
27 | };
28 |
29 | K _$enumDecode(
30 | Map enumValues,
31 | Object? source, {
32 | K? unknownValue,
33 | }) {
34 | if (source == null) {
35 | throw ArgumentError(
36 | 'A value must be provided. Supported values: '
37 | '${enumValues.values.join(', ')}',
38 | );
39 | }
40 |
41 | return enumValues.entries.singleWhere(
42 | (e) => e.value == source,
43 | orElse: () {
44 | if (unknownValue == null) {
45 | throw ArgumentError(
46 | '`$source` is not one of the supported values: '
47 | '${enumValues.values.join(', ')}',
48 | );
49 | }
50 | return MapEntry(unknownValue, enumValues.values.first);
51 | },
52 | ).key;
53 | }
54 |
55 | const _$OrderStatusEnumMap = {
56 | OrderStatus.pending: 'pending',
57 | OrderStatus.preparing: 'preparing',
58 | OrderStatus.ready: 'ready',
59 | OrderStatus.delivered: 'delivered',
60 | OrderStatus.canceled: 'canceled',
61 | };
62 |
--------------------------------------------------------------------------------
/lib/models/src/user_log.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | import '../../enums/enums.dart';
5 | part 'user_log.g.dart';
6 |
7 | @JsonSerializable(explicitToJson: true)
8 | class UserLog {
9 | UserLog({
10 | this.id,
11 | required this.activity,
12 | required this.created,
13 | required this.userId,
14 | });
15 | final Activity activity;
16 |
17 | @JsonKey(fromJson: _fromJson, toJson: _toJson)
18 | final DateTime created;
19 |
20 | final String? id;
21 | final String userId;
22 |
23 | factory UserLog.fromJson(Map json) =>
24 | _$UserLogFromJson(json);
25 | Map toJson() => _$UserLogToJson(this);
26 |
27 | static DateTime _fromJson(Timestamp timestamp) => timestamp.toDate();
28 | static FieldValue _toJson(DateTime time) => FieldValue.serverTimestamp();
29 | }
30 |
--------------------------------------------------------------------------------
/lib/models/src/user_log.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'user_log.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | UserLog _$UserLogFromJson(Map json) => UserLog(
10 | id: json['id'] as String?,
11 | activity: _$enumDecode(_$ActivityEnumMap, json['activity']),
12 | created: UserLog._fromJson(json['created'] as Timestamp),
13 | userId: json['userId'] as String,
14 | );
15 |
16 | Map _$UserLogToJson(UserLog instance) => {
17 | 'activity': _$ActivityEnumMap[instance.activity],
18 | 'created': UserLog._toJson(instance.created),
19 | 'id': instance.id,
20 | 'userId': instance.userId,
21 | };
22 |
23 | K _$enumDecode(
24 | Map enumValues,
25 | Object? source, {
26 | K? unknownValue,
27 | }) {
28 | if (source == null) {
29 | throw ArgumentError(
30 | 'A value must be provided. Supported values: '
31 | '${enumValues.values.join(', ')}',
32 | );
33 | }
34 |
35 | return enumValues.entries.singleWhere(
36 | (e) => e.value == source,
37 | orElse: () {
38 | if (unknownValue == null) {
39 | throw ArgumentError(
40 | '`$source` is not one of the supported values: '
41 | '${enumValues.values.join(', ')}',
42 | );
43 | }
44 | return MapEntry(unknownValue, enumValues.values.first);
45 | },
46 | ).key;
47 | }
48 |
49 | const _$ActivityEnumMap = {
50 | Activity.login: 'login',
51 | Activity.addToCart: 'addToCart',
52 | Activity.placeOrder: 'placeOrder',
53 | Activity.logout: 'logout',
54 | };
55 |
--------------------------------------------------------------------------------
/lib/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:go_router/go_router.dart';
3 | import 'package:mjcoffee/screens/cart.dart';
4 | import 'package:mjcoffee/screens/forgot_password.dart';
5 | import 'package:mjcoffee/screens/login_email.dart';
6 | import 'package:mjcoffee/screens/profile.dart';
7 | import 'package:mjcoffee/screens/register.dart';
8 | import 'package:mjcoffee/services/src/analytics.dart';
9 |
10 | import 'const.dart';
11 | import 'models/models.dart';
12 | import 'screens/home.dart';
13 | import 'screens/login.dart';
14 | import 'screens/menu.dart';
15 | import 'screens/menu_detail.dart';
16 | import 'screens/splash.dart';
17 | import 'services/src/auth.dart';
18 |
19 | class LoginInfo extends ChangeNotifier {
20 | var _isLoggedIn = AuthService.instance.currentUser != null;
21 |
22 | bool get isLoggedIn => _isLoggedIn;
23 |
24 | set isLoggedIn(bool value) {
25 | _isLoggedIn = value;
26 | notifyListeners();
27 | }
28 | }
29 |
30 | final loginInfo = LoginInfo();
31 |
32 | Coffee _coffeeFrom(String s) {
33 | return coffees.where((coffee) => coffee.id.toString() == s).first;
34 | }
35 |
36 | final router = GoRouter(
37 | // redirect: (GoRouterState state) {
38 | // final loggedIn = loginInfo.isLoggedIn;
39 | // final isLogging = state.location == '/login';
40 | // if (!loggedIn && !isLogging) return '/login';
41 | // if (loggedIn && isLogging) return '/';
42 | // return null;
43 | // },
44 | observers: [AnalyticsService.observer],
45 | refreshListenable: loginInfo,
46 | urlPathStrategy: UrlPathStrategy.path,
47 | debugLogDiagnostics: false,
48 | initialLocation: '/splash',
49 | routes: [
50 | GoRoute(
51 | name: 'spalsh',
52 | path: '/splash',
53 | pageBuilder: (context, state) => MaterialPage(
54 | key: state.pageKey,
55 | child: const SplashScreen(),
56 | ),
57 | ),
58 | GoRoute(
59 | name: 'home',
60 | path: '/',
61 | pageBuilder: (context, state) => MaterialPage(
62 | key: state.pageKey,
63 | child: const HomeScreen(),
64 | ),
65 | ),
66 | GoRoute(
67 | name: 'register',
68 | path: '/register',
69 | pageBuilder: (context, state) => MaterialPage(
70 | key: state.pageKey,
71 | child: RegisterScreen(),
72 | ),
73 | ),
74 | GoRoute(
75 | name: 'login_password',
76 | path: '/login_password',
77 | pageBuilder: (context, state) => MaterialPage(
78 | key: state.pageKey,
79 | child: const LoginEmailScreen(),
80 | ),
81 | ),
82 | GoRoute(
83 | name: 'cart',
84 | path: '/cart',
85 | pageBuilder: (context, state) => MaterialPage(
86 | key: state.pageKey,
87 | child: CartScreen(),
88 | ),
89 | ),
90 | GoRoute(
91 | name: 'forgot_password',
92 | path: '/forgot_password',
93 | pageBuilder: (context, state) => MaterialPage(
94 | key: state.pageKey,
95 | child: ForgotPasswordScreen(),
96 | ),
97 | ),
98 | GoRoute(
99 | name: 'profile',
100 | path: '/profile',
101 | pageBuilder: (context, state) => MaterialPage(
102 | key: state.pageKey,
103 | child: ProfileScreen(),
104 | ),
105 | ),
106 | GoRoute(
107 | name: 'login',
108 | path: '/login',
109 | pageBuilder: (context, state) {
110 | final scaffoldKey = state.extra;
111 | return MaterialPage(
112 | key: state.pageKey,
113 | child: LoginScreen(
114 | scaffoldKey: scaffoldKey,
115 | ),
116 | );
117 | },
118 | ),
119 | GoRoute(
120 | name: 'menu',
121 | path: '/menu',
122 | pageBuilder: (context, state) => MaterialPage(
123 | key: state.pageKey,
124 | child: const MenuScreen(),
125 | ),
126 | routes: [
127 | GoRoute(
128 | name: 'details',
129 | path: ':id', // e.g. /menu/1002
130 | pageBuilder: (context, state) {
131 | final coffee = state.params['id']!;
132 | return MaterialPage(
133 | key: state.pageKey,
134 | child: MenuDetails(id: coffee),
135 | );
136 | },
137 | ),
138 | ],
139 | ),
140 | ],
141 | errorPageBuilder: (context, state) => MaterialPage(
142 | key: state.pageKey,
143 | child: Scaffold(
144 | body: Center(
145 | child: Text(state.error.toString()),
146 | ),
147 | ),
148 | ),
149 | );
150 |
--------------------------------------------------------------------------------
/lib/screens/coffee_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:go_router/go_router.dart';
3 | import 'package:mjcoffee/const.dart';
4 | import 'package:mjcoffee/models/models.dart';
5 | import 'package:mjcoffee/services/services.dart';
6 |
7 | class CoffeeItem extends StatelessWidget {
8 | CoffeeItem({
9 | required this.animation,
10 | required this.coffee,
11 | });
12 |
13 | final FirestoreService _firestoreService = FirestoreService.instance;
14 |
15 | final Coffee coffee;
16 | final Animation animation;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StreamBuilder(
21 | stream: _firestoreService.getCoffee(coffee.id),
22 | builder: (context, AsyncSnapshot snapshot) {
23 | if (snapshot.hasError) {
24 | print(snapshot.error);
25 | return SizedBox();
26 | }
27 |
28 | Coffee newCoffee = coffee;
29 |
30 | if (snapshot.hasData) {
31 | newCoffee = snapshot.data!;
32 | }
33 |
34 | return SlideTransition(
35 | position: Tween(
36 | begin: const Offset(-1, 0),
37 | end: Offset(0, 0),
38 | ).animate(
39 | CurvedAnimation(
40 | parent: animation,
41 | curve: Curves.bounceIn,
42 | reverseCurve: Curves.bounceOut,
43 | ),
44 | ),
45 | child: GestureDetector(
46 | child: ListTile(
47 | contentPadding: EdgeInsets.all(15),
48 | title: Text(
49 | newCoffee.name,
50 | style: Theme.of(context).textTheme.headline5,
51 | ),
52 | subtitle: Text(
53 | newCoffee.name,
54 | style: Theme.of(context).textTheme.subtitle2,
55 | ),
56 | leading: Icon(
57 | newCoffee.iconData,
58 | size: 40,
59 | color: brown,
60 | ),
61 | trailing: Icon(Icons.keyboard_arrow_right),
62 | ),
63 | onTap: () {
64 | GoRouter.of(context).goNamed(
65 | 'details',
66 | params: {'id': coffee.id},
67 | );
68 | },
69 | ),
70 | );
71 | },
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/screens/forgot_password.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:go_router/go_router.dart';
4 | import 'package:mjcoffee/helpers/helpers.dart';
5 | import 'package:mjcoffee/services/services.dart';
6 | import 'package:mjcoffee/widgets/widgets.dart';
7 |
8 | import '../const.dart';
9 |
10 | class ForgotPasswordScreen extends StatefulWidget {
11 | static String routeName = 'ForgotPasswordScreen';
12 | static Route route() {
13 | return MaterialPageRoute(
14 | settings: RouteSettings(name: routeName),
15 | builder: (BuildContext context) => ForgotPasswordScreen(),
16 | );
17 | }
18 |
19 | @override
20 | _ForgotPasswordScreenState createState() => _ForgotPasswordScreenState();
21 | }
22 |
23 | class _ForgotPasswordScreenState extends State {
24 | final formKey = GlobalKey();
25 | final _emailFieldController = TextEditingController();
26 |
27 | final AuthService _authService = AuthService.instance;
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return Scaffold(
32 | appBar: AppBar(
33 | title: Text("Forgot Password"),
34 | actions: [
35 | Image.asset(
36 | "assets/logo.png",
37 | semanticLabel: 'logo',
38 | fit: BoxFit.fitWidth,
39 | ),
40 | ],
41 | ),
42 | body: SafeArea(
43 | child: Padding(
44 | padding: const EdgeInsets.symmetric(horizontal: 32),
45 | child: Form(
46 | key: formKey,
47 | child: Column(
48 | mainAxisAlignment: MainAxisAlignment.spaceAround,
49 | children: [
50 | TextFormField(
51 | key: Key('email'),
52 | controller: _emailFieldController,
53 | decoration: InputDecoration(
54 | labelText: 'Username',
55 | hintText: 'me@majidhajian.com',
56 | labelStyle: TextStyle(color: darkBrown),
57 | enabledBorder: UnderlineInputBorder(
58 | borderSide: BorderSide(
59 | color: Colors.grey.shade400,
60 | ),
61 | ),
62 | focusedBorder: UnderlineInputBorder(
63 | borderSide: BorderSide(color: darkBrown),
64 | ),
65 | border: UnderlineInputBorder(
66 | borderSide: BorderSide(color: darkBrown),
67 | ),
68 | ),
69 | cursorColor: darkBrown,
70 | validator: Validators.validateEmail,
71 | ),
72 | CommonButton(
73 | onPressed: _onSubmitLoginButton,
74 | text: 'Submit',
75 | ),
76 | ],
77 | ),
78 | ),
79 | ),
80 | ),
81 | );
82 | }
83 |
84 | bool _isFormValidated() {
85 | final FormState form = formKey.currentState!;
86 | return form.validate();
87 | }
88 |
89 | _onSubmitLoginButton() async {
90 | if (_isFormValidated()) {
91 | ScaffoldMessenger.of(context).showSnackBar(
92 | loadingSnackBar(
93 | text: " Wait please...",
94 | ),
95 | );
96 |
97 | await _authService.sendPasswordResetEmail(
98 | email: _emailFieldController.text,
99 | );
100 |
101 | ScaffoldMessenger.of(context).hideCurrentSnackBar();
102 |
103 | final snackBar = SnackBar(
104 | backgroundColor: Colors.red,
105 | content: Text('Please check your email!'),
106 | );
107 |
108 | ScaffoldMessenger.of(context).showSnackBar(snackBar);
109 |
110 | GoRouter.of(context).goNamed('home');
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/screens/home.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 | import 'package:go_router/go_router.dart';
4 | import '../const.dart';
5 |
6 | class HomeScreen extends StatefulWidget {
7 | static String routeName = 'homeScreen';
8 |
9 | const HomeScreen({Key? key}) : super(key: key);
10 |
11 | static Route route() {
12 | return MaterialPageRoute(
13 | settings: RouteSettings(name: routeName),
14 | builder: (BuildContext context) => const HomeScreen(),
15 | );
16 | }
17 |
18 | @override
19 | _HomeScreenState createState() => _HomeScreenState();
20 | }
21 |
22 | class _HomeScreenState extends State {
23 | final loginScaffoldKey = GlobalKey();
24 | @override
25 | Widget build(BuildContext context) {
26 | return Scaffold(
27 | body: SafeArea(
28 | child: Column(
29 | mainAxisAlignment: MainAxisAlignment.spaceAround,
30 | children: [
31 | Image.asset(
32 | "assets/logo.png",
33 | height: 150,
34 | width: 150,
35 | ),
36 | SvgPicture.asset(
37 | "assets/ng_coffee.svg",
38 | height: MediaQuery.of(context).size.height / 3,
39 | width: MediaQuery.of(context).size.width,
40 | semanticsLabel: 'Wire Brain Coffee',
41 | fit: BoxFit.fitWidth,
42 | ),
43 | const Text(
44 | "Get the best coffee!",
45 | style: TextStyle(
46 | fontFamily: 'Raleway',
47 | fontSize: 30,
48 | fontWeight: FontWeight.w400,
49 | color: Colors.brown),
50 | textAlign: TextAlign.center,
51 | ),
52 | Row(
53 | mainAxisAlignment: MainAxisAlignment.spaceAround,
54 | children: [
55 | ClipRRect(
56 | borderRadius: BorderRadius.circular(25),
57 | child: TextButton(
58 | style: ButtonStyle(
59 | padding: MaterialStateProperty.all(
60 | const EdgeInsets.fromLTRB(55, 15, 55, 15),
61 | ),
62 | backgroundColor: MaterialStateProperty.all(darkBrown),
63 | ),
64 | onPressed: () {
65 | GoRouter.of(context).goNamed('register');
66 | },
67 | child: const Text(
68 | "Register",
69 | style: TextStyle(color: Colors.white),
70 | ),
71 | )),
72 | OutlinedButton(
73 | key: const Key('homeLoginButton'),
74 | style: ButtonStyle(
75 | padding: MaterialStateProperty.all(
76 | const EdgeInsets.fromLTRB(60, 15, 60, 15),
77 | ),
78 | shape: MaterialStateProperty.all(const StadiumBorder()),
79 | side: MaterialStateProperty.all(
80 | BorderSide(color: darkBrown),
81 | ),
82 | ),
83 | onPressed: () {
84 | GoRouter.of(context).goNamed(
85 | 'login',
86 | extra: loginScaffoldKey,
87 | );
88 | },
89 | child: Text(
90 | "Log In",
91 | style: TextStyle(color: darkBrown),
92 | ),
93 | ),
94 | ],
95 | ),
96 | ],
97 | ),
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/screens/login_email.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_auth/firebase_auth.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/flutter_svg.dart';
5 | import 'package:go_router/go_router.dart';
6 | import 'package:mjcoffee/const.dart';
7 | import 'package:mjcoffee/enums/enums.dart';
8 | import 'package:mjcoffee/services/services.dart';
9 | import 'package:mjcoffee/widgets/widgets.dart';
10 |
11 | class LoginEmailScreen extends StatefulWidget {
12 | const LoginEmailScreen({Key? key}) : super(key: key);
13 |
14 | @override
15 | _LoginEmailScreenState createState() => _LoginEmailScreenState();
16 | }
17 |
18 | class _LoginEmailScreenState extends State {
19 | final formKey = GlobalKey();
20 | final _emailFieldController = TextEditingController();
21 | final _passwordFieldController = TextEditingController();
22 |
23 | final AnalyticsService _analyticsService = AnalyticsService.instance;
24 | final AuthService _authService = AuthService.instance;
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Scaffold(
29 | resizeToAvoidBottomInset: false,
30 | appBar: AppBar(
31 | title: Text("Login"),
32 | actions: [
33 | Image.asset(
34 | "assets/logo.png",
35 | semanticLabel: 'logo',
36 | fit: BoxFit.fitWidth,
37 | ),
38 | ],
39 | ),
40 | body: SafeArea(
41 | child: Padding(
42 | padding: const EdgeInsets.symmetric(horizontal: 32),
43 | child: Form(
44 | key: formKey,
45 | child: Column(
46 | mainAxisAlignment: MainAxisAlignment.spaceAround,
47 | children: [
48 | Center(
49 | child: SvgPicture.asset(
50 | "assets/hotbeverage.svg",
51 | height: MediaQuery.of(context).size.height / 3,
52 | width: MediaQuery.of(context).size.width,
53 | semanticsLabel: 'MJ Coffee',
54 | fit: BoxFit.fitWidth,
55 | ),
56 | ),
57 | LoginInputs(
58 | emailFieldController: _emailFieldController,
59 | passwordFieldController: _passwordFieldController,
60 | ),
61 | Row(
62 | mainAxisAlignment: MainAxisAlignment.end,
63 | children: [
64 | TextButton(
65 | onPressed: () {
66 | GoRouter.of(context).goNamed('forgot_password');
67 | },
68 | child: Text(
69 | "Forgot password?",
70 | style: TextStyle(
71 | color: darkBrown,
72 | fontWeight: FontWeight.w500,
73 | ),
74 | ),
75 | ),
76 | ],
77 | ),
78 | CommonButton(
79 | onPressed: _onSubmitLoginButton,
80 | text: 'login',
81 | ),
82 | CreateAccount(),
83 | ],
84 | ),
85 | ),
86 | ),
87 | ),
88 | );
89 | }
90 |
91 | bool _isFormValidated() {
92 | final FormState form = formKey.currentState!;
93 | return form.validate();
94 | }
95 |
96 | _onSubmitLoginButton() async {
97 | if (_isFormValidated()) {
98 | ScaffoldMessenger.of(context).showSnackBar(
99 | loadingSnackBar(
100 | text: " Signing-In...",
101 | ),
102 | );
103 |
104 | final User? user = await _authService.signInWithEmailAndPassword(
105 | email: _emailFieldController.text,
106 | password: _passwordFieldController.text,
107 | );
108 |
109 | ScaffoldMessenger.of(context).hideCurrentSnackBar();
110 |
111 | if (user != null) {
112 | _analyticsService.logLogin();
113 |
114 | _analyticsService.setUserProperties(
115 | userId: user.uid,
116 | userRoles: [UserRole.customer],
117 | );
118 |
119 | GoRouter.of(context).goNamed('menu');
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/screens/logout.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_svg/flutter_svg.dart';
4 | import 'package:go_router/go_router.dart';
5 | import 'package:mjcoffee/services/services.dart';
6 |
7 | class LogoutScreen extends StatefulWidget {
8 | static String routeName = 'LogoutScreen';
9 |
10 | const LogoutScreen({Key? key}) : super(key: key);
11 |
12 | static Route route() {
13 | return MaterialPageRoute(
14 | settings: RouteSettings(name: routeName),
15 | builder: (BuildContext context) => const LogoutScreen(),
16 | );
17 | }
18 |
19 | @override
20 | _LogoutScreenState createState() => _LogoutScreenState();
21 | }
22 |
23 | class _LogoutScreenState extends State {
24 | @override
25 | Widget build(BuildContext context) {
26 | return Column(
27 | mainAxisAlignment: MainAxisAlignment.spaceAround,
28 | children: [
29 | Center(
30 | child: Column(
31 | children: [
32 | SvgPicture.asset(
33 | "assets/coffee_break.svg",
34 | height: MediaQuery.of(context).size.height / 3,
35 | width: MediaQuery.of(context).size.width,
36 | semanticsLabel: 'Wire Brain Coffee',
37 | fit: BoxFit.fitWidth,
38 | ),
39 | ],
40 | ),
41 | ),
42 | Padding(
43 | padding: const EdgeInsets.symmetric(horizontal: 30),
44 | child: OutlinedButton(
45 | onPressed: () {
46 | GoRouter.of(context).goNamed('home');
47 | },
48 | child: const Text('Logout'),
49 | ),
50 | ),
51 | ],
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/screens/menu.dart:
--------------------------------------------------------------------------------
1 | // ignore: import_of_legacy_library_into_null_safe
2 | import 'package:firebase_analytics/observer.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:go_router/go_router.dart';
5 | import 'package:mjcoffee/screens/cart.dart';
6 | import 'package:mjcoffee/screens/menu_list.dart';
7 | import 'package:mjcoffee/screens/orders.dart';
8 | import 'package:mjcoffee/screens/profile.dart';
9 | import 'package:mjcoffee/services/services.dart';
10 | import 'package:mjcoffee/widgets/widgets.dart';
11 |
12 | class MenuScreen extends StatefulWidget {
13 | const MenuScreen({Key? key}) : super(key: key);
14 |
15 | @override
16 | _MenuScreenState createState() => _MenuScreenState();
17 | }
18 |
19 | class _MenuScreenState extends State {
20 | int _selectedIndex = 0;
21 |
22 | final List tabs = [
23 | MenuList(),
24 | CartScreen(),
25 | OrdersScreen(),
26 | ProfileScreen(),
27 | ];
28 |
29 | final FirebaseAnalyticsObserver observer = AnalyticsService.observer;
30 | final AuthService _authService = AuthService.instance;
31 | final FirestoreService _firestoreService = FirestoreService.instance;
32 | final MessagingService _messagingService = MessagingService.instance;
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 | setupMessaging();
38 | }
39 |
40 | void _sendCurrentTabToAnalytics() {
41 | final String screeName =
42 | '${GoRouter.of(context).location}/${_selectedIndex}';
43 | observer.analytics.setCurrentScreen(
44 | screenName: screeName,
45 | );
46 | }
47 |
48 | void _onItemTapped(int index) {
49 | setState(() {
50 | _selectedIndex = index;
51 | });
52 | _sendCurrentTabToAnalytics();
53 | }
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | return Scaffold(
58 | backgroundColor: Colors.white,
59 | appBar: AppBar(
60 | title: Text("Welcome"),
61 | centerTitle: false,
62 | actions: [
63 | CartBadge(
64 | top: 8,
65 | right: 10,
66 | child: IconButton(
67 | icon: Icon(Icons.shopping_cart),
68 | onPressed: () {
69 | setState(() {
70 | _selectedIndex = 1;
71 | });
72 | },
73 | ),
74 | ),
75 | ],
76 | ),
77 | body: tabs[_selectedIndex],
78 | bottomNavigationBar: BottomNavigationBar(
79 | backgroundColor: Colors.white,
80 | type: BottomNavigationBarType.fixed,
81 | unselectedItemColor: Colors.brown.shade300,
82 | items: [
83 | const BottomNavigationBarItem(
84 | icon: Icon(Icons.home),
85 | label: "Menu",
86 | ),
87 | const BottomNavigationBarItem(
88 | icon: Icon(Icons.shopping_cart),
89 | label: "Cart",
90 | ),
91 | const BottomNavigationBarItem(
92 | icon: Icon(Icons.list),
93 | label: "Orders",
94 | ),
95 | BottomNavigationBarItem(
96 | icon: Icon(Icons.person),
97 | label: "Profile",
98 | ),
99 | ],
100 | currentIndex: _selectedIndex,
101 | selectedItemColor: Colors.brown.shade800,
102 | onTap: _onItemTapped,
103 | ),
104 | );
105 | }
106 |
107 | Future setupMessaging() async {
108 | Future.delayed(
109 | const Duration(seconds: 2),
110 | () async {
111 | await _messagingService.initialize();
112 | if (_messagingService.userDeviceToken != null) {
113 | await _firestoreService.registerUserToken(
114 | token: _messagingService.userDeviceToken,
115 | userId: _authService.currentUser!.uid,
116 | );
117 | }
118 | },
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/screens/menu_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/services/services.dart';
3 |
4 | import '../models/models.dart';
5 | import 'coffee_item.dart';
6 |
7 | class MenuList extends StatefulWidget {
8 | static String routeName = 'MenuList';
9 |
10 | @override
11 | _MenuListState createState() => _MenuListState();
12 | }
13 |
14 | class _MenuListState extends State {
15 | final GlobalKey listKey = GlobalKey();
16 | final FirestoreService _firestoreService = FirestoreService.instance;
17 | List _items = [];
18 |
19 | @override
20 | void initState() {
21 | super.initState();
22 |
23 | _loadItems();
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return AnimatedList(
29 | key: listKey,
30 | initialItemCount: _items.length,
31 | itemBuilder: (context, index, Animation animation) {
32 | return CoffeeItem(
33 | coffee: _items[index],
34 | animation: animation,
35 | );
36 | },
37 | );
38 | }
39 |
40 | Future _loadItems() async {
41 | final coffees = await _firestoreService.getCoffees().first;
42 |
43 | for (Coffee item in coffees) {
44 | await Future.delayed(Duration(milliseconds: 80));
45 | _items.add(item);
46 | if (listKey.currentState != null) {
47 | listKey.currentState!.insertItem(_items.length - 1);
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/screens/orders.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/enums/enums.dart';
3 | import 'package:mjcoffee/models/models.dart';
4 | import 'package:mjcoffee/services/services.dart';
5 | import 'package:mjcoffee/widgets/widgets.dart';
6 |
7 | import '../const.dart';
8 |
9 | class OrdersScreen extends StatelessWidget {
10 | static String routeName = 'Orders';
11 |
12 | final FirestoreService _firestoreService = FirestoreService.instance;
13 | final AuthService _authService = AuthService.instance;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return StreamBuilder>(
18 | stream: _firestoreService.getUserOrders(_authService.currentUser!.uid),
19 | builder: (context, AsyncSnapshot> snapshot) {
20 | if (snapshot.hasError) {
21 | return NoItems(title: 'No Orders!');
22 | }
23 | if (snapshot.connectionState == ConnectionState.active) {
24 | if (snapshot.hasData) {
25 | final orders = snapshot.data!;
26 |
27 | if (orders.isEmpty) {
28 | return NoItems(title: 'No Orders!');
29 | }
30 | return ListView.builder(
31 | itemCount: orders.length,
32 | itemBuilder: (context, index) {
33 | final order = orders[index];
34 |
35 | return ExpansionTile(
36 | tilePadding: EdgeInsets.all(15),
37 | leading: Icon(order.status.iconData, color: brown),
38 | title: Text(
39 | order.status.name.toUpperCase(),
40 | style: Theme.of(context).textTheme.headline4,
41 | ),
42 | subtitle: Column(
43 | crossAxisAlignment: CrossAxisAlignment.start,
44 | children: [
45 | Text('Order Id: ${order.id ?? ''}'),
46 | Text('Updated: ${order.updated}'),
47 | ],
48 | ),
49 | expandedAlignment: Alignment.centerLeft,
50 | childrenPadding: const EdgeInsets.all(15),
51 | initiallyExpanded: false,
52 | children: [
53 | ...order.items
54 | .map(
55 | (item) => CoffeeCartExtraInfo(item: item),
56 | )
57 | .toList(),
58 | TotalAmount(cartTotal: order.total),
59 | ],
60 | );
61 | },
62 | );
63 | }
64 | }
65 | return Center(
66 | child: CircularProgressIndicator(),
67 | );
68 | },
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/screens/register.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_auth/firebase_auth.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:go_router/go_router.dart';
5 | import 'package:mjcoffee/enums/enums.dart';
6 | import 'package:mjcoffee/services/services.dart';
7 | import 'package:mjcoffee/widgets/widgets.dart';
8 |
9 | class RegisterScreen extends StatelessWidget {
10 | final formKey = GlobalKey();
11 | final _emailFieldController = TextEditingController();
12 | final _passwordFieldController = TextEditingController();
13 |
14 | final AnalyticsService _analyticsService = AnalyticsService.instance;
15 | final AuthService _authService = AuthService.instance;
16 | final FirestoreService _firestoreService = FirestoreService.instance;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: AppBar(
22 | title: Text("Register"),
23 | actions: [
24 | Image.asset(
25 | "assets/logo.png",
26 | semanticLabel: 'logo',
27 | fit: BoxFit.fitWidth,
28 | ),
29 | ],
30 | ),
31 | body: SafeArea(
32 | child: Padding(
33 | padding: const EdgeInsets.symmetric(horizontal: 32),
34 | child: Form(
35 | key: formKey,
36 | child: Column(
37 | mainAxisAlignment: MainAxisAlignment.spaceAround,
38 | children: [
39 | LoginInputs(
40 | emailFieldController: _emailFieldController,
41 | passwordFieldController: _passwordFieldController,
42 | ),
43 | CommonButton(
44 | onPressed: () {
45 | _onSubmitLoginButton(context);
46 | },
47 | text: 'Register',
48 | ),
49 | ],
50 | ),
51 | ),
52 | ),
53 | ),
54 | );
55 | }
56 |
57 | bool _isFormValidated() {
58 | final FormState form = formKey.currentState!;
59 | return form.validate();
60 | }
61 |
62 | _onSubmitLoginButton(context) async {
63 | if (_isFormValidated()) {
64 | ScaffoldMessenger.of(context).showSnackBar(
65 | loadingSnackBar(
66 | text: " Creating user...",
67 | ),
68 | );
69 |
70 | final User? user = await _authService.createUserWithEmailAndPassword(
71 | email: _emailFieldController.text,
72 | password: _passwordFieldController.text,
73 | );
74 |
75 | ScaffoldMessenger.of(context).hideCurrentSnackBar();
76 |
77 | if (user != null) {
78 | _analyticsService.logLogin();
79 |
80 | _analyticsService.setUserProperties(
81 | userId: user.uid,
82 | userRoles: [UserRole.customer],
83 | );
84 |
85 | await _firestoreService.setUserRoles(user.uid, [UserRole.customer]);
86 |
87 | GoRouter.of(context).goNamed('menu');
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/screens/splash.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_spinkit/flutter_spinkit.dart';
3 | import 'package:go_router/go_router.dart';
4 |
5 | class SplashScreen extends StatefulWidget {
6 | const SplashScreen({Key? key}) : super(key: key);
7 |
8 | @override
9 | _SplashScreenState createState() => _SplashScreenState();
10 | }
11 |
12 | class _SplashScreenState extends State {
13 | @override
14 | Widget build(BuildContext context) {
15 | return Container(
16 | decoration: const BoxDecoration(
17 | image: DecorationImage(
18 | image: AssetImage("assets/coffeback.jpg"),
19 | fit: BoxFit.cover,
20 | ),
21 | ),
22 | child: const Padding(
23 | padding: EdgeInsets.only(top: 600),
24 | child: SpinKitFadingCircle(
25 | color: Colors.white,
26 | size: 70.0,
27 | ),
28 | ),
29 | );
30 | }
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | Future.delayed(
36 | const Duration(milliseconds: 2000),
37 | () {
38 | context.goNamed('home');
39 | },
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/services/services.dart:
--------------------------------------------------------------------------------
1 | export 'src/analytics.dart';
2 | export 'src/auth.dart';
3 | export 'src/firestore.dart';
4 | export 'src/messaging.dart';
5 | export 'src/in_app_messaging.dart';
6 |
--------------------------------------------------------------------------------
/lib/services/src/analytics.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_analytics/firebase_analytics.dart';
2 | import 'package:firebase_analytics/observer.dart';
3 | import '../../enums/enums.dart';
4 |
5 | class AnalyticsService {
6 | final FirebaseAnalytics _analytics = FirebaseAnalytics();
7 |
8 | // Singleton setup: prevents multiple instances of this class.
9 | factory AnalyticsService() => _service;
10 | AnalyticsService._();
11 | static final AnalyticsService _service = AnalyticsService._();
12 |
13 | static AnalyticsService get instance => _service;
14 |
15 | static FirebaseAnalyticsObserver get observer =>
16 | FirebaseAnalyticsObserver(analytics: _service._analytics);
17 |
18 | Future logLogin({loginMethod = 'email'}) async {
19 | return _analytics.logLogin(loginMethod: loginMethod);
20 | }
21 |
22 | Future logAddToCart({
23 | required String itemId,
24 | required String itemName,
25 | required String itemCategory,
26 | required int quantity,
27 | }) async {
28 | return _analytics.logAddToCart(
29 | itemId: itemId,
30 | itemName: itemName,
31 | itemCategory: itemCategory,
32 | quantity: quantity,
33 | );
34 | }
35 |
36 | Future logPlaceOrder({
37 | required String orderId,
38 | required num total,
39 | required List coffees,
40 | required int quantity,
41 | }) async {
42 | return _analytics.logEvent(
43 | name: 'place_order',
44 | parameters: {
45 | 'quantity': quantity,
46 | 'total': total,
47 | 'coffees': coffees.toString(),
48 | 'orderId': orderId,
49 | },
50 | );
51 | }
52 |
53 | Future setUserProperties({
54 | required String userId,
55 | required List userRoles,
56 | }) async {
57 | await _analytics.setUserId(userId);
58 | await _analytics.setUserProperty(
59 | name: 'user_role', // custom userProperty
60 | value:
61 | userRoles.contains(UserRole.customer) ? "customer" : 'adminOrUnknown',
62 | );
63 | }
64 |
65 | Future logLogoutPressed({
66 | bool isBasketEmpty = true,
67 | }) async {
68 | print('logout_pressed');
69 | return _analytics.logEvent(
70 | name: 'logout_pressed',
71 | parameters: {'is_basket_empty': isBasketEmpty},
72 | );
73 | }
74 |
75 | Future logRemoveItem({
76 | required String itemId,
77 | required String itemName,
78 | }) async {
79 | return _analytics.logEvent(
80 | name: 'remove_and_empty_basket',
81 | parameters: {
82 | 'itemId': itemId,
83 | 'itemName': itemName,
84 | },
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/services/src/in_app_messaging.dart:
--------------------------------------------------------------------------------
1 | // ignore: import_of_legacy_library_into_null_safe
2 | import 'package:firebase_in_app_messaging/firebase_in_app_messaging.dart';
3 |
4 | class InAppMessagingService {
5 | final _firebaseInAppMessaging = FirebaseInAppMessaging.instance;
6 |
7 | // Singleton setup: prevents multiple instances of this class.
8 | InAppMessagingService._();
9 | static final InAppMessagingService _service = InAppMessagingService._();
10 | factory InAppMessagingService() => _service;
11 |
12 | static InAppMessagingService get instance => _service;
13 |
14 | // triger an Analytics events
15 | triggerEvent(String event) {
16 | _firebaseInAppMessaging.triggerEvent(event);
17 | }
18 |
19 | /// Enables or disables suppression of message displays.
20 | Future setMessagesSuppressed(bool suppress) async {
21 | return _firebaseInAppMessaging.setMessagesSuppressed(suppress);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/services/src/messaging.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_messaging/firebase_messaging.dart';
2 | import 'package:mjcoffee/widgets/widgets.dart';
3 |
4 | class MessagingService {
5 | final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
6 |
7 | // Singleton setup: prevents multiple instances of this class.
8 | MessagingService._();
9 | static final MessagingService _service = MessagingService._();
10 | factory MessagingService() => _service;
11 |
12 | static MessagingService get instance => _service;
13 |
14 | Future initialize() async {
15 | final NotificationSettings settings = await _requestPermission();
16 |
17 | if (settings.authorizationStatus == AuthorizationStatus.authorized) {
18 | await _getToken();
19 | _firebaseMessagingForgroundHandler();
20 | }
21 | }
22 |
23 | Future subscribeToTopic(String topic) {
24 | return _firebaseMessaging.subscribeToTopic(topic);
25 | }
26 |
27 | Future unsubscribeFromTopic(String topic) {
28 | return _firebaseMessaging.unsubscribeFromTopic(topic);
29 | }
30 |
31 | Future isPremissionEnabled(AuthorizationStatus status) async {
32 | final settings = await _firebaseMessaging.getNotificationSettings();
33 | return settings.authorizationStatus == status;
34 | }
35 |
36 | Future _requestPermission() async {
37 | final NotificationSettings settings =
38 | await _firebaseMessaging.requestPermission(
39 | alert: true,
40 | badge: true,
41 | sound: true,
42 | carPlay: false,
43 | criticalAlert: false,
44 | provisional: false,
45 | announcement: false,
46 | );
47 | print('User granted permission: ${settings.authorizationStatus}');
48 |
49 | return settings;
50 | }
51 |
52 | _firebaseMessagingForgroundHandler() {
53 | FirebaseMessaging.onMessage.listen(
54 | (
55 | RemoteMessage message,
56 | ) {
57 | print('Message data: ${message.data}');
58 | // custom message data: {price: 10, coffee: latte2}
59 | if (message.notification != null) {
60 | showAlertDialog(
61 | message.notification!.body ?? '',
62 | message.notification?.title,
63 | );
64 | }
65 | },
66 | );
67 | }
68 |
69 | void showMessage(RemoteMessage remoteMessage) {
70 | showAlertDialog(remoteMessage.messageId ?? '');
71 | }
72 |
73 | get userDeviceToken {
74 | return _token;
75 | }
76 |
77 | String? _token;
78 |
79 | void setToken(String token) {
80 | print('FCM Token: $token');
81 | _token = token;
82 | }
83 |
84 | Future _getToken() async {
85 | final String? token = await _firebaseMessaging.getToken();
86 | if (token != null) {
87 | _token = token;
88 | }
89 |
90 | _firebaseMessaging.onTokenRefresh.listen(setToken);
91 | }
92 |
93 | Future deleteToken() async {
94 | await _firebaseMessaging.deleteToken();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'const.dart';
4 |
5 | ThemeData getTheme() {
6 | return ThemeData(
7 | textTheme: TextTheme(
8 | headline1: TextStyle(color: darkBrown),
9 | headline2: TextStyle(color: darkBrown),
10 | headline3: TextStyle(color: darkBrown),
11 | headline4: TextStyle(color: darkBrown),
12 | headline5: TextStyle(color: darkBrown),
13 | headline6: TextStyle(color: darkBrown),
14 | caption: TextStyle(color: darkBrown),
15 | bodyText1: TextStyle(color: darkBrown),
16 | subtitle1: TextStyle(color: darkBrown),
17 | subtitle2: TextStyle(color: darkBrown),
18 | bodyText2: TextStyle(color: darkBrown),
19 | overline: TextStyle(color: darkBrown),
20 | button: TextStyle(color: darkBrown),
21 | ),
22 | scaffoldBackgroundColor: Colors.white,
23 | fontFamily: 'Raleway',
24 | appBarTheme: const AppBarTheme(
25 | iconTheme: IconThemeData(color: Colors.brown),
26 | color: Colors.white,
27 | elevation: 0.0,
28 | centerTitle: true,
29 | titleTextStyle: TextStyle(
30 | color: Colors.brown,
31 | ),
32 | toolbarTextStyle: TextStyle(
33 | color: Colors.brown,
34 | ),
35 | ),
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/lib/widgets/src/alert_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/router.dart';
3 |
4 | Future showAlertDialog(String message, [String? title]) async {
5 | print('showAlertDialog');
6 | return showDialog(
7 | context: router.routerDelegate.navigatorKey.currentContext!,
8 | builder: (BuildContext context) {
9 | return AlertDialog(
10 | title: Text(title ?? 'Error!'),
11 | content: Text(message),
12 | );
13 | },
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/lib/widgets/src/button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../const.dart';
4 |
5 | class CommonButton extends StatelessWidget {
6 | const CommonButton({
7 | Key? key,
8 | this.onPressed,
9 | this.highlighColor = false,
10 | required this.text,
11 | }) : super(key: key);
12 |
13 | final VoidCallback? onPressed;
14 | final String text;
15 | final bool highlighColor;
16 |
17 | Color get backgroundColor {
18 | return highlighColor ? darkBrown : Colors.white;
19 | }
20 |
21 | Color get textColor {
22 | return highlighColor ? Colors.white : darkBrown;
23 | }
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return TextButton(
28 | key: key,
29 | onPressed: onPressed,
30 | child: Text(
31 | text,
32 | style: TextStyle(color: textColor),
33 | ),
34 | style: ButtonStyle(
35 | backgroundColor: MaterialStateProperty.all(
36 | backgroundColor,
37 | ),
38 | side: MaterialStateProperty.all(
39 | BorderSide(color: darkBrown),
40 | ),
41 | padding: MaterialStateProperty.all(
42 | EdgeInsets.fromLTRB(55, 15, 55, 15),
43 | ),
44 | shape: MaterialStateProperty.all(
45 | StadiumBorder(),
46 | ),
47 | ),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/widgets/src/cart_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/models/models.dart';
3 | import 'package:mjcoffee/services/services.dart';
4 |
5 | class CartBadge extends StatelessWidget {
6 | CartBadge({
7 | required this.child,
8 | this.color,
9 | required this.top,
10 | required this.right,
11 | });
12 |
13 | final FirestoreService _firestoreService = FirestoreService.instance;
14 | final AuthService _authService = AuthService.instance;
15 |
16 | final double top;
17 | final double right;
18 | final Widget child;
19 | final Color? color;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return StreamBuilder?>(
24 | stream: _firestoreService.getUserCart(_authService.currentUser!.uid),
25 | builder: (context, AsyncSnapshot?> snapshot) {
26 | int cartItems = 0;
27 |
28 | if (snapshot.hasError) {
29 | print(snapshot.error);
30 | return SizedBox();
31 | }
32 |
33 | if (snapshot.connectionState == ConnectionState.active) {
34 | if (snapshot.hasData) {
35 | cartItems = snapshot.data!.length;
36 | return Stack(
37 | alignment: Alignment.center,
38 | children: [
39 | child,
40 | Positioned(
41 | right: right,
42 | top: top,
43 | child: Container(
44 | padding: EdgeInsets.all(2.0),
45 | decoration: BoxDecoration(
46 | borderRadius: BorderRadius.circular(10.0),
47 | color: color != null ? color : Colors.red,
48 | ),
49 | constraints: BoxConstraints(
50 | minWidth: 16,
51 | minHeight: 16,
52 | ),
53 | child: Text(
54 | cartItems.toString(),
55 | textAlign: TextAlign.center,
56 | style: TextStyle(
57 | fontSize: 10,
58 | ),
59 | ),
60 | ),
61 | )
62 | ],
63 | );
64 | }
65 | }
66 | return SizedBox();
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/widgets/src/coffee_additions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/enums/enums.dart';
3 |
4 | class CoffeeAdditions extends StatelessWidget {
5 | CoffeeAdditions({
6 | Key? key,
7 | required this.additions,
8 | required this.onPressed,
9 | this.lessSpace = false,
10 | }) : super(key: key);
11 |
12 | final List additions;
13 | final bool lessSpace;
14 | final Function(List) onPressed;
15 |
16 | get hasCake {
17 | return additions.contains(CoffeeAddition.cake);
18 | }
19 |
20 | get hasIceCream {
21 | return additions.contains(CoffeeAddition.icecream);
22 | }
23 |
24 | get hasCheese {
25 | return additions.contains(CoffeeAddition.cheese);
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Row(
31 | mainAxisAlignment: MainAxisAlignment.spaceAround,
32 | children: [
33 | if (!lessSpace) ...[
34 | Text(
35 | "Additions",
36 | style: TextStyle(
37 | color: Colors.grey.shade700,
38 | ),
39 | ),
40 | SizedBox(width: 14),
41 | ],
42 | IconButton(
43 | icon: Icon(
44 | CoffeeAddition.cake.iconData,
45 | color: getColor(hasCake),
46 | ),
47 | onPressed: () {
48 | toggle(hasCake, CoffeeAddition.cake);
49 | },
50 | ),
51 | IconButton(
52 | icon: Icon(
53 | CoffeeAddition.icecream.iconData,
54 | color: getColor(additions.contains(CoffeeAddition.icecream)),
55 | ),
56 | onPressed: () {
57 | toggle(hasIceCream, CoffeeAddition.icecream);
58 | },
59 | ),
60 | IconButton(
61 | icon: Icon(
62 | CoffeeAddition.cheese.iconData,
63 | color: getColor(additions.contains(CoffeeAddition.cheese)),
64 | ),
65 | onPressed: () {
66 | toggle(hasCheese, CoffeeAddition.cheese);
67 | },
68 | ),
69 | ],
70 | );
71 | }
72 |
73 | getColor(bool isSelected) {
74 | return isSelected ? Colors.brown.shade800 : Colors.grey.shade400;
75 | }
76 |
77 | toggle(bool selected, CoffeeAddition addition) {
78 | if (selected) {
79 | final newList = additions.where((item) => item != addition).toList();
80 | onPressed(newList);
81 | } else {
82 | final newList = [...additions, addition];
83 | onPressed(newList);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lib/widgets/src/coffee_cart_extra_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/models/models.dart';
3 |
4 | import 'coffee_additions.dart';
5 | import 'coffee_size.dart';
6 | import 'coffee_sugar.dart';
7 |
8 | class CoffeeCartExtraInfo extends StatelessWidget {
9 | const CoffeeCartExtraInfo({
10 | Key? key,
11 | required this.item,
12 | }) : super(key: key);
13 |
14 | final CartItem item;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Padding(
19 | padding: const EdgeInsets.symmetric(vertical: 10),
20 | child: Column(
21 | children: [
22 | Text('Details: ${item.quantity} x ${item.coffee.name}'),
23 | CoffeeSize(
24 | icon: item.coffee.iconData,
25 | size: item.size,
26 | lessSpace: true,
27 | onPressed: (_) {},
28 | ),
29 | Divider(height: 1),
30 | CoffeeSugar(
31 | sugar: item.sugar,
32 | lessSpace: true,
33 | onPressed: (_) {},
34 | ),
35 | Divider(height: 1),
36 | CoffeeAdditions(
37 | onPressed: (_) {},
38 | lessSpace: true,
39 | additions: item.additions,
40 | ),
41 | ],
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/widgets/src/coffee_count.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CoffeeCount extends StatefulWidget {
4 | CoffeeCount({
5 | Key? key,
6 | this.price,
7 | this.notifyValue,
8 | }) : super(key: key);
9 |
10 | final num? price;
11 | final Function(int)? notifyValue;
12 |
13 | @override
14 | _CoffeeCountState createState() => _CoffeeCountState();
15 | }
16 |
17 | class _CoffeeCountState extends State {
18 | int count = 1;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Row(
23 | mainAxisAlignment: MainAxisAlignment.center,
24 | children: [
25 | OutlinedButton(
26 | onPressed: () {
27 | if (count > 1) {
28 | setState(() {
29 | count = count - 1;
30 | });
31 | }
32 | widget.notifyValue!(count);
33 | },
34 | child: Icon(Icons.remove),
35 | style: ButtonStyle(
36 | side: MaterialStateProperty.all(
37 | BorderSide(color: Colors.grey.shade600),
38 | ),
39 | shape: MaterialStateProperty.all(
40 | RoundedRectangleBorder(
41 | borderRadius: BorderRadius.only(
42 | topLeft: Radius.circular(30),
43 | bottomLeft: Radius.circular(30),
44 | ),
45 | ),
46 | ),
47 | ),
48 | ),
49 | SizedBox(width: 20),
50 | Text(
51 | "$count",
52 | style: TextStyle(
53 | color: Colors.brown.shade800,
54 | fontSize: 26,
55 | ),
56 | ),
57 | SizedBox(width: 20),
58 | OutlinedButton(
59 | onPressed: () {
60 | setState(() {
61 | count = count + 1;
62 | });
63 | widget.notifyValue!(count);
64 | },
65 | child: Icon(Icons.add),
66 | style: ButtonStyle(
67 | padding: MaterialStateProperty.all(EdgeInsets.all(0)),
68 | shape: MaterialStateProperty.all(
69 | RoundedRectangleBorder(
70 | borderRadius: BorderRadius.only(
71 | bottomRight: Radius.circular(30),
72 | topRight: Radius.circular(30),
73 | ),
74 | ),
75 | ),
76 | side: MaterialStateProperty.all(
77 | BorderSide(
78 | color: Colors.grey.shade600,
79 | ),
80 | ),
81 | ),
82 | ),
83 | ],
84 | );
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lib/widgets/src/coffee_size.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/enums/enums.dart';
3 |
4 | class CoffeeSize extends StatelessWidget {
5 | CoffeeSize({
6 | Key? key,
7 | required this.icon,
8 | required this.size,
9 | this.lessSpace = false,
10 | required this.onPressed,
11 | }) : super(key: key);
12 |
13 | final IconData icon;
14 | final CoffeeCupSize size;
15 | final bool lessSpace;
16 | final Function(CoffeeCupSize) onPressed;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Row(
21 | mainAxisAlignment: MainAxisAlignment.spaceAround,
22 | children: [
23 | if (!lessSpace) ...[
24 | Text(
25 | "Size",
26 | style: TextStyle(
27 | color: Colors.grey.shade700,
28 | ),
29 | ),
30 | SizedBox(width: 50),
31 | ],
32 | IconButton(
33 | icon: Icon(
34 | icon,
35 | color: getColor(size == CoffeeCupSize.small),
36 | size: CoffeeCupSize.small.iconSize,
37 | ),
38 | onPressed: () {
39 | onPressed(CoffeeCupSize.small);
40 | },
41 | ),
42 | IconButton(
43 | icon: Icon(
44 | icon,
45 | color: getColor(size == CoffeeCupSize.medium),
46 | size: CoffeeCupSize.medium.iconSize,
47 | ),
48 | onPressed: () {
49 | onPressed(CoffeeCupSize.medium);
50 | },
51 | ),
52 | IconButton(
53 | icon: Icon(
54 | icon,
55 | color: getColor(size == CoffeeCupSize.large),
56 | size: CoffeeCupSize.large.iconSize,
57 | ),
58 | onPressed: () {
59 | onPressed(CoffeeCupSize.large);
60 | },
61 | ),
62 | ],
63 | );
64 | }
65 |
66 | getColor(bool isSelected) {
67 | return isSelected ? Colors.brown.shade800 : Colors.grey.shade400;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/widgets/src/coffee_sugar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/enums/enums.dart';
3 |
4 | class CoffeeSugar extends StatelessWidget {
5 | CoffeeSugar({
6 | Key? key,
7 | required this.sugar,
8 | this.lessSpace = false,
9 | required this.onPressed,
10 | }) : super(key: key);
11 |
12 | final CoffeeSugarCube sugar;
13 | final bool lessSpace;
14 | final Function(CoffeeSugarCube) onPressed;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Row(
19 | mainAxisAlignment: MainAxisAlignment.spaceAround,
20 | children: [
21 | if (!lessSpace) ...[
22 | Text(
23 | "Sugar",
24 | style: TextStyle(
25 | color: Colors.grey.shade700,
26 | ),
27 | ),
28 | SizedBox(width: 40),
29 | ],
30 | IconButton(
31 | icon: Icon(
32 | CoffeeSugarCube.no.iconData,
33 | color: getColor(sugar == CoffeeSugarCube.no),
34 | ),
35 | onPressed: () {
36 | onPressed(CoffeeSugarCube.no);
37 | },
38 | ),
39 | IconButton(
40 | icon: Icon(
41 | CoffeeSugarCube.one.iconData,
42 | color: getColor(sugar == CoffeeSugarCube.one),
43 | ),
44 | onPressed: () {
45 | onPressed(CoffeeSugarCube.one);
46 | },
47 | ),
48 | IconButton(
49 | icon: Row(
50 | children: [
51 | Icon(
52 | CoffeeSugarCube.two.iconData,
53 | color: getColor(sugar == CoffeeSugarCube.two),
54 | ),
55 | Flexible(
56 | child: Icon(
57 | CoffeeSugarCube.two.iconData,
58 | color: getColor(sugar == CoffeeSugarCube.two),
59 | ),
60 | ),
61 | ],
62 | ),
63 | onPressed: () {
64 | onPressed(CoffeeSugarCube.two);
65 | },
66 | ),
67 | ],
68 | );
69 | }
70 |
71 | getColor(bool isSelected) {
72 | return isSelected ? Colors.brown.shade800 : Colors.grey.shade400;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/widgets/src/create_account.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:go_router/go_router.dart';
3 |
4 | import '../../const.dart';
5 |
6 | class CreateAccount extends StatelessWidget {
7 | const CreateAccount({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Row(
12 | mainAxisAlignment: MainAxisAlignment.center,
13 | children: [
14 | Text(
15 | "Don\'t have an account?",
16 | style: TextStyle(color: Colors.grey.shade600),
17 | ),
18 | TextButton(
19 | onPressed: () {
20 | GoRouter.of(context).goNamed('register');
21 | },
22 | child: Text(
23 | " Register",
24 | style: TextStyle(
25 | color: darkBrown,
26 | fontWeight: FontWeight.w500,
27 | ),
28 | ),
29 | ),
30 | ],
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/widgets/src/loading_snack_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | loadingSnackBar({
4 | text = 'Loading...',
5 | }) {
6 | return SnackBar(
7 | content: Row(
8 | children: [
9 | CircularProgressIndicator(),
10 | SizedBox(
11 | width: 20,
12 | ),
13 | Text(text)
14 | ],
15 | ),
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lib/widgets/src/login_inputs.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:mjcoffee/helpers/helpers.dart';
3 |
4 | import '../../const.dart';
5 |
6 | class LoginInputs extends StatelessWidget {
7 | const LoginInputs({
8 | Key? key,
9 | this.emailFieldController,
10 | this.passwordFieldController,
11 | }) : super(key: key);
12 |
13 | final emailFieldController;
14 | final passwordFieldController;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Column(
19 | children: [
20 | TextFormField(
21 | key: Key('email'),
22 | controller: emailFieldController,
23 | decoration: InputDecoration(
24 | labelText: 'Username',
25 | hintText: 'me@majidhajian.com',
26 | labelStyle: TextStyle(color: darkBrown),
27 | enabledBorder: UnderlineInputBorder(
28 | borderSide: BorderSide(
29 | color: Colors.grey.shade400,
30 | ),
31 | ),
32 | focusedBorder: UnderlineInputBorder(
33 | borderSide: BorderSide(color: darkBrown),
34 | ),
35 | border: UnderlineInputBorder(
36 | borderSide: BorderSide(color: darkBrown),
37 | ),
38 | ),
39 | cursorColor: darkBrown,
40 | validator: Validators.validateEmail,
41 | ),
42 | SizedBox(height: 30),
43 | TextFormField(
44 | key: Key('password'),
45 | controller: passwordFieldController,
46 | autocorrect: false,
47 | obscureText: true,
48 | decoration: InputDecoration(
49 | hintText: 'securepassword',
50 | labelText: 'Password',
51 | labelStyle: TextStyle(color: darkBrown),
52 | enabledBorder: UnderlineInputBorder(
53 | borderSide: BorderSide(color: Colors.grey.shade400),
54 | ),
55 | focusedBorder: UnderlineInputBorder(
56 | borderSide: BorderSide(color: darkBrown),
57 | ),
58 | border: UnderlineInputBorder(
59 | borderSide: BorderSide(color: darkBrown),
60 | ),
61 | ),
62 | cursorColor: darkBrown,
63 | validator: Validators.validatePassword,
64 | ),
65 | SizedBox(height: 10),
66 | ],
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/widgets/src/no_items.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NoItems extends StatelessWidget {
4 | const NoItems({
5 | this.title,
6 | });
7 |
8 | final String? title;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Center(
13 | child: Text(
14 | title ?? 'No items',
15 | style: Theme.of(context).textTheme.headline2,
16 | ),
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/widgets/src/social_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
3 |
4 | import '../../const.dart';
5 |
6 | @immutable
7 | class SignInButton extends StatelessWidget {
8 | const SignInButton({
9 | required this.onPressed,
10 | required this.icon,
11 | required this.btnColor,
12 | required this.btnTextColor,
13 | required this.btnText,
14 | this.padding = 15.0,
15 | this.shape = const StadiumBorder(),
16 | });
17 |
18 | final void Function() onPressed;
19 | final Color btnColor;
20 | final Color btnTextColor;
21 | final String btnText;
22 | final IconData icon;
23 |
24 | final double padding;
25 | final ShapeBorder shape;
26 |
27 | factory SignInButton.apple({
28 | required void Function() onPressed,
29 | }) {
30 | return SignInButton(
31 | btnText: 'Sign in with Apple',
32 | btnTextColor: Colors.black,
33 | btnColor: const Color(0xfff7f7f7),
34 | icon: FontAwesomeIcons.apple,
35 | onPressed: onPressed,
36 | );
37 | }
38 |
39 | factory SignInButton.twitter({
40 | required onPressed,
41 | }) =>
42 | SignInButton(
43 | btnText: 'Sign in with Twitter',
44 | btnTextColor: Colors.white,
45 | btnColor: const Color(0xFF1DA1F2),
46 | icon: FontAwesomeIcons.twitter,
47 | onPressed: onPressed,
48 | );
49 |
50 | factory SignInButton.google({
51 | required onPressed,
52 | }) =>
53 | SignInButton(
54 | btnText: 'Sign in with Google',
55 | btnTextColor: Colors.white,
56 | btnColor: const Color(0xff4285F4),
57 | icon: FontAwesomeIcons.google,
58 | onPressed: onPressed,
59 | );
60 |
61 | factory SignInButton.mail({
62 | required onPressed,
63 | }) =>
64 | SignInButton(
65 | btnText: 'Sign in with Email',
66 | btnTextColor: Colors.white,
67 | btnColor: darkBrown,
68 | icon: FontAwesomeIcons.envelope,
69 | onPressed: onPressed,
70 | );
71 |
72 | factory SignInButton.phone({
73 | required onPressed,
74 | }) =>
75 | SignInButton(
76 | btnText: 'Sign in with Phone',
77 | btnTextColor: Colors.white,
78 | btnColor: Colors.grey,
79 | icon: FontAwesomeIcons.phone,
80 | onPressed: onPressed,
81 | );
82 |
83 | @override
84 | Widget build(BuildContext context) {
85 | return MaterialButton(
86 | color: btnColor,
87 | shape: shape,
88 | onPressed: onPressed,
89 | elevation: 1,
90 | child: Container(
91 | child: Row(
92 | mainAxisSize: MainAxisSize.min,
93 | mainAxisAlignment: MainAxisAlignment.start,
94 | children: [
95 | Padding(
96 | padding: EdgeInsets.all(padding),
97 | child: Icon(
98 | icon,
99 | ),
100 | ),
101 | Padding(
102 | padding: EdgeInsets.all(padding),
103 | child: Text(
104 | btnText,
105 | style: TextStyle(
106 | color: btnTextColor,
107 | ),
108 | ),
109 | ),
110 | ],
111 | ),
112 | ),
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/widgets/src/total_amount.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../const.dart';
4 |
5 | class TotalAmount extends StatelessWidget {
6 | const TotalAmount({
7 | Key? key,
8 | required this.cartTotal,
9 | }) : super(key: key);
10 |
11 | final num cartTotal;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Container(
16 | decoration: BoxDecoration(
17 | border: Border.all(color: brown),
18 | borderRadius: BorderRadius.circular(15),
19 | ),
20 | child: Padding(
21 | padding: const EdgeInsets.all(8.0),
22 | child: Row(
23 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
24 | children: [
25 | Text(
26 | 'total:',
27 | style: Theme.of(context).textTheme.headline6,
28 | ),
29 | Text(
30 | '\$${cartTotal.toString()}',
31 | style: Theme.of(context).textTheme.headline6,
32 | ),
33 | ],
34 | ),
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'src/alert_dialog.dart';
2 | export 'src/button.dart';
3 | export 'src/cart_badge.dart';
4 | export 'src/coffee_additions.dart';
5 | export 'src/coffee_count.dart';
6 | export 'src/coffee_sugar.dart';
7 | export 'src/coffee_size.dart';
8 | export 'src/create_account.dart';
9 | export 'src/loading_snack_bar.dart';
10 | export 'src/login_inputs.dart';
11 | export 'src/social_button.dart';
12 | export 'src/total_amount.dart';
13 | export 'src/coffee_cart_extra_info.dart';
14 | export 'src/no_items.dart';
15 |
--------------------------------------------------------------------------------
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/linux/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 | project(runner LANGUAGES CXX)
3 |
4 | set(BINARY_NAME "fullstack_flutter_firebase_workshop")
5 | set(APPLICATION_ID "io.wiredbrain.app")
6 |
7 | cmake_policy(SET CMP0063 NEW)
8 |
9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
10 |
11 | # Root filesystem for cross-building.
12 | if(FLUTTER_TARGET_PLATFORM_SYSROOT)
13 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
14 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
16 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
19 | endif()
20 |
21 | # Configure build options.
22 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
23 | set(CMAKE_BUILD_TYPE "Debug" CACHE
24 | STRING "Flutter build mode" FORCE)
25 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
26 | "Debug" "Profile" "Release")
27 | endif()
28 |
29 | # Compilation settings that should be applied to most targets.
30 | function(APPLY_STANDARD_SETTINGS TARGET)
31 | target_compile_features(${TARGET} PUBLIC cxx_std_14)
32 | target_compile_options(${TARGET} PRIVATE -Wall -Werror)
33 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
34 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
35 | endfunction()
36 |
37 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
38 |
39 | # Flutter library and tool build rules.
40 | add_subdirectory(${FLUTTER_MANAGED_DIR})
41 |
42 | # System-level dependencies.
43 | find_package(PkgConfig REQUIRED)
44 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
45 |
46 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
47 |
48 | # Application build
49 | add_executable(${BINARY_NAME}
50 | "main.cc"
51 | "my_application.cc"
52 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
53 | )
54 | apply_standard_settings(${BINARY_NAME})
55 | target_link_libraries(${BINARY_NAME} PRIVATE flutter)
56 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
57 | add_dependencies(${BINARY_NAME} flutter_assemble)
58 | # Only the install-generated bundle's copy of the executable will launch
59 | # correctly, since the resources must in the right relative locations. To avoid
60 | # people trying to run the unbundled copy, put it in a subdirectory instead of
61 | # the default top-level location.
62 | set_target_properties(${BINARY_NAME}
63 | PROPERTIES
64 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
65 | )
66 |
67 | # Generated plugin build rules, which manage building the plugins and adding
68 | # them to the application.
69 | include(flutter/generated_plugins.cmake)
70 |
71 |
72 | # === Installation ===
73 | # By default, "installing" just makes a relocatable bundle in the build
74 | # directory.
75 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
76 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
77 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
78 | endif()
79 |
80 | # Start with a clean build bundle directory every time.
81 | install(CODE "
82 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
83 | " COMPONENT Runtime)
84 |
85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
87 |
88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
89 | COMPONENT Runtime)
90 |
91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
92 | COMPONENT Runtime)
93 |
94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
95 | COMPONENT Runtime)
96 |
97 | if(PLUGIN_BUNDLED_LIBRARIES)
98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
100 | COMPONENT Runtime)
101 | endif()
102 |
103 | # Fully re-copy the assets directory on each build to avoid having stale files
104 | # from a previous install.
105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
106 | install(CODE "
107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
108 | " COMPONENT Runtime)
109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
111 |
112 | # Install the AOT library on non-Debug builds only.
113 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
114 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
115 | COMPONENT Runtime)
116 | endif()
117 |
--------------------------------------------------------------------------------
/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
4 |
5 | # Configuration provided via flutter tool.
6 | include(${EPHEMERAL_DIR}/generated_config.cmake)
7 |
8 | # TODO: Move the rest of this into files in ephemeral. See
9 | # https://github.com/flutter/flutter/issues/57146.
10 |
11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
12 | # which isn't available in 3.10.
13 | function(list_prepend LIST_NAME PREFIX)
14 | set(NEW_LIST "")
15 | foreach(element ${${LIST_NAME}})
16 | list(APPEND NEW_LIST "${PREFIX}${element}")
17 | endforeach(element)
18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
19 | endfunction()
20 |
21 | # === Flutter Library ===
22 | # System-level dependencies.
23 | find_package(PkgConfig REQUIRED)
24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
27 |
28 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
29 |
30 | # Published to parent scope for install step.
31 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
32 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
33 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
34 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
35 |
36 | list(APPEND FLUTTER_LIBRARY_HEADERS
37 | "fl_basic_message_channel.h"
38 | "fl_binary_codec.h"
39 | "fl_binary_messenger.h"
40 | "fl_dart_project.h"
41 | "fl_engine.h"
42 | "fl_json_message_codec.h"
43 | "fl_json_method_codec.h"
44 | "fl_message_codec.h"
45 | "fl_method_call.h"
46 | "fl_method_channel.h"
47 | "fl_method_codec.h"
48 | "fl_method_response.h"
49 | "fl_plugin_registrar.h"
50 | "fl_plugin_registry.h"
51 | "fl_standard_message_codec.h"
52 | "fl_standard_method_codec.h"
53 | "fl_string_codec.h"
54 | "fl_value.h"
55 | "fl_view.h"
56 | "flutter_linux.h"
57 | )
58 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
59 | add_library(flutter INTERFACE)
60 | target_include_directories(flutter INTERFACE
61 | "${EPHEMERAL_DIR}"
62 | )
63 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
64 | target_link_libraries(flutter INTERFACE
65 | PkgConfig::GTK
66 | PkgConfig::GLIB
67 | PkgConfig::GIO
68 | )
69 | add_dependencies(flutter flutter_assemble)
70 |
71 | # === Flutter tool backend ===
72 | # _phony_ is a non-existent file to force this command to run every time,
73 | # since currently there's no way to get a full input/output list from the
74 | # flutter tool.
75 | add_custom_command(
76 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
77 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
78 | COMMAND ${CMAKE_COMMAND} -E env
79 | ${FLUTTER_TOOL_ENVIRONMENT}
80 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
81 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
82 | VERBATIM
83 | )
84 | add_custom_target(flutter_assemble DEPENDS
85 | "${FLUTTER_LIBRARY}"
86 | ${FLUTTER_LIBRARY_HEADERS}
87 | )
88 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void fl_register_plugins(FlPluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | set(PLUGIN_BUNDLED_LIBRARIES)
9 |
10 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
11 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
12 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
13 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
15 | endforeach(plugin)
16 |
--------------------------------------------------------------------------------
/linux/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/linux/my_application.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | #include
4 | #ifdef GDK_WINDOWING_X11
5 | #include
6 | #endif
7 |
8 | #include "flutter/generated_plugin_registrant.h"
9 |
10 | struct _MyApplication {
11 | GtkApplication parent_instance;
12 | char** dart_entrypoint_arguments;
13 | };
14 |
15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
16 |
17 | // Implements GApplication::activate.
18 | static void my_application_activate(GApplication* application) {
19 | MyApplication* self = MY_APPLICATION(application);
20 | GtkWindow* window =
21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
22 |
23 | // Use a header bar when running in GNOME as this is the common style used
24 | // by applications and is the setup most users will be using (e.g. Ubuntu
25 | // desktop).
26 | // If running on X and not using GNOME then just use a traditional title bar
27 | // in case the window manager does more exotic layout, e.g. tiling.
28 | // If running on Wayland assume the header bar will work (may need changing
29 | // if future cases occur).
30 | gboolean use_header_bar = TRUE;
31 | #ifdef GDK_WINDOWING_X11
32 | GdkScreen* screen = gtk_window_get_screen(window);
33 | if (GDK_IS_X11_SCREEN(screen)) {
34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
36 | use_header_bar = FALSE;
37 | }
38 | }
39 | #endif
40 | if (use_header_bar) {
41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
42 | gtk_widget_show(GTK_WIDGET(header_bar));
43 | gtk_header_bar_set_title(header_bar, "fullstack_flutter_firebase_workshop");
44 | gtk_header_bar_set_show_close_button(header_bar, TRUE);
45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
46 | } else {
47 | gtk_window_set_title(window, "fullstack_flutter_firebase_workshop");
48 | }
49 |
50 | gtk_window_set_default_size(window, 1280, 720);
51 | gtk_widget_show(GTK_WIDGET(window));
52 |
53 | g_autoptr(FlDartProject) project = fl_dart_project_new();
54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
55 |
56 | FlView* view = fl_view_new(project);
57 | gtk_widget_show(GTK_WIDGET(view));
58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
59 |
60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view));
61 |
62 | gtk_widget_grab_focus(GTK_WIDGET(view));
63 | }
64 |
65 | // Implements GApplication::local_command_line.
66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
67 | MyApplication* self = MY_APPLICATION(application);
68 | // Strip out the first argument as it is the binary name.
69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
70 |
71 | g_autoptr(GError) error = nullptr;
72 | if (!g_application_register(application, nullptr, &error)) {
73 | g_warning("Failed to register: %s", error->message);
74 | *exit_status = 1;
75 | return TRUE;
76 | }
77 |
78 | g_application_activate(application);
79 | *exit_status = 0;
80 |
81 | return TRUE;
82 | }
83 |
84 | // Implements GObject::dispose.
85 | static void my_application_dispose(GObject* object) {
86 | MyApplication* self = MY_APPLICATION(object);
87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
89 | }
90 |
91 | static void my_application_class_init(MyApplicationClass* klass) {
92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate;
93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
95 | }
96 |
97 | static void my_application_init(MyApplication* self) {}
98 |
99 | MyApplication* my_application_new() {
100 | return MY_APPLICATION(g_object_new(my_application_get_type(),
101 | "application-id", APPLICATION_ID,
102 | "flags", G_APPLICATION_NON_UNIQUE,
103 | nullptr));
104 | }
105 |
--------------------------------------------------------------------------------
/linux/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import cloud_firestore
9 | import firebase_auth
10 | import firebase_core
11 | import firebase_messaging
12 | import path_provider_macos
13 | import sign_in_with_apple
14 |
15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
16 | FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
17 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
18 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
19 | FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
20 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
21 | SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
22 | }
23 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.11'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | end
35 |
36 | post_install do |installer|
37 | installer.pods_project.targets.each do |target|
38 | flutter_additional_macos_build_settings(target)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
71 |
73 |
79 |
80 |
81 |
82 |
84 |
85 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = fullstack_flutter_firebase_workshop
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.fullstackFlutterFirebaseWorkshop
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/privacy.md:
--------------------------------------------------------------------------------
1 | **Privacy Policy**
2 |
3 | Majid Hajian built the Wired Brain Coffee app as a Free app. This SERVICE is provided by Majid Hajian at no cost and is intended for use as is.
4 |
5 | This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
6 |
7 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
8 |
9 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Wired Brain Coffee unless otherwise defined in this Privacy Policy.
10 |
11 | **Information Collection and Use**
12 |
13 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to Nothing. The information that I request will be retained on your device and is not collected by me in any way.
14 |
15 | The app does use third party services that may collect information used to identify you.
16 |
17 | Link to privacy policy of third party service providers used by the app
18 |
19 | - [Google Play Services](https://www.google.com/policies/privacy/)
20 |
21 | **Log Data**
22 |
23 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
24 |
25 | **Cookies**
26 |
27 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
28 |
29 | This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
30 |
31 | **Service Providers**
32 |
33 | I may employ third-party companies and individuals due to the following reasons:
34 |
35 | - To facilitate our Service;
36 | - To provide the Service on our behalf;
37 | - To perform Service-related services; or
38 | - To assist us in analyzing how our Service is used.
39 |
40 | I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
41 |
42 | **Security**
43 |
44 | I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
45 |
46 | **Links to Other Sites**
47 |
48 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
49 |
50 | **Children’s Privacy**
51 |
52 | These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13\. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.
53 |
54 | **Changes to This Privacy Policy**
55 |
56 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
57 |
58 | This policy is effective as of 2020-01-24
59 |
60 | **Contact Us**
61 |
62 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at me@majidhajian.com.
63 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: mjcoffee
2 | description: Project by majid Hajian
3 |
4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: '>=2.13.0 <3.0.0'
10 | flutter: '>=2.5.0 <3.0.0'
11 |
12 | dependencies:
13 | build_runner: ^2.1.4
14 | cloud_firestore: ^2.5.4
15 | crypto: ^3.0.1
16 | firebase_analytics: ^8.3.4
17 | firebase_auth: ^3.1.4
18 | firebase_in_app_messaging: ^0.5.0+11
19 | firebase_messaging: ^10.0.9
20 | flutter:
21 | sdk: flutter
22 | flutter_spinkit: ^5.1.0
23 | flutter_svg: ^0.23.0+1
24 | font_awesome_flutter: ^9.2.0
25 | go_router: ^2.1.2
26 | google_fonts: ^2.1.0
27 | google_sign_in: ^5.2.0
28 | http: ^0.13.4
29 | json_annotation: ^4.1.0
30 | sign_in_with_apple: ^3.2.0
31 |
32 | dev_dependencies:
33 | flutter_launcher_icons: ^0.9.2
34 | json_serializable: ^5.0.0
35 | integration_test:
36 | sdk: flutter
37 | flutter_test:
38 | sdk: flutter
39 |
40 | flutter:
41 | uses-material-design: true
42 | assets:
43 | - assets/
44 | # run flutter pub run flutter_launcher_icons:main
45 | flutter_icons:
46 | android: 'launcher_icon'
47 | ios: true
48 | image_path: 'assets/logo.png'
49 |
--------------------------------------------------------------------------------
/test/validators_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:mjcoffee/helpers/helpers.dart';
3 |
4 | void main() {
5 | group(
6 | 'Validators',
7 | () {
8 | group(
9 | 'Email',
10 | () {
11 | test(
12 | 'should return error string if empty',
13 | () {
14 | final result = Validators.validateEmail('');
15 | expect(result, 'Email can\'t be empty');
16 | },
17 | );
18 |
19 | test(
20 | 'should return null if not empty',
21 | () {
22 | final result = Validators.validateEmail('email');
23 | expect(result, null);
24 | },
25 | );
26 | },
27 | );
28 | group(
29 | 'Password',
30 | () {
31 | test(
32 | 'should return error string if empty',
33 | () {
34 | final result = Validators.validatePassword('');
35 | expect(result, 'Password can\'t be empty');
36 | },
37 | );
38 |
39 | test(
40 | 'should return null if not empty',
41 | () {
42 | final result = Validators.validatePassword('password');
43 | expect(result, null);
44 | },
45 | );
46 | },
47 | );
48 | },
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | void main() {}
2 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | fullstack_flutter_firebase_workshop
30 |
31 |
32 |
33 |
36 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fullstack_flutter_firebase_workshop",
3 | "short_name": "fullstack_flutter_firebase_workshop",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | project(fullstack_flutter_firebase_workshop LANGUAGES CXX)
3 |
4 | set(BINARY_NAME "fullstack_flutter_firebase_workshop")
5 |
6 | cmake_policy(SET CMP0063 NEW)
7 |
8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
9 |
10 | # Configure build options.
11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
12 | if(IS_MULTICONFIG)
13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
14 | CACHE STRING "" FORCE)
15 | else()
16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
17 | set(CMAKE_BUILD_TYPE "Debug" CACHE
18 | STRING "Flutter build mode" FORCE)
19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
20 | "Debug" "Profile" "Release")
21 | endif()
22 | endif()
23 |
24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
28 |
29 | # Use Unicode for all projects.
30 | add_definitions(-DUNICODE -D_UNICODE)
31 |
32 | # Compilation settings that should be applied to most targets.
33 | function(APPLY_STANDARD_SETTINGS TARGET)
34 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
36 | target_compile_options(${TARGET} PRIVATE /EHsc)
37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
39 | endfunction()
40 |
41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
42 |
43 | # Flutter library and tool build rules.
44 | add_subdirectory(${FLUTTER_MANAGED_DIR})
45 |
46 | # Application build
47 | add_subdirectory("runner")
48 |
49 | # Generated plugin build rules, which manage building the plugins and adding
50 | # them to the application.
51 | include(flutter/generated_plugins.cmake)
52 |
53 |
54 | # === Installation ===
55 | # Support files are copied into place next to the executable, so that it can
56 | # run in place. This is done instead of making a separate bundle (as on Linux)
57 | # so that building and running from within Visual Studio will work.
58 | set(BUILD_BUNDLE_DIR "$")
59 | # Make the "install" step default, as it's required to run.
60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
63 | endif()
64 |
65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
67 |
68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
69 | COMPONENT Runtime)
70 |
71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
72 | COMPONENT Runtime)
73 |
74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
75 | COMPONENT Runtime)
76 |
77 | if(PLUGIN_BUNDLED_LIBRARIES)
78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
80 | COMPONENT Runtime)
81 | endif()
82 |
83 | # Fully re-copy the assets directory on each build to avoid having stale files
84 | # from a previous install.
85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
86 | install(CODE "
87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
88 | " COMPONENT Runtime)
89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
91 |
92 | # Install the AOT library on non-Debug builds only.
93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
94 | CONFIGURATIONS Profile;Release
95 | COMPONENT Runtime)
96 |
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 |
3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
4 |
5 | # Configuration provided via flutter tool.
6 | include(${EPHEMERAL_DIR}/generated_config.cmake)
7 |
8 | # TODO: Move the rest of this into files in ephemeral. See
9 | # https://github.com/flutter/flutter/issues/57146.
10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
11 |
12 | # === Flutter Library ===
13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
14 |
15 | # Published to parent scope for install step.
16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
20 |
21 | list(APPEND FLUTTER_LIBRARY_HEADERS
22 | "flutter_export.h"
23 | "flutter_windows.h"
24 | "flutter_messenger.h"
25 | "flutter_plugin_registrar.h"
26 | "flutter_texture_registrar.h"
27 | )
28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
29 | add_library(flutter INTERFACE)
30 | target_include_directories(flutter INTERFACE
31 | "${EPHEMERAL_DIR}"
32 | )
33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
34 | add_dependencies(flutter flutter_assemble)
35 |
36 | # === Wrapper ===
37 | list(APPEND CPP_WRAPPER_SOURCES_CORE
38 | "core_implementations.cc"
39 | "standard_codec.cc"
40 | )
41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
43 | "plugin_registrar.cc"
44 | )
45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
46 | list(APPEND CPP_WRAPPER_SOURCES_APP
47 | "flutter_engine.cc"
48 | "flutter_view_controller.cc"
49 | )
50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
51 |
52 | # Wrapper sources needed for a plugin.
53 | add_library(flutter_wrapper_plugin STATIC
54 | ${CPP_WRAPPER_SOURCES_CORE}
55 | ${CPP_WRAPPER_SOURCES_PLUGIN}
56 | )
57 | apply_standard_settings(flutter_wrapper_plugin)
58 | set_target_properties(flutter_wrapper_plugin PROPERTIES
59 | POSITION_INDEPENDENT_CODE ON)
60 | set_target_properties(flutter_wrapper_plugin PROPERTIES
61 | CXX_VISIBILITY_PRESET hidden)
62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
63 | target_include_directories(flutter_wrapper_plugin PUBLIC
64 | "${WRAPPER_ROOT}/include"
65 | )
66 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
67 |
68 | # Wrapper sources needed for the runner.
69 | add_library(flutter_wrapper_app STATIC
70 | ${CPP_WRAPPER_SOURCES_CORE}
71 | ${CPP_WRAPPER_SOURCES_APP}
72 | )
73 | apply_standard_settings(flutter_wrapper_app)
74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
75 | target_include_directories(flutter_wrapper_app PUBLIC
76 | "${WRAPPER_ROOT}/include"
77 | )
78 | add_dependencies(flutter_wrapper_app flutter_assemble)
79 |
80 | # === Flutter tool backend ===
81 | # _phony_ is a non-existent file to force this command to run every time,
82 | # since currently there's no way to get a full input/output list from the
83 | # flutter tool.
84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
86 | add_custom_command(
87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
89 | ${CPP_WRAPPER_SOURCES_APP}
90 | ${PHONY_OUTPUT}
91 | COMMAND ${CMAKE_COMMAND} -E env
92 | ${FLUTTER_TOOL_ENVIRONMENT}
93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
94 | windows-x64 $
95 | VERBATIM
96 | )
97 | add_custom_target(flutter_assemble DEPENDS
98 | "${FLUTTER_LIBRARY}"
99 | ${FLUTTER_LIBRARY_HEADERS}
100 | ${CPP_WRAPPER_SOURCES_CORE}
101 | ${CPP_WRAPPER_SOURCES_PLUGIN}
102 | ${CPP_WRAPPER_SOURCES_APP}
103 | )
104 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void RegisterPlugins(flutter::PluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | set(PLUGIN_BUNDLED_LIBRARIES)
9 |
10 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
11 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
12 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
13 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
15 | endforeach(plugin)
16 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | project(runner LANGUAGES CXX)
3 |
4 | add_executable(${BINARY_NAME} WIN32
5 | "flutter_window.cpp"
6 | "main.cpp"
7 | "utils.cpp"
8 | "win32_window.cpp"
9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
10 | "Runner.rc"
11 | "runner.exe.manifest"
12 | )
13 | apply_standard_settings(${BINARY_NAME})
14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
17 | add_dependencies(${BINARY_NAME} flutter_assemble)
18 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #ifdef FLUTTER_BUILD_NUMBER
64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0
67 | #endif
68 |
69 | #ifdef FLUTTER_BUILD_NAME
70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.example" "\0"
93 | VALUE "FileDescription", "A new Flutter project." "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "fullstack_flutter_firebase_workshop" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "fullstack_flutter_firebase_workshop.exe" "\0"
98 | VALUE "ProductName", "fullstack_flutter_firebase_workshop" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.CreateAndShow(L"fullstack_flutter_firebase_workshop", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhadaily/fullstack_flutter_firebase_workshop/fc4a6218099f87de89cd8e48101f703f3ce154f3/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr);
51 | if (target_length == 0) {
52 | return std::string();
53 | }
54 | std::string utf8_string;
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates and shows a win32 window with |title| and position and size using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size to will treat the width height passed in to this function
35 | // as logical pixels and scale to appropriate for the default monitor. Returns
36 | // true if the window was created successfully.
37 | bool CreateAndShow(const std::wstring& title,
38 | const Point& origin,
39 | const Size& size);
40 |
41 | // Release OS resources associated with window.
42 | void Destroy();
43 |
44 | // Inserts |content| into the window tree.
45 | void SetChildContent(HWND content);
46 |
47 | // Returns the backing Window handle to enable clients to set icon and other
48 | // window properties. Returns nullptr if the window has been destroyed.
49 | HWND GetHandle();
50 |
51 | // If true, closing this window will quit the application.
52 | void SetQuitOnClose(bool quit_on_close);
53 |
54 | // Return a RECT representing the bounds of the current client area.
55 | RECT GetClientArea();
56 |
57 | protected:
58 | // Processes and route salient window messages for mouse handling,
59 | // size change and DPI. Delegates handling of these to member overloads that
60 | // inheriting classes can handle.
61 | virtual LRESULT MessageHandler(HWND window,
62 | UINT const message,
63 | WPARAM const wparam,
64 | LPARAM const lparam) noexcept;
65 |
66 | // Called when CreateAndShow is called, allowing subclass window-related
67 | // setup. Subclasses should return false if setup fails.
68 | virtual bool OnCreate();
69 |
70 | // Called when Destroy is called.
71 | virtual void OnDestroy();
72 |
73 | private:
74 | friend class WindowClassRegistrar;
75 |
76 | // OS callback called by message pump. Handles the WM_NCCREATE message which
77 | // is passed when the non-client area is being created and enables automatic
78 | // non-client DPI scaling so that the non-client area automatically
79 | // responsponds to changes in DPI. All other messages are handled by
80 | // MessageHandler.
81 | static LRESULT CALLBACK WndProc(HWND const window,
82 | UINT const message,
83 | WPARAM const wparam,
84 | LPARAM const lparam) noexcept;
85 |
86 | // Retrieves a class instance pointer for |window|
87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
88 |
89 | bool quit_on_close_ = false;
90 |
91 | // window handle for top level window.
92 | HWND window_handle_ = nullptr;
93 |
94 | // window handle for hosted content.
95 | HWND child_content_ = nullptr;
96 | };
97 |
98 | #endif // RUNNER_WIN32_WINDOW_H_
99 |
--------------------------------------------------------------------------------