├── .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 | --------------------------------------------------------------------------------