├── .git-crypt ├── .gitattributes └── keys │ └── default │ └── 0 │ └── E2236A7EAE7C97C0E7E1123D54BA00035D9A09E2.gpg ├── .gitattributes.txt ├── .github └── workflows │ └── github_action.yml ├── .gitignore ├── .gradle ├── 5.2.1 │ ├── fileChanges │ │ └── last-build.bin │ ├── fileHashes │ │ └── fileHashes.lock │ └── gc.properties ├── buildOutputCleanup │ ├── buildOutputCleanup.lock │ └── cache.properties └── vcs-1 │ └── gc.properties ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ ├── libs │ │ └── jsch-0.1.55.jar │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── aajinkya │ │ │ │ └── sign_in │ │ │ │ ├── MainActivity.java │ │ │ │ └── SshPlugin.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── Aajinkya.jpg ├── About2.png ├── Aditi.png ├── Calibre Semibold.otf ├── Flower_Float.txt ├── Home2.png ├── Miheer.png ├── Pratit.png ├── Reports.gif ├── RoverDev0.jpg ├── RoverDev1.jpg ├── RoverDev2.jpg ├── RoverDev3.jpg ├── RoverPy App.jpg ├── RoverPy_Logo.png ├── SF-Pro-Text-Bold.otf ├── SF-Pro-Text-Regular.otf ├── Sponsor2.png ├── Teddy.flr ├── TomatoModel.txt ├── WhatsNew.png ├── about.gif ├── aboutUs.png ├── background.flr ├── controller.jpg ├── custom_icons.ttf ├── dict.txt ├── github_logo.png ├── image_01.png ├── image_02.jpg ├── image_03.jpg ├── image_04.jpg ├── loadingSpaceman.flr ├── locate.jpg ├── manifest.json ├── map.gif ├── map.jpg ├── model.tflite ├── model_unquant.tflite ├── person.png ├── preview.png ├── profileCard.jpg ├── success.flr ├── test_barcode.jpg ├── test_face.jpg ├── test_text.png └── user.gif ├── demo ├── RoverPyHomePage.mp4 ├── RoverPyIOSUI.mp4 ├── RoverPyProfile.mp4 └── RoverPyWorkingRover.mp4 ├── fonts ├── LexendDeca-Regular.ttf ├── Poppins-Medium.ttf └── Raleway-SemiBold.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── 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.h │ ├── AppDelegate.m │ ├── 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 │ └── main.m ├── lib ├── animations.dart ├── auth │ ├── constants.dart │ ├── login.dart │ └── register.dart ├── commons │ ├── animated_popup.dart │ ├── collapsing_navigation_drawer.dart │ ├── custom_card.dart │ ├── drawer_tile.dart │ └── stepper_control.dart ├── helpers │ ├── map_helper.dart │ └── map_marker.dart ├── main.dart ├── models │ ├── developer_info.dart │ ├── drawer_item.dart │ ├── models_export.dart │ ├── series_model.dart │ ├── theme_model.dart │ └── user.dart ├── pages │ ├── QuickAccess.dart │ ├── Specific.dart │ ├── about.dart │ ├── constants_profile.dart │ ├── controls_page.dart │ ├── home_page.dart │ ├── map_page.dart │ ├── new_process_page.dart │ ├── pages_export.dart │ ├── process_page.dart │ ├── profile.dart │ ├── report_page.dart │ ├── shh_page.dart │ ├── ssh.dart │ ├── tf.dart │ └── video_page.dart ├── services │ ├── AuthService.dart │ ├── BackgroundCollectedPage.dart │ ├── BackgroundCollectingTask.dart │ ├── BluetoothDeviceListEntry.dart │ ├── ChatPage.dart │ ├── DiscoveryPage.dart │ ├── MainPage.dart │ ├── SelectBondedDevicePage.dart │ ├── bluetoothSerial.dart │ └── helpers │ │ ├── LineChart.dart │ │ └── PaintStyle.dart ├── utils │ ├── customIcons.dart │ ├── data.dart │ ├── themes.dart │ └── utils_export.dart └── wrapper.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.git-crypt/.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not edit this file. To specify the files to encrypt, create your own 2 | # .gitattributes file in the directory where your files are. 3 | * !filter !diff 4 | *.gpg binary -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/E2236A7EAE7C97C0E7E1123D54BA00035D9A09E2.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/.git-crypt/keys/default/0/E2236A7EAE7C97C0E7E1123D54BA00035D9A09E2.gpg -------------------------------------------------------------------------------- /.gitattributes.txt: -------------------------------------------------------------------------------- 1 | google-services.json filter=git-crypt diff=git-crypt -------------------------------------------------------------------------------- /.github/workflows/github_action.yml: -------------------------------------------------------------------------------- 1 | name: Build CI 2 | on: [push, pull_request_target] 3 | 4 | jobs: 5 | build: 6 | name: Build for Android 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Clone repository 11 | uses: actions/checkout@v2 12 | 13 | - name: Setup Java 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: '12.x' 17 | 18 | - name: Setup Flutter 19 | uses: subosito/flutter-action@v1 20 | 21 | - name: Build APK 22 | run: | 23 | flutter pub get 24 | flutter build apk 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /.gradle/5.2.1/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gradle/5.2.1/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/.gradle/5.2.1/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /.gradle/5.2.1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/.gradle/5.2.1/gc.properties -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 12 20:57:57 IST 2020 2 | gradle.version=5.2.1 3 | -------------------------------------------------------------------------------- /.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/.gradle/vcs-1/gc.properties -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aajinkya Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | RoverPy Logo 3 |

4 | 5 | # RoverPy App 6 | > Taking Agriculture to a whole new level! 7 | 8 | This is an application interface for the [RoverPy Project](https://github.com/RoverPy/RoverPy), which allows the farmers to see whatever is going on their RoverPy on the app. 9 | 10 | The job of the app is to process the images captured by the RoverPy using a Tensorflow Lite Model deployed on Firebase Custom Model, while at the same time control the RoverPy from the Controls Page and see a live-feed on the app. Wait that's not it, our app provides an interface to see all the past records of the RoverPy's round and depending on the confidence level (of it's health) a bar-chart is built in realtime to give a graphical representation of all the past records! Moreover, to actually visualize where the plant was detected we have provided marker's (either healthy / unhealthy) on Google Map using the co-ordinates gathered by the RoverPy. 11 | 12 | ## Key Features 13 | - [x] Our custom Tensorflow Lite Model is being used on our App 14 | - [x] Our own Dataset gathered from a live farm 15 | - [x] Multiple Tensorflow Models for various plants & flowers to detect a wide array of diseases 16 | - [x] Live feed directly from the RoverPy to our app 17 | - [x] Controlling the RoverPy from our App 18 | - [x] An in-depth stastical insight for the past records 19 | - [x] Use of dynamic graphs based on the confidence level 20 | - [x] A minimalistic yet sexy UI :wink: 21 | 22 | ## Screenshots 23 |

24 | RoverPy Logo 25 |

26 | 27 | ## Contributors 28 | This project was made by: 29 | - [Pratit Bandiwadekar](https://github.com/Pratit23) 30 | - [Aajinkya Singh](https://github.com/aajinkya1203) 31 | - [Aditi Mohan](https://github.com/Aditi-Mohan) 32 | - [Miheer Thampi](https://github.com/mimi69-38) 33 | 34 | ## Found something off? Want to collaborate? 35 | - Fork this repo 36 | - Make your changes 37 | - Make a PR and add any of the [Contributor](https://github.com/RoverPy/RoverPyApp#contributors) above as a [Reviewer](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/requesting-a-pull-request-review) 38 | 39 | ## License 40 | You can view our License [here](https://github.com/RoverPy/RoverPyApp/blob/master/LICENSE) 41 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | 29 | android { 30 | compileSdkVersion 28 31 | 32 | aaptOptions { 33 | noCompress "tflite" 34 | noCompress 'lite' 35 | } 36 | 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | 41 | defaultConfig { 42 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 43 | applicationId "aajinkya.sign_in" 44 | minSdkVersion 23 45 | targetSdkVersion 28 46 | versionCode flutterVersionCode.toInteger() 47 | versionName flutterVersionName 48 | multiDexEnabled true 49 | } 50 | 51 | buildTypes { 52 | release { 53 | // TODO: Add your own signing config for the release build. 54 | // Signing with the debug keys for now, so `flutter run --release` works. 55 | signingConfig signingConfigs.debug 56 | } 57 | } 58 | } 59 | 60 | flutter { 61 | source '../..' 62 | } 63 | 64 | dependencies { 65 | // ... 66 | // Image labeling feature with automl model downloaded 67 | // from firebase 68 | implementation 'com.google.mlkit:image-labeling-automl:16.0.0' 69 | implementation 'com.google.mlkit:linkfirebase:16.0.0' 70 | testImplementation 'junit:junit:4.12' 71 | androidTestImplementation 'androidx.test:runner:1.1.1' 72 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 73 | implementation 'org.tensorflow:tensorflow-lite:2.0.0' 74 | implementation files('libs/jsch-0.1.55.jar') 75 | } -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "202297952467", 4 | "firebase_url": "https://roverpy-aamp.firebaseio.com", 5 | "project_id": "roverpy-aamp", 6 | "storage_bucket": "roverpy-aamp.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:202297952467:android:e6318e1afa9d298c352c26", 12 | "android_client_info": { 13 | "package_name": "aajinkya.sign_in" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "202297952467-so23djdglh979c803vnt77i4gutrdrg5.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyBK_rUshb6zhCrR7tEKjGMuDuT2yDXRfMc" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "202297952467-so23djdglh979c803vnt77i4gutrdrg5.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /android/app/libs/jsch-0.1.55.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/libs/jsch-0.1.55.jar -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 16 | 17 | 19 | 26 | 30 | 34 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /android/app/src/main/java/aajinkya/sign_in/MainActivity.java: -------------------------------------------------------------------------------- 1 | package aajinkya.sign_in; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /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 | repositories { 3 | google() 4 | jcenter() 5 | maven { 6 | url 'https://google.bintray.com/exoplayer/' 7 | } 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | classpath 'com.google.gms:google-services:4.0.1' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | rootProject.buildDir = '../build' 24 | subprojects { 25 | project.buildDir = "${rootProject.buildDir}/${project.name}" 26 | } 27 | subprojects { 28 | project.evaluationDependsOn(':app') 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | 17 | 18 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 19 | 20 | def plugins = new Properties() 21 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 22 | if (pluginsFile.exists()) { 23 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 24 | } 25 | 26 | plugins.each { name, path -> 27 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 28 | include ":$name" 29 | project(":$name").projectDir = pluginDirectory 30 | } -------------------------------------------------------------------------------- /assets/Aajinkya.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Aajinkya.jpg -------------------------------------------------------------------------------- /assets/About2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/About2.png -------------------------------------------------------------------------------- /assets/Aditi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Aditi.png -------------------------------------------------------------------------------- /assets/Calibre Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Calibre Semibold.otf -------------------------------------------------------------------------------- /assets/Flower_Float.txt: -------------------------------------------------------------------------------- 1 | 0 Daisy 2 | 1 Dandelion 3 | 2 Roses 4 | 3 Sunflower 5 | 4 Tulips 6 | -------------------------------------------------------------------------------- /assets/Home2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Home2.png -------------------------------------------------------------------------------- /assets/Miheer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Miheer.png -------------------------------------------------------------------------------- /assets/Pratit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Pratit.png -------------------------------------------------------------------------------- /assets/Reports.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Reports.gif -------------------------------------------------------------------------------- /assets/RoverDev0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverDev0.jpg -------------------------------------------------------------------------------- /assets/RoverDev1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverDev1.jpg -------------------------------------------------------------------------------- /assets/RoverDev2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverDev2.jpg -------------------------------------------------------------------------------- /assets/RoverDev3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverDev3.jpg -------------------------------------------------------------------------------- /assets/RoverPy App.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverPy App.jpg -------------------------------------------------------------------------------- /assets/RoverPy_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/RoverPy_Logo.png -------------------------------------------------------------------------------- /assets/SF-Pro-Text-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/SF-Pro-Text-Bold.otf -------------------------------------------------------------------------------- /assets/SF-Pro-Text-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/SF-Pro-Text-Regular.otf -------------------------------------------------------------------------------- /assets/Sponsor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Sponsor2.png -------------------------------------------------------------------------------- /assets/Teddy.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/Teddy.flr -------------------------------------------------------------------------------- /assets/TomatoModel.txt: -------------------------------------------------------------------------------- 1 | 0 healthy 2 | 1 early_blight 3 | -------------------------------------------------------------------------------- /assets/WhatsNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/WhatsNew.png -------------------------------------------------------------------------------- /assets/about.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/about.gif -------------------------------------------------------------------------------- /assets/aboutUs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/aboutUs.png -------------------------------------------------------------------------------- /assets/background.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/background.flr -------------------------------------------------------------------------------- /assets/controller.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/controller.jpg -------------------------------------------------------------------------------- /assets/custom_icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/custom_icons.ttf -------------------------------------------------------------------------------- /assets/dict.txt: -------------------------------------------------------------------------------- 1 | Apple___Black_rot 2 | Apple___healthy 3 | -------------------------------------------------------------------------------- /assets/github_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/github_logo.png -------------------------------------------------------------------------------- /assets/image_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/image_01.png -------------------------------------------------------------------------------- /assets/image_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/image_02.jpg -------------------------------------------------------------------------------- /assets/image_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/image_03.jpg -------------------------------------------------------------------------------- /assets/image_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/image_04.jpg -------------------------------------------------------------------------------- /assets/loadingSpaceman.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/loadingSpaceman.flr -------------------------------------------------------------------------------- /assets/locate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/locate.jpg -------------------------------------------------------------------------------- /assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "modelFile": "model.tflite", 3 | "labelsFile": "dict.txt", 4 | "modelType": "IMAGE_LABELING" 5 | } -------------------------------------------------------------------------------- /assets/map.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/map.gif -------------------------------------------------------------------------------- /assets/map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/map.jpg -------------------------------------------------------------------------------- /assets/model.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/model.tflite -------------------------------------------------------------------------------- /assets/model_unquant.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/model_unquant.tflite -------------------------------------------------------------------------------- /assets/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/person.png -------------------------------------------------------------------------------- /assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/preview.png -------------------------------------------------------------------------------- /assets/profileCard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/profileCard.jpg -------------------------------------------------------------------------------- /assets/success.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/success.flr -------------------------------------------------------------------------------- /assets/test_barcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/test_barcode.jpg -------------------------------------------------------------------------------- /assets/test_face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/test_face.jpg -------------------------------------------------------------------------------- /assets/test_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/test_text.png -------------------------------------------------------------------------------- /assets/user.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/assets/user.gif -------------------------------------------------------------------------------- /demo/RoverPyHomePage.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/demo/RoverPyHomePage.mp4 -------------------------------------------------------------------------------- /demo/RoverPyIOSUI.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/demo/RoverPyIOSUI.mp4 -------------------------------------------------------------------------------- /demo/RoverPyProfile.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/demo/RoverPyProfile.mp4 -------------------------------------------------------------------------------- /demo/RoverPyWorkingRover.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/demo/RoverPyWorkingRover.mp4 -------------------------------------------------------------------------------- /fonts/LexendDeca-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/fonts/LexendDeca-Regular.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /fonts/Raleway-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/fonts/Raleway-SemiBold.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /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 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /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 | 8 | -------------------------------------------------------------------------------- /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.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoverPy/RoverPyApp/e39abf68aa9a7ed339963daeb5ef937431b9e30b/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 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sign_in 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/animations.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flare_flutter/flare_actor.dart'; 4 | import 'package:sign_in/auth/login.dart'; 5 | import 'package:sign_in/auth/register.dart'; 6 | 7 | class AuthDemo extends StatefulWidget { 8 | @override 9 | _AuthDemoState createState() => _AuthDemoState(); 10 | } 11 | 12 | class _AuthDemoState extends State 13 | with SingleTickerProviderStateMixin { 14 | String _animationName = "Flow"; 15 | Animation classificationAnimation; 16 | AnimationController controller; 17 | bool controly = false; 18 | void toggleView() { 19 | setState(() { 20 | controly = !controly; 21 | }); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | assert(() { 27 | if (controller == null) { 28 | throw FlutterError.fromParts([ 29 | ErrorSummary('AnimationController.dispose() called more than once.'), 30 | ]); 31 | } 32 | return true; 33 | }()); 34 | controller.dispose(); 35 | controller = null; 36 | super.dispose(); 37 | } 38 | 39 | Widget showWidget() { 40 | controller.forward(); 41 | return controly 42 | ? SlideTransition( 43 | position: classificationAnimation, 44 | child: (Container( 45 | alignment: Alignment.center, 46 | child: LoginScreen( 47 | toggleView: toggleView, 48 | controller: dispose, 49 | ), 50 | )), 51 | ) 52 | : SlideTransition( 53 | position: classificationAnimation, 54 | child: (Container( 55 | alignment: Alignment.center, 56 | child: Register(toggleView: toggleView, controller: dispose), 57 | )), 58 | ); 59 | } 60 | 61 | @override 62 | void initState() { 63 | super.initState(); 64 | controller = AnimationController( 65 | duration: Duration(milliseconds: 3000), vsync: this); 66 | classificationAnimation = Tween( 67 | begin: Offset(6.0, 0.0), 68 | end: Offset.zero, 69 | ).animate( 70 | CurvedAnimation( 71 | parent: controller, 72 | curve: Interval( 73 | 0.5, 74 | 0.7, 75 | curve: Curves.easeOut, 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | controller.forward(); 84 | return Scaffold( 85 | body: Stack( 86 | fit: StackFit.expand, 87 | children: [ 88 | FlareActor( 89 | 'assets/background.flr', 90 | alignment: Alignment.topCenter, 91 | fit: BoxFit.cover, 92 | animation: _animationName, 93 | callback: (String val) { 94 | setState(() { 95 | _animationName = "Flow"; 96 | }); 97 | }, 98 | ), 99 | showWidget(), 100 | ], 101 | overflow: Overflow.clip, 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/auth/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | dynamic textInputDecoration = InputDecoration( 5 | hintStyle: TextStyle( 6 | fontFamily: 'Lexend_Deca', 7 | ), 8 | filled: true, 9 | fillColor: Colors.grey[100], 10 | enabledBorder: OutlineInputBorder( 11 | borderSide: BorderSide.none, 12 | borderRadius: BorderRadius.all( 13 | const Radius.circular(50.0), 14 | ), 15 | ), 16 | focusedBorder: OutlineInputBorder( 17 | borderSide: BorderSide(color: Colors.deepPurple, width: 2.0), 18 | borderRadius: BorderRadius.all( 19 | const Radius.circular(50.0), 20 | ), 21 | ), 22 | 23 | ); -------------------------------------------------------------------------------- /lib/auth/login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:sign_in/auth/constants.dart'; 4 | import 'package:sign_in/models/user.dart'; 5 | import 'package:sign_in/services/AuthService.dart'; 6 | 7 | class LoginScreen extends StatefulWidget { 8 | final Function toggleView, controller; 9 | LoginScreen({this.toggleView, this.controller}); 10 | @override 11 | _LoginScreenState createState() => _LoginScreenState(); 12 | } 13 | 14 | class _LoginScreenState extends State { 15 | final AuthService _auth = AuthService(); 16 | final _formKey = GlobalKey(); 17 | String email = ""; 18 | String password = ""; 19 | String name = ""; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Container( 24 | padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 20.0), 25 | height: 500, 26 | child: Card( 27 | elevation: 8, 28 | shape: RoundedRectangleBorder( 29 | borderRadius: BorderRadius.circular(20.0), 30 | ), 31 | child: Form( 32 | key: _formKey, 33 | child: Padding( 34 | padding: const EdgeInsets.all(20.0), 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.spaceAround, 37 | children: [ 38 | SizedBox( 39 | height: 10.0, 40 | ), 41 | Row( 42 | children: [ 43 | Text( 44 | "RoverPy", 45 | style: TextStyle( 46 | fontSize: 30.0, 47 | color: Colors.amber, 48 | fontWeight: FontWeight.bold, 49 | letterSpacing: .6, 50 | ), 51 | ), 52 | ], 53 | ), 54 | 55 | TextFormField( 56 | decoration: textInputDecoration.copyWith( 57 | prefixIcon: Icon(Icons.supervised_user_circle), hintText: "Display Name"), 58 | validator: (val) { 59 | return null; 60 | }, 61 | onChanged: (val) { 62 | setState(() { 63 | name = val.isEmpty ? "User" : val; 64 | }); 65 | }, 66 | ), 67 | SizedBox( 68 | height: 20.0, 69 | ), 70 | TextFormField( 71 | decoration: textInputDecoration.copyWith( 72 | prefixIcon: Icon(Icons.email), hintText: "Email ID"), 73 | validator: (val) { 74 | return val.isEmpty ? "Enter a valid email" : null; 75 | }, 76 | onChanged: (val) { 77 | setState(() { 78 | email = val; 79 | }); 80 | }, 81 | ), 82 | SizedBox( 83 | height: 20.0, 84 | ), 85 | TextFormField( 86 | obscureText: true, 87 | decoration: textInputDecoration.copyWith( 88 | prefixIcon: Icon(Icons.lock), hintText: "Password"), 89 | validator: (val) { 90 | return val.length < 6 91 | ? "Oops! Password seems too short" 92 | : null; 93 | }, 94 | onChanged: (val) { 95 | setState(() { 96 | password = val; 97 | }); 98 | }, 99 | ), 100 | SizedBox( 101 | height: 20.0, 102 | ), 103 | Row( 104 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 105 | children: [ 106 | Container( 107 | child: InkWell( 108 | child: FlatButton( 109 | padding: EdgeInsets.all(0.0), 110 | shape: RoundedRectangleBorder( 111 | borderRadius: BorderRadius.circular(24.0)), 112 | onPressed: () async { 113 | //async action here 114 | if (_formKey.currentState.validate()) { 115 | dynamic res = await _auth.signInWithEmailAndPass( 116 | email, password, name); 117 | print(res); 118 | if (res == null) { 119 | Scaffold.of(context).showSnackBar(SnackBar( 120 | content: Text( 121 | "Something went wrong! Make sure your credentials are correct and retry!"), 122 | )); 123 | } else { 124 | widget.controller(); 125 | Scaffold.of(context).showSnackBar(SnackBar( 126 | content: Text("Successfully logged in!"), 127 | )); 128 | } 129 | } 130 | }, 131 | child: Ink( 132 | decoration: BoxDecoration( 133 | gradient: LinearGradient( 134 | colors: [Colors.teal, Colors.teal[200]], 135 | begin: Alignment.topLeft, 136 | end: Alignment.bottomRight, 137 | ), 138 | borderRadius: BorderRadius.circular(24.0)), 139 | child: Container( 140 | padding: EdgeInsets.symmetric( 141 | vertical: 12.0, horizontal: 30.0), 142 | child: Text( 143 | "SIGN IN", 144 | style: TextStyle( 145 | color: Colors.white, 146 | //fontWeight: FontWeight.bold, 147 | fontSize: 16.0, 148 | letterSpacing: 0.6, 149 | ), 150 | ), 151 | ), 152 | ), 153 | ), 154 | ), 155 | ), 156 | FlatButton( 157 | onPressed: () { 158 | widget.toggleView(); 159 | }, 160 | child: Text( 161 | 'Register?', 162 | ), 163 | ) 164 | ], 165 | ) 166 | ], 167 | ), 168 | ), 169 | ), 170 | ), 171 | ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /lib/auth/register.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:sign_in/auth/constants.dart'; 4 | import 'package:sign_in/services/AuthService.dart'; 5 | 6 | class Register extends StatefulWidget { 7 | final Function toggleView, controller; 8 | Register({this.toggleView, this.controller}); 9 | @override 10 | _RegisterState createState() => _RegisterState(); 11 | } 12 | 13 | class _RegisterState extends State { 14 | final AuthService _auth = AuthService(); 15 | final _formKey = GlobalKey(); 16 | String email = ""; 17 | String password = ""; 18 | String cnfPass = ""; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return SingleChildScrollView( 23 | child: Container( 24 | padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 20.0), 25 | height: 500, 26 | child: Card( 27 | elevation: 8, 28 | shape: RoundedRectangleBorder( 29 | borderRadius: BorderRadius.circular(20.0), 30 | ), 31 | child: Form( 32 | key: _formKey, 33 | child: Padding( 34 | padding: const EdgeInsets.all(20.0), 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.spaceAround, 37 | children: [ 38 | SizedBox( 39 | height: 10.0, 40 | ), 41 | Row( 42 | children: [ 43 | Text( 44 | "RoverPy", 45 | style: TextStyle( 46 | fontSize: 30.0, 47 | color: Colors.amber, 48 | fontWeight: FontWeight.bold, 49 | letterSpacing: .6, 50 | ), 51 | ), 52 | ], 53 | ), 54 | SizedBox( 55 | height: 5.0, 56 | ), 57 | TextFormField( 58 | decoration: textInputDecoration.copyWith( 59 | prefixIcon: Icon(Icons.email), hintText: "Email ID"), 60 | validator: (val) { 61 | return val.isEmpty ? "Enter a valid email" : null; 62 | }, 63 | onChanged: (val) { 64 | setState(() { 65 | email = val; 66 | }); 67 | }, 68 | ), 69 | SizedBox( 70 | height: 20.0, 71 | ), 72 | TextFormField( 73 | obscureText: true, 74 | decoration: textInputDecoration.copyWith( 75 | prefixIcon: Icon(Icons.lock), hintText: "Password"), 76 | validator: (val) { 77 | return val.length < 6 78 | ? "Oops! Password seems too short" 79 | : null; 80 | }, 81 | onChanged: (val) { 82 | setState(() { 83 | password = val; 84 | }); 85 | }, 86 | ), 87 | SizedBox( 88 | height: 20.0, 89 | ), 90 | TextFormField( 91 | obscureText: true, 92 | decoration: textInputDecoration.copyWith( 93 | prefixIcon: Icon(Icons.lock_outline), 94 | hintText: "Confirm Password"), 95 | validator: (val) { 96 | return val.compareTo(password) != 0 97 | ? "Uh oh, Passwords do not match!" 98 | : null; 99 | }, 100 | onChanged: (val) { 101 | setState(() { 102 | cnfPass = val; 103 | }); 104 | }, 105 | ), 106 | SizedBox( 107 | height: 20.0, 108 | ), 109 | Row( 110 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 111 | children: [ 112 | Container( 113 | child: InkWell( 114 | child: FlatButton( 115 | padding: EdgeInsets.all(0.0), 116 | shape: RoundedRectangleBorder( 117 | borderRadius: BorderRadius.circular(24.0)), 118 | onPressed: () async { 119 | //async action here 120 | if (_formKey.currentState.validate()) { 121 | dynamic res = await _auth 122 | .registerWithEmailAndPass(email, password); 123 | print(res); 124 | if (res == null) { 125 | Scaffold.of(context).showSnackBar(SnackBar( 126 | content: Text( 127 | "Something went wrong! Make sure your credentials are correct and retry!"), 128 | )); 129 | } else { 130 | widget.controller(); 131 | Scaffold.of(context).showSnackBar(SnackBar( 132 | content: 133 | Text("Account created successfully!"), 134 | )); 135 | } 136 | } 137 | }, 138 | child: Ink( 139 | decoration: BoxDecoration( 140 | gradient: LinearGradient( 141 | colors: [Colors.teal, Colors.teal[200]], 142 | begin: Alignment.topLeft, 143 | end: Alignment.bottomRight, 144 | ), 145 | borderRadius: BorderRadius.circular(24.0)), 146 | child: Container( 147 | padding: EdgeInsets.symmetric( 148 | vertical: 12.0, horizontal: 30.0), 149 | child: Text( 150 | "REGISTER", 151 | style: TextStyle( 152 | color: Colors.white, 153 | //fontWeight: FontWeight.bold, 154 | fontSize: 16.0, 155 | letterSpacing: 0.6, 156 | ), 157 | ), 158 | ), 159 | ), 160 | ), 161 | ), 162 | ), 163 | FlatButton( 164 | onPressed: () { 165 | widget.toggleView(); 166 | }, 167 | child: Text( 168 | 'Sign In?', 169 | ), 170 | ) 171 | ], 172 | ) 173 | ], 174 | ), 175 | ), 176 | ), 177 | ), 178 | ), 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/commons/animated_popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:quiver/async.dart'; 4 | import 'package:sign_in/utils/data.dart'; 5 | 6 | class AnimatedPopUp extends StatefulWidget { 7 | final List images; 8 | 9 | AnimatedPopUp({this.images}); 10 | @override 11 | _AnimatedPopUpState createState() => _AnimatedPopUpState(); 12 | } 13 | 14 | class _AnimatedPopUpState extends State { 15 | int elapsed = 0; 16 | 17 | void _incrementCounter() { 18 | 19 | final cd = CountdownTimer(Duration(seconds: widget.images.length-1), Duration(milliseconds: 500)); 20 | cd.listen((data) { 21 | setState(() { 22 | elapsed = cd.elapsed.inSeconds; 23 | print(elapsed); 24 | }); 25 | }, onDone: () { 26 | cd.cancel(); 27 | Navigator.pop(context); 28 | }); 29 | 30 | } 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {_incrementCounter();}); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return AnimatedSwitcher( 41 | child: Image( 42 | image: NetworkImage(widget.images[elapsed]), 43 | key: ValueKey(elapsed)), 44 | duration: Duration(seconds: 1), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/commons/collapsing_navigation_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:sign_in/commons/drawer_tile.dart'; 5 | import 'package:sign_in/models/models_export.dart'; 6 | import '../models/drawer_item.dart'; 7 | import 'package:sign_in/services/AuthService.dart'; 8 | 9 | import '../services/AuthService.dart'; 10 | 11 | int currSelected = 0; 12 | 13 | class CollapsingNavDrawer extends StatefulWidget { 14 | @override 15 | _CollapsingNavDrawerState createState() => _CollapsingNavDrawerState(); 16 | } 17 | 18 | class _CollapsingNavDrawerState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Container( 22 | color: Theme.of(context).backgroundColor, 23 | width: MediaQuery.of(context).size.width / 2, 24 | child: Column( 25 | children: [ 26 | Container( 27 | width: MediaQuery.of(context).size.width / 2, 28 | color: Theme.of(context).primaryColor, 29 | child: Column( 30 | children: [ 31 | SizedBox( 32 | height: 35.0, 33 | ), 34 | Container( 35 | height: 50.0, 36 | child: Text('RoverPy', 37 | style: Theme.of(context).textTheme.headline5), 38 | ), 39 | ], 40 | )), 41 | Expanded( 42 | child: ListView.builder( 43 | itemCount: drawerList.length, 44 | itemBuilder: (context, index) { 45 | return InkWell( 46 | onTap: () { 47 | setState(() { 48 | currSelected = index; 49 | }); 50 | Navigator.pushAndRemoveUntil( 51 | context, 52 | MaterialPageRoute( 53 | builder: (context) => drawerList[index].goToPage), 54 | (Route route) => false); 55 | }, 56 | child: DrawerTile( 57 | isSelected: index == currSelected, 58 | iconData: drawerList[index].icon, 59 | title: drawerList[index].title, 60 | ), 61 | ); 62 | }), 63 | ), 64 | GestureDetector( 65 | onTap: () { 66 | AuthService().logOut(); 67 | }, 68 | child: Container( 69 | child: Padding( 70 | padding: const EdgeInsets.all(8.0), 71 | child: Row( 72 | children: [ 73 | Padding( 74 | padding: const EdgeInsets.all(8.0), 75 | child: Icon( 76 | Icons.lock_open, 77 | color: Theme.of(context).primaryColor, 78 | ), 79 | ), 80 | Padding( 81 | padding: const EdgeInsets.all(8.0), 82 | child: Text( 83 | 'Log out', 84 | style: TextStyle( 85 | fontSize: 18.0, 86 | color: Theme.of(context).primaryColor, 87 | fontWeight: FontWeight.w400, 88 | ), 89 | ), 90 | ), 91 | ], 92 | ), 93 | )), 94 | ), 95 | SizedBox( 96 | height: 35.0, 97 | ), 98 | Container( 99 | child: Consumer( 100 | builder: (context, themeProvider, child) { 101 | return Row( 102 | mainAxisAlignment: MainAxisAlignment.center, 103 | children: [ 104 | Icon( 105 | Icons.brightness_7, 106 | size: 30.0, 107 | color: themeProvider.isDarkTheme 108 | ? Colors.grey 109 | : Theme.of(context).splashColor, 110 | ), 111 | Switch( 112 | value: themeProvider.isDarkTheme, 113 | onChanged: (value) { 114 | themeProvider.isDarkTheme = value; 115 | }, 116 | ), 117 | Icon( 118 | Icons.brightness_2, 119 | size: 30.0, 120 | color: themeProvider.isDarkTheme 121 | ? Theme.of(context).splashColor 122 | : Colors.grey, 123 | ) 124 | ], 125 | ); 126 | }, 127 | ), 128 | ), 129 | ], 130 | ), 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/commons/custom_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:url_launcher/url_launcher.dart'; 3 | 4 | class CustomCard extends StatefulWidget { 5 | final String name; 6 | final String image; 7 | final String url; 8 | final String subtitle; 9 | final int i; 10 | CustomCard({ this.name, this.image, this.url, this.subtitle, this.i}); 11 | @override 12 | _CustomCardState createState() => _CustomCardState(); 13 | } 14 | 15 | class _CustomCardState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return Card( 19 | child: Container( 20 | padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 12.0), 21 | child: ListTile( 22 | leading: Hero( 23 | tag: "Profile${widget.i}", 24 | child: Material( 25 | type: MaterialType.transparency, 26 | child: CircleAvatar( 27 | backgroundImage: NetworkImage(widget.image), 28 | radius: 20.0, 29 | ), 30 | ), 31 | ), 32 | title: Hero( 33 | tag: "Name${widget.i}", 34 | child: Material( 35 | type: MaterialType.transparency, 36 | child: Text( 37 | widget.name, 38 | style: TextStyle( 39 | fontSize: 20.0, 40 | color: Colors.black, 41 | ), 42 | ), 43 | ) 44 | ), 45 | trailing: InkWell( 46 | onTap: (){ 47 | launch(widget.url); 48 | }, 49 | child: CircleAvatar( 50 | backgroundColor: Colors.black, 51 | backgroundImage: AssetImage('assets/github_logo.png'), 52 | radius: 15.0, 53 | ), 54 | ) 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/commons/drawer_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DrawerTile extends StatefulWidget { 4 | final IconData iconData; 5 | final String title; 6 | final bool isSelected; 7 | 8 | DrawerTile({this.iconData, this.title, this.isSelected}); 9 | 10 | @override 11 | _DrawerTileState createState() => _DrawerTileState(); 12 | } 13 | 14 | class _DrawerTileState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | height: 50.0, 19 | child: Row( 20 | mainAxisAlignment: MainAxisAlignment.start, 21 | children: [ 22 | Padding( 23 | padding: const EdgeInsets.all(8.0), 24 | child: Icon(widget.iconData, color: widget.isSelected?Theme.of(context).splashColor: Theme.of(context).primaryColor,), 25 | ), 26 | Padding( 27 | padding: const EdgeInsets.all(8.0), 28 | child: Text( 29 | '${widget.title}', 30 | style: TextStyle( 31 | fontSize: 18.0, 32 | color: widget.isSelected?Theme.of(context).splashColor: Theme.of(context).primaryColor, 33 | fontWeight: widget.isSelected?FontWeight.w600:FontWeight.w400, 34 | ), 35 | ), 36 | ), 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/commons/stepper_control.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/physics.dart'; 4 | 5 | class StepperTouch extends StatefulWidget { 6 | StepperTouch({ 7 | Key key, 8 | this.size = 300.0, 9 | this.onEnd, 10 | this.onHoldDown, 11 | @required this.initialValue, 12 | @required this.positiveValue, 13 | @required this.negativeValue, 14 | this.direction = Axis.horizontal, 15 | this.withSpring = true, 16 | }) : super(key: key); 17 | 18 | /// the orientation of the stepper its horizontal or vertical. 19 | final Axis direction; 20 | 21 | final String initialValue; 22 | final String positiveValue; 23 | final String negativeValue; 24 | 25 | /// called whenever the stepper is released 26 | final ValueChanged onEnd; 27 | 28 | /// called whenever the stepper is moved 29 | final ValueChanged onHoldDown; 30 | 31 | /// if you want a springSimulation to happens the the user let go the stepper 32 | /// defaults to true 33 | final bool withSpring; 34 | final double size; 35 | final Color buttonsColor = Color.fromRGBO(0, 255, 255, 1); 36 | 37 | @override 38 | _Stepper2State createState() => _Stepper2State(); 39 | } 40 | 41 | class _Stepper2State extends State 42 | with SingleTickerProviderStateMixin { 43 | AnimationController _controller; 44 | Animation _animation; 45 | Color _color; 46 | String _value; 47 | double _startAnimationPosX; 48 | double _startAnimationPosY; 49 | 50 | @override 51 | void initState() { 52 | super.initState(); 53 | _value = widget.initialValue; 54 | _color = Color.fromRGBO(0, 255, 255, 1); 55 | _controller = 56 | AnimationController(vsync: this, lowerBound: -0.5, upperBound: 0.5); 57 | _controller.value = 0.0; 58 | _controller.addListener(() {}); 59 | 60 | if (widget.direction == Axis.horizontal) { 61 | _animation = Tween(begin: Offset(0.0, 0.0), end: Offset(0.5, 0.0)) 62 | .animate(_controller); 63 | } else { 64 | _animation = Tween(begin: Offset(0.0, 0.0), end: Offset(0.0, 1.5)) 65 | .animate(_controller); 66 | } 67 | } 68 | 69 | @override 70 | void dispose() { 71 | _controller?.dispose(); 72 | super.dispose(); 73 | } 74 | 75 | @override 76 | void didUpdateWidget(oldWidget) { 77 | super.didUpdateWidget(oldWidget); 78 | if (widget.direction == Axis.horizontal) { 79 | _animation = Tween(begin: Offset(0.0, 0.0), end: Offset(0.5, 0.0)) 80 | .animate(_controller); 81 | } else { 82 | _animation = Tween(begin: Offset(0.0, 0.0), end: Offset(0.0, 1.5)) 83 | .animate(_controller); 84 | } 85 | } 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | return FittedBox( 90 | child: Container( 91 | width: widget.direction == Axis.horizontal 92 | ? widget.size 93 | : widget.size / 2 - 30.0, 94 | height: widget.direction == Axis.horizontal 95 | ? widget.size / 2 - 30.0 96 | : widget.size, 97 | child: Material( 98 | type: MaterialType.canvas, 99 | clipBehavior: Clip.antiAlias, 100 | borderRadius: BorderRadius.circular(60.0), 101 | color: Colors.blue.withOpacity(0.2), 102 | child: Stack( 103 | alignment: Alignment.center, 104 | children: [ 105 | Positioned( 106 | left: widget.direction == Axis.horizontal ? 10.0 : null, 107 | bottom: widget.direction == Axis.horizontal ? null : 10.0, 108 | child: Icon( 109 | widget.direction == Axis.horizontal 110 | ? Icons.arrow_left 111 | : Icons.arrow_drop_down, 112 | size: 40.0, 113 | color: widget.buttonsColor), 114 | ), 115 | Positioned( 116 | right: widget.direction == Axis.horizontal ? 10.0 : null, 117 | top: widget.direction == Axis.horizontal ? null : 10.0, 118 | child: Icon( 119 | widget.direction == Axis.horizontal 120 | ? Icons.arrow_right 121 | : Icons.arrow_drop_up, 122 | size: 40.0, 123 | color: widget.buttonsColor), 124 | ), 125 | GestureDetector( 126 | onHorizontalDragStart: _onPanStart, 127 | onHorizontalDragUpdate: _onPanUpdate, 128 | onHorizontalDragEnd: _onPanEnd, 129 | child: SlideTransition( 130 | position: _animation, 131 | child: Material( 132 | shape: const CircleBorder(), 133 | child: AnimatedContainer( 134 | duration: Duration(milliseconds: 500), 135 | height: widget.size / 3, 136 | decoration: BoxDecoration( 137 | color: _color, 138 | shape: BoxShape.circle, 139 | ), 140 | curve: Curves.linear, 141 | ), 142 | elevation: 5.0, 143 | ), 144 | ), 145 | ), 146 | ], 147 | ), 148 | ), 149 | ), 150 | ); 151 | } 152 | 153 | double offsetFromGlobalPos(Offset globalPosition) { 154 | RenderBox box = context.findRenderObject() as RenderBox; 155 | Offset local = box.globalToLocal(globalPosition); 156 | _startAnimationPosX = ((local.dx * 0.75) / box.size.width) - 0.4; 157 | _startAnimationPosY = ((local.dy * 0.75) / box.size.height) - 0.4; 158 | if (widget.direction == Axis.horizontal) { 159 | return ((local.dx * 0.75) / box.size.width) - 0.4; 160 | } else { 161 | return ((local.dy * 0.75) / box.size.height) - 0.4; 162 | } 163 | } 164 | 165 | void _onPanStart(DragStartDetails details) { 166 | _controller.stop(); 167 | _controller.value = offsetFromGlobalPos(details.globalPosition); 168 | } 169 | 170 | void _onPanUpdate(DragUpdateDetails details) { 171 | _controller.value = offsetFromGlobalPos(details.globalPosition); 172 | setState(() { 173 | //int r = 0 + ((_controller.value * 2).abs() * 255).round(); 174 | int g = 255 - ((_controller.value * 2).abs() * 255).round(); 175 | _color = Color.fromRGBO(0, g, 255, 1); 176 | }); 177 | if (_controller.value <= -0.10) { 178 | setState(() => _value = widget.positiveValue); 179 | } else if (_controller.value >= 0.10) { 180 | setState(() => _value = widget.negativeValue); 181 | } 182 | if (widget.onHoldDown != null) { 183 | widget.onHoldDown(_value); 184 | } 185 | } 186 | 187 | void _onPanEnd(DragEndDetails details) { 188 | _controller.stop(); 189 | if (widget.withSpring) { 190 | final SpringDescription _kDefaultSpring = 191 | new SpringDescription.withDampingRatio( 192 | mass: 0.9, 193 | stiffness: 250.0, 194 | ratio: 0.6, 195 | ); 196 | if (widget.direction == Axis.horizontal) { 197 | _controller.animateWith( 198 | SpringSimulation(_kDefaultSpring, _startAnimationPosX, 0.0, 0.0)); 199 | } else { 200 | _controller.animateWith( 201 | SpringSimulation(_kDefaultSpring, _startAnimationPosY, 0.0, 0.0)); 202 | } 203 | } else { 204 | _controller.animateTo(0.0, 205 | curve: Curves.bounceOut, duration: Duration(milliseconds: 500)); 206 | } 207 | _value = widget.initialValue; 208 | if (widget.onEnd != null) { 209 | widget.onEnd(_value); 210 | } 211 | setState(() { 212 | _color = Color.fromRGBO(0, 255, 255, 1); 213 | }); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /lib/helpers/map_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'dart:ui'; 5 | import 'package:http/http.dart' as http; 6 | 7 | import 'package:fluster/fluster.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 10 | import 'package:sign_in/helpers/map_marker.dart'; 11 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 12 | 13 | /// In here we are encapsulating all the logic required to get marker icons from url images 14 | /// and to show clusters using the [Fluster] package. 15 | class MapHelper { 16 | /// If there is a cached file and it's not old returns the cached marker image file 17 | /// else it will download the image and save it on the temp dir and return that file. 18 | /// 19 | /// This mechanism is possible using the [DefaultCacheManager] package and is useful 20 | /// to improve load times on the next map loads, the first time will always take more 21 | /// time to download the file and set the marker image. 22 | /// 23 | /// You can resize the marker image by providing a [targetWidth]. 24 | static Future getMarkerImageFromUrl( 25 | String url, { 26 | int targetWidth, 27 | }) async { 28 | assert(url != null); 29 | 30 | // final File markerImageFile = await DefaultCacheManager().getSingleFile(url); 31 | 32 | http.Response res = await http.get(url); 33 | 34 | Uint8List markerImageBytes = res.bodyBytes; 35 | 36 | if (targetWidth != null) { 37 | markerImageBytes = await _resizeImageBytes( 38 | markerImageBytes, 39 | targetWidth, 40 | ); 41 | } 42 | 43 | return BitmapDescriptor.fromBytes(markerImageBytes); 44 | } 45 | 46 | /// Draw a [clusterColor] circle with the [clusterSize] text inside that is [width] wide. 47 | /// 48 | /// Then it will convert the canvas to an image and generate the [BitmapDescriptor] 49 | /// to be used on the cluster marker icons. 50 | static Future _getClusterMarker( 51 | int clusterSize, 52 | Color clusterColor, 53 | Color textColor, 54 | int width, 55 | ) async { 56 | assert(clusterSize != null); 57 | assert(clusterColor != null); 58 | assert(width != null); 59 | 60 | final PictureRecorder pictureRecorder = PictureRecorder(); 61 | final Canvas canvas = Canvas(pictureRecorder); 62 | final Paint paint = Paint()..color = clusterColor; 63 | final TextPainter textPainter = TextPainter( 64 | textDirection: TextDirection.ltr, 65 | ); 66 | 67 | final double radius = width / 2; 68 | 69 | canvas.drawCircle( 70 | Offset(radius, radius), 71 | radius, 72 | paint, 73 | ); 74 | 75 | textPainter.text = TextSpan( 76 | text: clusterSize.toString(), 77 | style: TextStyle( 78 | fontSize: radius - 5, 79 | fontWeight: FontWeight.bold, 80 | color: textColor, 81 | ), 82 | ); 83 | 84 | textPainter.layout(); 85 | textPainter.paint( 86 | canvas, 87 | Offset(radius - textPainter.width / 2, radius - textPainter.height / 2), 88 | ); 89 | 90 | final image = await pictureRecorder.endRecording().toImage( 91 | radius.toInt() * 2, 92 | radius.toInt() * 2, 93 | ); 94 | final data = await image.toByteData(format: ImageByteFormat.png); 95 | 96 | return BitmapDescriptor.fromBytes(data.buffer.asUint8List()); 97 | } 98 | 99 | /// Resizes the given [imageBytes] with the [targetWidth]. 100 | /// 101 | /// We don't want the marker image to be too big so we might need to resize the image. 102 | static Future _resizeImageBytes( 103 | Uint8List imageBytes, 104 | int targetWidth, 105 | ) async { 106 | assert(imageBytes != null); 107 | assert(targetWidth != null); 108 | 109 | final Codec imageCodec = await instantiateImageCodec( 110 | imageBytes, 111 | targetWidth: targetWidth, 112 | ); 113 | 114 | final FrameInfo frameInfo = await imageCodec.getNextFrame(); 115 | 116 | final ByteData byteData = await frameInfo.image.toByteData( 117 | format: ImageByteFormat.png, 118 | ); 119 | 120 | return byteData.buffer.asUint8List(); 121 | } 122 | 123 | /// Inits the cluster manager with all the [MapMarker] to be displayed on the map. 124 | /// Here we're also setting up the cluster marker itself, also with an [clusterImageUrl]. 125 | /// 126 | /// For more info about customizing your clustering logic check the [Fluster] constructor. 127 | static Future> initClusterManager( 128 | List markers, 129 | int minZoom, 130 | int maxZoom, 131 | ) async { 132 | assert(markers != null); 133 | assert(minZoom != null); 134 | assert(maxZoom != null); 135 | 136 | return Fluster( 137 | minZoom: minZoom, 138 | maxZoom: maxZoom, 139 | radius: 150, 140 | extent: 2048, 141 | nodeSize: 64, 142 | points: markers, 143 | createCluster: ( 144 | BaseCluster cluster, 145 | double lng, 146 | double lat, 147 | ) => 148 | MapMarker( 149 | id: cluster.id.toString(), 150 | position: LatLng(lat, lng), 151 | isCluster: cluster.isCluster, 152 | clusterId: cluster.id, 153 | pointsSize: cluster.pointsSize, 154 | childMarkerId: cluster.childMarkerId, 155 | ), 156 | ); 157 | } 158 | 159 | /// Gets a list of markers and clusters that reside within the visible bounding box for 160 | /// the given [currentZoom]. For more info check [Fluster.clusters]. 161 | static Future> getClusterMarkers( 162 | Fluster clusterManager, 163 | double currentZoom, 164 | Color clusterColor, 165 | Color clusterTextColor, 166 | int clusterWidth, 167 | ) { 168 | assert(currentZoom != null); 169 | assert(clusterColor != null); 170 | assert(clusterTextColor != null); 171 | assert(clusterWidth != null); 172 | 173 | if (clusterManager == null) return Future.value([]); 174 | 175 | return Future.wait(clusterManager.clusters( 176 | [-180, -85, 180, 85], currentZoom.toInt()).map((mapMarker) async { 177 | if (mapMarker.isCluster) { 178 | mapMarker.icon = await _getClusterMarker( 179 | mapMarker.pointsSize, 180 | clusterColor, 181 | clusterTextColor, 182 | clusterWidth, 183 | ); 184 | } 185 | 186 | return mapMarker.toMarker(); 187 | }).toList()); 188 | } 189 | } -------------------------------------------------------------------------------- /lib/helpers/map_marker.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluster/fluster.dart'; 2 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | /// [Fluster] can only handle markers that conform to the [Clusterable] abstract class. 6 | /// 7 | /// You can customize this class by adding more parameters that might be needed for 8 | /// your use case. For instance, you can pass an onTap callback or add an 9 | /// [InfoWindow] to your marker here, then you can use the [toMarker] method to convert 10 | /// this to a proper [Marker] that the [GoogleMap] can read. 11 | class MapMarker extends Clusterable { 12 | final String id; 13 | final LatLng position; 14 | BitmapDescriptor icon; 15 | 16 | MapMarker({ 17 | @required this.id, 18 | @required this.position, 19 | this.icon, 20 | isCluster = false, 21 | clusterId, 22 | pointsSize, 23 | childMarkerId, 24 | }) : super( 25 | markerId: id, 26 | latitude: position.latitude, 27 | longitude: position.longitude, 28 | isCluster: isCluster, 29 | clusterId: clusterId, 30 | pointsSize: pointsSize, 31 | childMarkerId: childMarkerId, 32 | ); 33 | 34 | Marker toMarker() => Marker( 35 | markerId: MarkerId(isCluster ? 'cl_$id' : id), 36 | position: LatLng( 37 | position.latitude, 38 | position.longitude, 39 | ), 40 | icon: icon, 41 | ); 42 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sign_in/wrapper.dart'; 3 | import './services/AuthService.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | 7 | void main() => runApp(MyApp()); 8 | 9 | class MyApp extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return StreamProvider.value( 13 | value: AuthService().userDetail, child: Wrapper()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/developer_info.dart: -------------------------------------------------------------------------------- 1 | class DevInfo { 2 | final String name; 3 | final String gitLink; 4 | final String imageLink; 5 | final String otherInfo; 6 | final Details deets; 7 | 8 | DevInfo( 9 | {this.name, this.gitLink, this.imageLink, this.otherInfo, this.deets}); 10 | } 11 | 12 | List developers = [ 13 | DevInfo( 14 | name: 'Miheer', 15 | gitLink: 'https://github.com/mimi69-38', 16 | imageLink: 'assets/Miheer.png', 17 | otherInfo: 'very USEFUL member of our team'), 18 | DevInfo( 19 | name: 'Pratit', 20 | gitLink: 'https://github.com/Pratit23', 21 | imageLink: 'assets/Pratit.png', 22 | otherInfo: 'an actually useful member of the team'), 23 | DevInfo( 24 | name: 'Aajinkya', 25 | gitLink: 'https://github.com/aajinkya1203', 26 | imageLink: 'assets/Aajinkya.jpg', 27 | otherInfo: 'another actually useful member of the team'), 28 | DevInfo( 29 | name: 'Aditi', 30 | gitLink: 'https://github.com/Aditi-Mohan', 31 | imageLink: 'assets/Aditi.png', 32 | otherInfo: 'we do bruh. about jiggy too!'), 33 | ]; 34 | 35 | class Details {} 36 | -------------------------------------------------------------------------------- /lib/models/drawer_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:sign_in/commons/stepper_control.dart'; 4 | import 'package:sign_in/services/MainPage.dart'; 5 | import '../pages/pages_export.dart'; 6 | 7 | class DrawerItem { 8 | final String title; 9 | final IconData icon; 10 | final Widget goToPage; 11 | 12 | DrawerItem({this.title, this.icon, this.goToPage}); 13 | } 14 | 15 | List drawerList = [ 16 | DrawerItem(icon: Icons.home, title: 'Home', goToPage: HomePage()), 17 | DrawerItem( 18 | icon: Icons.swap_calls, title: 'Control Rover', goToPage: ControlsPage()), 19 | DrawerItem(icon: Icons.spa, title: 'Report', goToPage: ReportPage()), 20 | DrawerItem(icon: Icons.spa, title: 'Bluetooth', goToPage: MainPage()), 21 | ]; 22 | -------------------------------------------------------------------------------- /lib/models/models_export.dart: -------------------------------------------------------------------------------- 1 | export 'drawer_item.dart'; 2 | export 'theme_model.dart'; -------------------------------------------------------------------------------- /lib/models/series_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:charts_flutter/flutter.dart' as charts; 2 | 3 | class SeriesModel { 4 | final String label; 5 | final int count; 6 | final charts.Color color; 7 | 8 | SeriesModel({this.label, this.count, this.color}); 9 | } 10 | 11 | class Label { 12 | final String label; 13 | final String url; 14 | final double confidence; 15 | 16 | Label({this.label, this.url, this.confidence}); 17 | } -------------------------------------------------------------------------------- /lib/models/theme_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | class ThemePreference { 5 | 6 | Future getThemePreference() async { 7 | SharedPreferences pref = await SharedPreferences.getInstance(); 8 | return pref.getBool('isDarkTheme')??false; 9 | } 10 | 11 | Future setThemePreference(bool value) async { 12 | SharedPreferences pref = await SharedPreferences.getInstance(); 13 | pref.setBool('isDarkTheme', value); 14 | } 15 | } 16 | 17 | class DarkThemeProvider extends ChangeNotifier { 18 | ThemePreference themePreference = ThemePreference(); 19 | static bool _isDarkTheme = false; 20 | 21 | bool get isDarkTheme => _isDarkTheme; 22 | 23 | set isDarkTheme(bool value) { 24 | _isDarkTheme = value; 25 | themePreference.setThemePreference(value); 26 | notifyListeners(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/models/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | final String uid; 3 | 4 | User({this.uid}) { 5 | currUser = this; 6 | } 7 | } 8 | 9 | User currUser; 10 | -------------------------------------------------------------------------------- /lib/pages/QuickAccess.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class QuickAccess 4 | { 5 | final String jobTitle; 6 | final String jobLocation; 7 | final String companyImageSource; 8 | final Color backgroundColor; 9 | 10 | QuickAccess(this.jobTitle, this.jobLocation, this.companyImageSource, this.backgroundColor); 11 | } -------------------------------------------------------------------------------- /lib/pages/Specific.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sign_in/models/developer_info.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | class Specific extends StatefulWidget { 6 | final DevInfo info; 7 | final int i; 8 | final String url; 9 | Specific({this.info, this.i, this.url}); 10 | @override 11 | _SpecificState createState() => _SpecificState(); 12 | } 13 | 14 | class _SpecificState extends State { 15 | List colorList = [ 16 | new Color(0xffff5f6d), 17 | new Color(0xffffc371), 18 | new Color(0xff43cea2), 19 | new Color(0xff185a9d), 20 | new Color(0xff4568dc), 21 | new Color(0xffb06ab3), 22 | new Color(0xffeecda3), 23 | new Color(0xffef629f) 24 | ]; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | backgroundColor: new Color(0xff333333), 30 | body: Stack( 31 | // alignment: Alignment.center, 32 | children: [ 33 | Container( 34 | width: MediaQuery.of(context).size.width, 35 | height: 1 / 3 * MediaQuery.of(context).size.height, 36 | decoration: BoxDecoration( 37 | gradient: LinearGradient( 38 | begin: Alignment.topRight, 39 | end: Alignment.bottomLeft, 40 | colors: [colorList[2*(widget.i)], colorList[2*(widget.i)+1]], 41 | ), 42 | borderRadius: BorderRadius.only( 43 | bottomLeft: Radius.circular(24.0), 44 | bottomRight: Radius.circular(24.0), 45 | ), 46 | ), 47 | ), 48 | Positioned( 49 | top: 1 / 3 * MediaQuery.of(context).size.height - 41.5, 50 | left: 1 / 2 * MediaQuery.of(context).size.width - 41.5, 51 | child: Hero( 52 | tag: "Profile${widget.i}", 53 | child: Material( 54 | type: MaterialType.transparency, 55 | child: CircleAvatar( 56 | backgroundImage: AssetImage(widget.info.imageLink), 57 | radius: 45.0, 58 | ), 59 | ), 60 | ), 61 | ), 62 | Row( 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | children: [ 65 | Column( 66 | mainAxisAlignment: MainAxisAlignment.center, 67 | children: [ 68 | Hero( 69 | tag: "Name${widget.i}", 70 | child: Material( 71 | type: MaterialType.transparency, 72 | child: Text( 73 | widget.info.name, 74 | style: TextStyle( 75 | fontSize: 40.0, 76 | color: Colors.white, 77 | ), 78 | ), 79 | ), 80 | ), 81 | Text(widget.info.otherInfo.toString(), 82 | style: TextStyle( 83 | fontSize: 16.0, 84 | color: new Color(0xffff958d), 85 | )), 86 | ]), 87 | ], 88 | ), 89 | ], 90 | ), 91 | floatingActionButton: FloatingActionButton( 92 | heroTag: 'fab', 93 | onPressed: () { 94 | launch(widget.url); 95 | }, 96 | tooltip: "Github", 97 | child: Icon(Icons.code), 98 | ), 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/pages/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:sign_in/commons/custom_card.dart'; 4 | import 'package:sign_in/models/developer_info.dart'; 5 | import 'package:sign_in/pages/Specific.dart'; 6 | 7 | class AboutUs extends StatefulWidget { 8 | @override 9 | _AboutUsState createState() => _AboutUsState(); 10 | } 11 | 12 | class _AboutUsState extends State { 13 | 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | backgroundColor: Theme.of(context).backgroundColor, 19 | body: Column( 20 | children: [ 21 | SizedBox(height: 40.0,), 22 | Card( 23 | color: Colors.transparent, 24 | child: ClipPath( 25 | clipper: ShapeBorderClipper( 26 | shape: RoundedRectangleBorder( 27 | borderRadius: BorderRadius.circular(20.0) 28 | ), 29 | ), 30 | child: Container( 31 | height: MediaQuery.of(context).size.height/4, 32 | width: MediaQuery.of(context).size.width, 33 | padding: EdgeInsets.symmetric(vertical: 50.0, horizontal: 40.0), 34 | decoration: BoxDecoration( 35 | image: DecorationImage( 36 | image: AssetImage('assets/aboutUs.png'), 37 | fit: BoxFit.cover 38 | ) 39 | ), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | SizedBox(height: 20,), 44 | Container( 45 | margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 5.0), 46 | decoration: BoxDecoration( 47 | border: Border.all( 48 | color: Colors.black, 49 | width: 3.0, 50 | ), 51 | borderRadius: BorderRadius.circular(20.0) 52 | ), 53 | child: Text( 54 | "About Us", 55 | style: TextStyle( 56 | fontFamily: 'Lexend_Deca', 57 | color: Colors.black, 58 | fontWeight: FontWeight.bold, 59 | fontSize: 35.0 60 | ), 61 | ), 62 | ), 63 | ], 64 | ), 65 | ), 66 | ), 67 | ), 68 | Expanded( 69 | child: ListView.builder( 70 | itemCount: developers.length, 71 | itemBuilder: (context, index) { 72 | // return InkWell( 73 | // onTap: (){ 74 | // Navigator.push(context, MaterialPageRoute(builder: (_)=>Specific( 75 | // info: developers[index], 76 | // i:index, 77 | // url: developers[index].gitLink, 78 | // ) 79 | // ) 80 | // ); 81 | // }, 82 | // child: CustomCard( 83 | // i: index, 84 | // name: developers[index].name, 85 | // image: developers[index].imageLink, 86 | // subtitle: developers[index].otherInfo, 87 | // url: developers[index].gitLink, 88 | // ), 89 | // ); 90 | return SafeArea( 91 | child: Column( 92 | children: [ 93 | CupertinoButton( 94 | child: Container( 95 | height: 150, 96 | width: MediaQuery.of(context).size.width, 97 | decoration: BoxDecoration( 98 | image: DecorationImage( 99 | image: AssetImage("assets/RoverDev$index.jpg"), 100 | fit: BoxFit.cover, 101 | ), 102 | borderRadius: BorderRadius.circular(12), 103 | ), 104 | child: Row( 105 | crossAxisAlignment: CrossAxisAlignment.start, 106 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 107 | children: [ 108 | Container( 109 | margin: EdgeInsets.fromLTRB(15, 15, 0, 0), 110 | child: Hero( 111 | tag: "Name$index", 112 | child: Material( 113 | type: MaterialType.transparency, 114 | child: Text( 115 | developers[index].name, 116 | style: TextStyle( 117 | color: Colors.white, 118 | fontSize: 20, 119 | fontWeight: FontWeight.w600), 120 | ), 121 | ), 122 | ), 123 | ), 124 | Container( 125 | // height: 50.0, 126 | // alignment: Alignment.centerLeft, 127 | margin: EdgeInsets.fromLTRB(0, 10, 15, 0), 128 | child: Hero( 129 | tag: "Profile$index", 130 | child: Material( 131 | type: MaterialType.transparency, 132 | child: CircleAvatar( 133 | backgroundImage: AssetImage(developers[index].imageLink), 134 | radius: 20.0, 135 | ), 136 | ), 137 | ), 138 | ) 139 | ], 140 | ), 141 | ), 142 | onPressed: () { 143 | Navigator.push(context, MaterialPageRoute(builder: (_)=>Specific( 144 | info: developers[index], 145 | i:index, 146 | url: developers[index].gitLink, 147 | ) 148 | ) 149 | ); 150 | }, 151 | ), 152 | ], 153 | ), 154 | ); 155 | }, 156 | ), 157 | ), 158 | ], 159 | ), 160 | floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, 161 | floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, 162 | floatingActionButton: FloatingActionButton( 163 | heroTag: 'fab', 164 | onPressed: () { 165 | Navigator.of(context).pop(); 166 | }, 167 | elevation: 5.0, 168 | tooltip: "Go back", 169 | child: Icon(Icons.arrow_back), 170 | ), 171 | ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /lib/pages/constants_profile.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | final Color secondaryColor = Color(0xFFF0F0F0); 4 | final Color primaryColor = Color(0xFFFF7E7E); 5 | 6 | final Color lightYellowColor = Color(0xFFFFF9DE); 7 | final Color lightGreenColor = Color(0xFFDEFFDF); 8 | final Color lightPurpleColor = Color(0xFFEBDEFF); 9 | -------------------------------------------------------------------------------- /lib/pages/map_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:fluster/fluster.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 6 | import 'package:intl/intl.dart'; 7 | import 'package:sign_in/helpers/map_helper.dart'; 8 | import 'package:sign_in/helpers/map_marker.dart'; 9 | import 'package:cloud_firestore/cloud_firestore.dart'; 10 | import 'package:sign_in/models/user.dart'; 11 | 12 | class MapPage extends StatefulWidget { 13 | @override 14 | _MapPageState createState() => _MapPageState(); 15 | } 16 | 17 | class _MapPageState extends State { 18 | final Completer _mapController = Completer(); 19 | 20 | /// Set of displayed markers and cluster markers on the map 21 | final Set _markers = Set(); 22 | 23 | /// Minimum zoom at which the markers will cluster 24 | final int _minClusterZoom = 0; 25 | 26 | /// Maximum zoom at which the markers will cluster 27 | final int _maxClusterZoom = 19; 28 | 29 | /// [Fluster] instance used to manage the clusters 30 | Fluster _clusterManager; 31 | 32 | /// Current map zoom. Initial zoom will be 15, street level 33 | double _currentZoom = 15; 34 | 35 | /// Map loading flag 36 | bool _isMapLoading = true; 37 | 38 | /// Markers loading flag 39 | bool _areMarkersLoading = true; 40 | 41 | /// Url image used on normal markers 42 | final String _markerImageUrl = 43 | 'https://firebasestorage.googleapis.com/v0/b/roverpy-aamp.appspot.com/o/map_icon_rs.png?alt=media&token=f8fb0564-30f1-4f84-b14a-a02f8d731763'; 44 | 45 | final String _unhealthyMarkerImageUrl = 'https://firebasestorage.googleapis.com/v0/b/roverpy-aamp.appspot.com/o/map_icon_un.png?alt=media&token=af8b990f-6cba-4932-99a8-cbc85e36d724'; 46 | 47 | /// Color of the cluster circle 48 | final Color _clusterColor = Colors.blue; 49 | 50 | /// Color of the cluster text 51 | final Color _clusterTextColor = Colors.white; 52 | 53 | final List _labels = []; 54 | 55 | /// Example marker coordinates 56 | final List _markerLocations = [ 57 | // LatLng(41.147125, -8.611249), 58 | // LatLng(41.145599, -8.610691), 59 | // LatLng(41.145645, -8.614761), 60 | // LatLng(41.146775, -8.614913), 61 | // LatLng(41.146982, -8.615682), 62 | // LatLng(41.140558, -8.611530), 63 | // LatLng(41.138393, -8.608642), 64 | // LatLng(41.137860, -8.609211), 65 | // LatLng(41.138344, -8.611236), 66 | // LatLng(41.139813, -8.609381), 67 | ]; 68 | 69 | /// Called when the Google Map widget is created. Updates the map loading state 70 | /// and inits the markers. 71 | void _onMapCreated(GoogleMapController controller) { 72 | _mapController.complete(controller); 73 | 74 | setState(() { 75 | _isMapLoading = false; 76 | }); 77 | 78 | _initMarkers(); 79 | } 80 | 81 | /// Inits [Fluster] and all the markers with network images and updates the loading state. 82 | void _initMarkers() async { 83 | final List markers = []; 84 | String lastUsed; 85 | 86 | DocumentSnapshot userDoc = await Firestore.instance.collection('users').document('HHJjcEassOW3nRJEE65tYXmTJzn2').get(); 87 | lastUsed = userDoc.data['lastUsed']; 88 | print(lastUsed); 89 | 90 | QuerySnapshot docs = await Firestore.instance.collection('users').document('HHJjcEassOW3nRJEE65tYXmTJzn2').collection(lastUsed).getDocuments(); 91 | 92 | // docs.documents.forEach((element) { 93 | // _markerLocations.add(LatLng(element.data['lat'], element.data['lng'])); 94 | // _labels.add(element.data['label']); 95 | // }); 96 | 97 | for (int i=0; i< docs.documents.length; i++) { 98 | print('${docs.documents[i].data['lat']} ${docs.documents[i].data['lng']}'); 99 | if (docs.documents[i].documentID != 'report') { 100 | LatLng markerLocation = LatLng(docs.documents[i].data['lat'], docs.documents[i].data['lng']); 101 | _markerLocations.add(markerLocation); 102 | 103 | final BitmapDescriptor markerImage = 104 | await MapHelper.getMarkerImageFromUrl(docs.documents[i].data['label'] == 'healthy'? _markerImageUrl: _unhealthyMarkerImageUrl);//TODO: Filter based on label 105 | 106 | markers.add( 107 | MapMarker( 108 | id: i.toString(), 109 | position: markerLocation, 110 | icon: markerImage, 111 | ), 112 | ); 113 | } 114 | } 115 | 116 | _clusterManager = await MapHelper.initClusterManager( 117 | markers, 118 | _minClusterZoom, 119 | _maxClusterZoom, 120 | ); 121 | 122 | await _updateMarkers(); 123 | } 124 | 125 | /// Gets the markers and clusters to be displayed on the map for the current zoom level and 126 | /// updates state. 127 | Future _updateMarkers([double updatedZoom]) async { 128 | if (_clusterManager == null || updatedZoom == _currentZoom) return; 129 | 130 | if (updatedZoom != null) { 131 | _currentZoom = updatedZoom; 132 | } 133 | 134 | setState(() { 135 | _areMarkersLoading = true; 136 | }); 137 | 138 | final updatedMarkers = await MapHelper.getClusterMarkers( 139 | _clusterManager, 140 | _currentZoom, 141 | _clusterColor, 142 | _clusterTextColor, 143 | 80, 144 | ); 145 | 146 | _markers 147 | ..clear() 148 | ..addAll(updatedMarkers); 149 | 150 | setState(() { 151 | _areMarkersLoading = false; 152 | }); 153 | } 154 | 155 | @override 156 | Widget build(BuildContext context) { 157 | return Scaffold( 158 | // appBar: AppBar( 159 | // title: Text('Map Page'), 160 | // ), 161 | body: Stack( 162 | children: [ 163 | // Google Map widget 164 | Opacity( 165 | opacity: _isMapLoading ? 0 : 1, 166 | child: GoogleMap( 167 | mapToolbarEnabled: false, 168 | initialCameraPosition: CameraPosition( 169 | target: LatLng(41.143029, -8.611274), 170 | zoom: _currentZoom, 171 | ), 172 | markers: _markers, 173 | onMapCreated: (controller) => _onMapCreated(controller), 174 | onCameraMove: (position) => _updateMarkers(position.zoom), 175 | ), 176 | ), 177 | 178 | // Map loading indicator 179 | Opacity( 180 | opacity: _isMapLoading ? 1 : 0, 181 | child: Center(child: CircularProgressIndicator()), 182 | ), 183 | 184 | // Map markers loading indicator 185 | if (_areMarkersLoading) 186 | Padding( 187 | padding: const EdgeInsets.all(8.0), 188 | child: Align( 189 | alignment: Alignment.topCenter, 190 | child: Card( 191 | elevation: 2, 192 | color: Colors.grey.withOpacity(0.9), 193 | child: Padding( 194 | padding: const EdgeInsets.all(4), 195 | child: Text( 196 | 'Loading', 197 | style: TextStyle(color: Colors.white), 198 | ), 199 | ), 200 | ), 201 | ), 202 | ), 203 | ], 204 | ), 205 | floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, 206 | floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, 207 | floatingActionButton: FloatingActionButton( 208 | heroTag: 'fab', 209 | onPressed: () { 210 | Navigator.of(context).pop(); 211 | }, 212 | elevation: 5.0, 213 | tooltip: "Go back", 214 | child: Icon(Icons.arrow_back), 215 | ), 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/pages/pages_export.dart: -------------------------------------------------------------------------------- 1 | export 'home_page.dart'; 2 | export 'controls_page.dart'; 3 | export 'report_page.dart'; -------------------------------------------------------------------------------- /lib/pages/process_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:ui'; 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_ml_custom/firebase_ml_custom.dart'; 7 | import 'package:firebase_storage/firebase_storage.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/services.dart'; 10 | import 'package:sign_in/commons/animated_popup.dart'; 11 | import 'package:sign_in/models/user.dart'; 12 | import 'package:tflite/tflite.dart'; 13 | import 'package:path_provider/path_provider.dart'; 14 | import 'package:http/http.dart' as http; 15 | 16 | class ProcessingPage extends StatefulWidget { 17 | @override 18 | _ProcessingPageState createState() => _ProcessingPageState(); 19 | } 20 | 21 | class _ProcessingPageState extends State { 22 | final _firestore = Firestore.instance; 23 | final _storage = FirebaseStorage.instance; 24 | String _loaded; 25 | List _imgUrls = []; 26 | List _images; 27 | List> _labels = []; 28 | bool _isLoaded = false; 29 | String _currImg = 30 | 'https://www.generationsforpeace.org/wp-content/uploads/2018/03/empty.jpg'; 31 | 32 | Future loadModel() async { 33 | final modelFile = await loadModelFromFirebase(); 34 | _loaded = await loadTFLiteModel(modelFile); 35 | print(_loaded); 36 | } 37 | 38 | /// Downloads custom model from the Firebase console and return its file. 39 | /// located on the mobile device. 40 | static Future loadModelFromFirebase() async { 41 | try { 42 | // Create model with a name that is specified in the Firebase console 43 | final model = FirebaseCustomRemoteModel('TomatoModel'); 44 | 45 | // Specify conditions when the model can be downloaded. 46 | // If there is no wifi access when the app is started, 47 | // this app will continue loading until the conditions are satisfied. 48 | final conditions = FirebaseModelDownloadConditions( 49 | androidRequireWifi: true, 50 | iosAllowCellularAccess: false, 51 | iosAllowBackgroundDownloading: true, 52 | androidRequireDeviceIdle: false, 53 | ); 54 | 55 | // Create model manager associated with default Firebase App instance. 56 | final modelManager = FirebaseModelManager.instance; 57 | 58 | // Begin downloading and wait until the model is downloaded successfully. 59 | await modelManager.download(model, conditions); 60 | assert(await modelManager.isModelDownloaded(model) == true); 61 | 62 | // Get latest model file to use it for inference by the interpreter. 63 | var modelFile = await modelManager.getLatestModelFile(model); 64 | assert(modelFile != null); 65 | return modelFile; 66 | } catch (exception) { 67 | print('Failed on loading your model from Firebase: $exception'); 68 | print('The program will not be resumed'); 69 | rethrow; 70 | } 71 | } 72 | 73 | /// Loads the model into some TF Lite interpreter. 74 | /// In this case interpreter provided by tflite plugin. 75 | static Future loadTFLiteModel(File modelFile) async { 76 | try { 77 | final appDirectory = await getApplicationDocumentsDirectory(); 78 | final labelsData = await rootBundle.load("assets/TomatoModel.txt"); 79 | final labelsFile = await File(appDirectory.path + "/TomatoModel.txt") 80 | .writeAsBytes(labelsData.buffer 81 | .asUint8List(labelsData.offsetInBytes, labelsData.lengthInBytes)); 82 | 83 | assert(await Tflite.loadModel( 84 | model: modelFile.path, 85 | labels: labelsFile.path, 86 | isAsset: false, 87 | ) == 88 | "success"); 89 | return "Model is loaded"; 90 | } catch (exception) { 91 | print( 92 | 'Failed on loading your model to the TFLite interpreter: $exception'); 93 | print('The program will not be resumed'); 94 | rethrow; 95 | } 96 | } 97 | 98 | Future> getImages() async { 99 | DocumentSnapshot doc = await _firestore 100 | .collection('users') 101 | .document('HHJjcEassOW3nRJEE65tYXmTJzn2') 102 | .get(); 103 | // print(doc.data['urls'].length); 104 | _imgUrls = doc.data['urls']; 105 | return doc.data['urls']; 106 | } 107 | 108 | Future urlToFile(String imageUrl) async { 109 | // generate random number. 110 | var rng = new Random(); 111 | // get temporary directory of device. 112 | Directory tempDir = await getTemporaryDirectory(); 113 | // get temporary path from temporary directory. 114 | String tempPath = tempDir.path; 115 | // create a new file in temporary path with random file name. 116 | File file = new File('$tempPath' + (rng.nextInt(100)).toString() + '.png'); 117 | print('$tempPath' + (rng.nextInt(100)).toString() + '.png'); 118 | // call http.get method and pass imageUrl into it to get response. 119 | http.Response response = await http.get(imageUrl); 120 | // write bodyBytes received in response to file. 121 | await file.writeAsBytes(response.bodyBytes); 122 | // now return the file which is created with random name in 123 | // temporary directory and image bytes from response is written to // that file. 124 | return file; 125 | } 126 | 127 | Future> getImageLabels(File img) async { 128 | try { 129 | final image = img; 130 | if (image == null) { 131 | return null; 132 | } 133 | 134 | var labels = List.from(await Tflite.runModelOnImage( 135 | path: img.path, 136 | imageStd: 127.5, 137 | threshold: 0.8, 138 | )); 139 | // print("label:"); 140 | // print(labels); 141 | return labels; 142 | } catch (exception) { 143 | print("Failed on getting your image and it's labels: $exception"); 144 | print('Continuing with the program...'); 145 | rethrow; 146 | } 147 | } 148 | 149 | Future processImg(List docs) async { 150 | await Future.forEach(docs, (element) async { 151 | print('img: ${element.data['img']}'); 152 | File img = await urlToFile(element.data['img']); 153 | List lb = await getImageLabels(img); 154 | _labels.add(lb); 155 | print(lb); 156 | // .then((value) => getImageLabels(value)) 157 | // .then((value) { 158 | // print(value[0]); 159 | // _labels.add(value); 160 | // }); 161 | }); 162 | } 163 | 164 | @override 165 | Widget build(BuildContext context) { 166 | return Scaffold( 167 | appBar: AppBar( 168 | title: Text('Processing'), 169 | ), 170 | body: FutureBuilder( 171 | future: loadModel(), 172 | builder: (context, snap) { 173 | if (snap.connectionState == ConnectionState.waiting) 174 | return CircularProgressIndicator(); 175 | else { 176 | return StreamBuilder( 177 | stream: _firestore 178 | .collection('users') 179 | .document('HHJjcEassOW3nRJEE65tYXmTJzn2') 180 | .collection('urls') 181 | .snapshots(), 182 | builder: (context, snapshot) { 183 | if (!snapshot.hasData) 184 | return CircularProgressIndicator(); 185 | else 186 | return FutureBuilder( 187 | future: processImg(snapshot.data.documents), 188 | builder: (context, snaps) { 189 | if (snaps.connectionState == ConnectionState.done) { 190 | return ListView.builder( 191 | itemCount: _labels.length, 192 | itemBuilder: (context, index) { 193 | return Text( 194 | "${_labels[index]}", 195 | style: TextStyle(color: Colors.black), 196 | ); 197 | }); 198 | } 199 | else { 200 | List urls = snapshot.data.documents.map((e) => e.data['img'].toString()).toList(); 201 | print('len: ${urls.length}'); 202 | showAnimation(urls); 203 | return Container(); 204 | } 205 | }, 206 | ); 207 | }); 208 | } 209 | }, 210 | ), 211 | ); 212 | } 213 | 214 | showAnimation(List urls) async { 215 | await Future.delayed(Duration(microseconds: 1)); 216 | showDialog( 217 | context: context, 218 | builder: (BuildContext context) { 219 | return AlertDialog( 220 | content: AnimatedPopUp(images: urls), 221 | ); 222 | }); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/pages/report_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sign_in/commons/collapsing_navigation_drawer.dart'; 3 | 4 | class ReportPage extends StatefulWidget { 5 | @override 6 | _ReportPageState createState() => _ReportPageState(); 7 | } 8 | 9 | class _ReportPageState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | backgroundColor: Theme.of(context).backgroundColor, 14 | appBar: AppBar( 15 | title: Text( 16 | 'Reports', 17 | style: Theme.of(context).textTheme.headline, 18 | ), 19 | ), 20 | body: Container(), 21 | drawer: CollapsingNavDrawer(), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/pages/shh_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'ssh.dart'; 3 | 4 | class SSHPage extends StatefulWidget { 5 | @override 6 | _SSHPageState createState() => _SSHPageState(); 7 | } 8 | 9 | class _SSHPageState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | body: Center( 14 | child: FlatButton( 15 | child: Text('Click'), 16 | onPressed: () { 17 | var client = SSHClient( 18 | host: 'ip_address', 19 | port: 3000, 20 | username: '', 21 | passwordOrKey: '', 22 | ); 23 | client.connect(); 24 | client.execute('ps'); 25 | }, 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/pages/ssh.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | const MethodChannel _channel = const MethodChannel('ssh'); 7 | const EventChannel _eventChannel = const EventChannel('shell_sftp'); 8 | Stream _onStateChanged; 9 | 10 | Stream get onStateChanged { 11 | if (_onStateChanged == null) { 12 | _onStateChanged = 13 | _eventChannel.receiveBroadcastStream().map((dynamic event) => event); 14 | } 15 | return _onStateChanged; 16 | } 17 | 18 | typedef void Callback(dynamic result); 19 | 20 | class SSHClient { 21 | String id; 22 | String host; 23 | int port; 24 | String username; 25 | dynamic passwordOrKey; 26 | StreamSubscription stateSubscription; 27 | Callback shellCallback; 28 | Callback uploadCallback; 29 | Callback downloadCallback; 30 | 31 | SSHClient({ 32 | @required this.host, 33 | @required this.port, 34 | @required this.username, 35 | @required 36 | this.passwordOrKey, // password or {privateKey: value, [publicKey: value, passphrase: value]} 37 | }) { 38 | var uuid = new Uuid(); 39 | id = uuid.v4(); 40 | stateSubscription = onStateChanged.listen((dynamic result) { 41 | _parseOutput(result); 42 | }); 43 | } 44 | 45 | _parseOutput(dynamic result) { 46 | switch (result["name"]) { 47 | case "Shell": 48 | if (shellCallback != null && result["key"] == id) 49 | shellCallback(result["value"]); 50 | break; 51 | case "DownloadProgress": 52 | if (downloadCallback != null && result["key"] == id) 53 | downloadCallback(result["value"]); 54 | break; 55 | case "UploadProgress": 56 | if (uploadCallback != null && result["key"] == id) 57 | uploadCallback(result["value"]); 58 | break; 59 | } 60 | } 61 | 62 | Future connect() async { 63 | var result = await _channel.invokeMethod('connectToHost', { 64 | "id": id, 65 | "host": host, 66 | "port": port, 67 | "username": username, 68 | "passwordOrKey": passwordOrKey, 69 | }); 70 | return result; 71 | } 72 | 73 | Future execute(String cmd) async { 74 | var result = await _channel.invokeMethod('execute', { 75 | "id": id, 76 | "cmd": cmd, 77 | }); 78 | return result; 79 | } 80 | 81 | Future portForwardL(int rport, int lport, String rhost) async { 82 | var result = await _channel.invokeMethod('portForwardL', { 83 | "id": id, 84 | "rhost": rhost, 85 | "rport": rport, 86 | "lport": lport 87 | }); 88 | return result; 89 | } 90 | 91 | Future startShell({ 92 | String ptyType = "vanilla", // vanilla, vt100, vt102, vt220, ansi, xterm 93 | Callback callback, 94 | }) async { 95 | shellCallback = callback; 96 | var result = await _channel.invokeMethod('startShell', { 97 | "id": id, 98 | "ptyType": ptyType, 99 | }); 100 | return result; 101 | } 102 | 103 | Future writeToShell(String cmd) async { 104 | var result = await _channel.invokeMethod('writeToShell', { 105 | "id": id, 106 | "cmd": cmd, 107 | }); 108 | return result; 109 | } 110 | 111 | Future closeShell() async { 112 | shellCallback = null; 113 | await _channel.invokeMethod('closeShell', { 114 | "id": id, 115 | }); 116 | } 117 | 118 | Future connectSFTP() async { 119 | var result = await _channel.invokeMethod('connectSFTP', { 120 | "id": id, 121 | }); 122 | return result; 123 | } 124 | 125 | Future sftpLs([String path = '.']) async { 126 | var result = await _channel.invokeMethod('sftpLs', { 127 | "id": id, 128 | "path": path, 129 | }); 130 | return result; 131 | } 132 | 133 | Future sftpRename({ 134 | @required String oldPath, 135 | @required String newPath, 136 | }) async { 137 | var result = await _channel.invokeMethod('sftpRename', { 138 | "id": id, 139 | "oldPath": oldPath, 140 | "newPath": newPath, 141 | }); 142 | return result; 143 | } 144 | 145 | Future sftpMkdir(String path) async { 146 | var result = await _channel.invokeMethod('sftpMkdir', { 147 | "id": id, 148 | "path": path, 149 | }); 150 | return result; 151 | } 152 | 153 | Future sftpRm(String path) async { 154 | var result = await _channel.invokeMethod('sftpRm', { 155 | "id": id, 156 | "path": path, 157 | }); 158 | return result; 159 | } 160 | 161 | Future sftpRmdir(String path) async { 162 | var result = await _channel.invokeMethod('sftpRmdir', { 163 | "id": id, 164 | "path": path, 165 | }); 166 | return result; 167 | } 168 | 169 | Future sftpDownload({ 170 | @required String path, 171 | @required String toPath, 172 | Callback callback, 173 | }) async { 174 | downloadCallback = callback; 175 | var result = await _channel.invokeMethod('sftpDownload', { 176 | "id": id, 177 | "path": path, 178 | "toPath": toPath, 179 | }); 180 | return result; 181 | } 182 | 183 | Future sftpCancelDownload() async { 184 | await _channel.invokeMethod('sftpCancelDownload', { 185 | "id": id, 186 | }); 187 | } 188 | 189 | Future sftpUpload({ 190 | @required String path, 191 | @required String toPath, 192 | Callback callback, 193 | }) async { 194 | uploadCallback = callback; 195 | var result = await _channel.invokeMethod('sftpUpload', { 196 | "id": id, 197 | "path": path, 198 | "toPath": toPath, 199 | }); 200 | return result; 201 | } 202 | 203 | Future sftpCancelUpload() async { 204 | await _channel.invokeMethod('sftpCancelUpload', { 205 | "id": id, 206 | }); 207 | } 208 | 209 | Future disconnectSFTP() async { 210 | uploadCallback = null; 211 | downloadCallback = null; 212 | await _channel.invokeMethod('disconnectSFTP', { 213 | "id": id, 214 | }); 215 | } 216 | 217 | disconnect() { 218 | shellCallback = null; 219 | uploadCallback = null; 220 | downloadCallback = null; 221 | stateSubscription.cancel(); 222 | _channel.invokeMethod('disconnect', { 223 | "id": id, 224 | }); 225 | } 226 | 227 | Future isConnected() async { 228 | bool connected = false; // default to false 229 | var result = await _channel.invokeMethod('isConnected', {"id": id,}); 230 | if (result == "true") { // results returns a string, therefor we need to check the string 'true' 231 | connected = true; 232 | } 233 | return connected; 234 | } 235 | 236 | 237 | } 238 | -------------------------------------------------------------------------------- /lib/pages/tf.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'package:firebase_ml_custom/firebase_ml_custom.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:tflite/tflite.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | import 'package:http/http.dart' as http; 9 | 10 | class TFLite { 11 | bool _loaded; 12 | String _modelName; 13 | 14 | set setModel(String name) { 15 | _modelName = name; 16 | _loaded = false; 17 | } 18 | 19 | String get getModel => _modelName; 20 | 21 | bool get modelLoaded => _loaded; 22 | 23 | TFLite() { 24 | _loaded = false; 25 | _modelName = "TomatoModel"; 26 | } 27 | 28 | Future loadModel() async { 29 | final modelFile = await loadModelFromFirebase(); 30 | await loadTFLiteModel(modelFile); 31 | _loaded = true; 32 | } 33 | 34 | /// Downloads custom model from the Firebase console and return its file. 35 | /// located on the mobile device. 36 | Future loadModelFromFirebase() async { 37 | try { 38 | // Create model with a name that is specified in the Firebase console 39 | final model = FirebaseCustomRemoteModel(_modelName); 40 | 41 | // Specify conditions when the model can be downloaded. 42 | // If there is no wifi access when the app is started, 43 | // this app will continue loading until the conditions are satisfied. 44 | final conditions = FirebaseModelDownloadConditions( 45 | androidRequireWifi: true, 46 | iosAllowCellularAccess: false, 47 | iosAllowBackgroundDownloading: true, 48 | androidRequireDeviceIdle: false, 49 | ); 50 | 51 | // Create model manager associated with default Firebase App instance. 52 | final modelManager = FirebaseModelManager.instance; 53 | 54 | // Begin downloading and wait until the model is downloaded successfully. 55 | await modelManager.download(model, conditions); 56 | assert(await modelManager.isModelDownloaded(model) == true); 57 | 58 | // Get latest model file to use it for inference by the interpreter. 59 | var modelFile = await modelManager.getLatestModelFile(model); 60 | assert(modelFile != null); 61 | return modelFile; 62 | } catch (exception) { 63 | print('Failed on loading your model from Firebase: $exception'); 64 | print('The program will not be resumed'); 65 | rethrow; 66 | } 67 | } 68 | 69 | /// Loads the model into some TF Lite interpreter. 70 | /// In this case interpreter provided by tflite plugin. 71 | Future loadTFLiteModel(File modelFile) async { 72 | try { 73 | final appDirectory = await getApplicationDocumentsDirectory(); 74 | final labelsData = await rootBundle.load("assets/"+_modelName+".txt"); 75 | final labelsFile = await File(appDirectory.path + "/"+_modelName+".txt") 76 | .writeAsBytes(labelsData.buffer 77 | .asUint8List(labelsData.offsetInBytes, labelsData.lengthInBytes)); 78 | 79 | assert(await Tflite.loadModel( 80 | model: modelFile.path, 81 | labels: labelsFile.path, 82 | isAsset: false, 83 | ) == 84 | "success"); 85 | return "Model is loaded"; 86 | } catch (exception) { 87 | print( 88 | 'Failed on loading your model to the TFLite interpreter: $exception'); 89 | print('The program will not be resumed'); 90 | rethrow; 91 | } 92 | } 93 | 94 | Future urlToFile(String imageUrl) async { 95 | 96 | // generate random number. 97 | var rng = new Random(); 98 | 99 | // get temporary directory of device. 100 | Directory tempDir = await getTemporaryDirectory(); 101 | 102 | // get temporary path from temporary directory. 103 | String tempPath = tempDir.path; 104 | 105 | // create a new file in temporary path with random file name. 106 | File file = new File('$tempPath' + (rng.nextInt(100)).toString() + '.png'); 107 | print('$tempPath' + (rng.nextInt(100)).toString() + '.png'); 108 | 109 | // call http.get method and pass imageUrl into it to get response. 110 | http.Response response = await http.get(imageUrl); 111 | 112 | // write bodyBytes received in response to file. 113 | await file.writeAsBytes(response.bodyBytes); 114 | 115 | // now return the file which is created with random name in 116 | // temporary directory and image bytes from response is written to // that file. 117 | return file; 118 | } 119 | 120 | Future> getImageLabels(String url) async { 121 | var img = await urlToFile(url); 122 | try { 123 | final image = img; 124 | if (image == null) { 125 | return null; 126 | } 127 | 128 | var labels = List.from(await Tflite.runModelOnImage( 129 | path: img.path, 130 | imageStd: 127.5, 131 | threshold: 0.8, 132 | )); 133 | print("label: $labels"); 134 | return labels; 135 | } catch (exception) { 136 | print("Failed on getting your image and it's labels: $exception"); 137 | print('Continuing with the program...'); 138 | rethrow; 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /lib/pages/video_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mjpeg/flutter_mjpeg.dart'; 3 | 4 | class LiveFeed { 5 | Widget build(BuildContext context) { 6 | return Container( 7 | height: 200.0, 8 | child: Column( 9 | children: [ 10 | Expanded( 11 | child: Center( 12 | child: Mjpeg( 13 | isLive: true, 14 | stream: 15 | 'http://192.168.10.35:8081/videostream.cgi?rate=0&user=&pwd=', //'http://192.168.1.37:8081', 16 | ), 17 | ), 18 | ), 19 | ], 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/services/AuthService.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:sign_in/models/user.dart'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | 5 | class AuthService { 6 | //setting up an instance of Firebase, making it final so no one can override it 7 | final FirebaseAuth _auth = FirebaseAuth.instance; 8 | final Firestore _firestore = Firestore.instance; 9 | 10 | //stream 11 | Stream get userDetail { 12 | return _auth.onAuthStateChanged; 13 | } 14 | 15 | User createUser(FirebaseUser user) { 16 | return user == null? null: User(uid: user.uid); 17 | } 18 | 19 | //registering with email and pass 20 | Future registerWithEmailAndPass(String e, String p) async { 21 | try { 22 | AuthResult res = 23 | await _auth.createUserWithEmailAndPassword(email: e.trim(), password: p.trim()); 24 | FirebaseUser user = res.user; 25 | return createUser(user); 26 | } catch (err) { 27 | print("Caught an error while signing up! Error: $err"); 28 | return null; 29 | } 30 | } 31 | 32 | //setting up sign in with email and pass 33 | Future signInWithEmailAndPass(String e, String p, String n) async { 34 | try { 35 | AuthResult res = 36 | await _auth.signInWithEmailAndPassword(email: e.trim(), password: p.trim()); 37 | UserUpdateInfo updateInfo = UserUpdateInfo(); 38 | updateInfo.displayName = n; 39 | res.user.updateProfile(updateInfo); 40 | FirebaseUser user = res.user; 41 | return createUser(user); 42 | } catch (err) { 43 | print("Caught an error: $err"); 44 | return null; 45 | } 46 | } 47 | 48 | Future getCurrentUserName() async { 49 | try{ 50 | int counter = 0; 51 | FirebaseUser user = await _auth.currentUser(); 52 | DocumentReference document = Firestore.instance.document('users/${user.uid}'); 53 | dynamic d = await document.get().then((value) { 54 | counter = value['completed']; 55 | return [user.displayName, counter]; 56 | }); 57 | return d; 58 | }catch (err){ 59 | print("Caught an error: $err"); 60 | return null; 61 | } 62 | } 63 | 64 | 65 | //Logout function 66 | void logOut() async { 67 | try { 68 | await _auth.signOut(); 69 | } catch (err) { 70 | print("Caught an error: $err"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/services/BackgroundCollectedPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import './BackgroundCollectingTask.dart'; 4 | import './helpers/LineChart.dart'; 5 | import './helpers/PaintStyle.dart'; 6 | 7 | class BackgroundCollectedPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | final BackgroundCollectingTask task = 11 | BackgroundCollectingTask.of(context, rebuildOnChange: true); 12 | 13 | // Arguments shift is needed for timestamps as miliseconds in double could loose precision. 14 | final int argumentsShift = 15 | task.samples.first.timestamp.millisecondsSinceEpoch; 16 | 17 | final Duration showDuration = 18 | Duration(hours: 2); // @TODO . show duration should be configurable 19 | final Iterable lastSamples = task.getLastOf(showDuration); 20 | 21 | final Iterable arguments = lastSamples.map((sample) { 22 | return (sample.timestamp.millisecondsSinceEpoch - argumentsShift) 23 | .toDouble(); 24 | }); 25 | 26 | // Step for argument labels 27 | final Duration argumentsStep = 28 | Duration(minutes: 15); // @TODO . step duration should be configurable 29 | 30 | // Find first timestamp floored to step before 31 | final DateTime beginningArguments = lastSamples.first.timestamp; 32 | DateTime beginningArgumentsStep = DateTime(beginningArguments.year, 33 | beginningArguments.month, beginningArguments.day); 34 | while (beginningArgumentsStep.isBefore(beginningArguments)) { 35 | beginningArgumentsStep = beginningArgumentsStep.add(argumentsStep); 36 | } 37 | beginningArgumentsStep = beginningArgumentsStep.subtract(argumentsStep); 38 | final DateTime endingArguments = lastSamples.last.timestamp; 39 | 40 | // Generate list of timestamps of labels 41 | final Iterable argumentsLabelsTimestamps = () sync* { 42 | DateTime timestamp = beginningArgumentsStep; 43 | yield timestamp; 44 | while (timestamp.isBefore(endingArguments)) { 45 | timestamp = timestamp.add(argumentsStep); 46 | yield timestamp; 47 | } 48 | }(); 49 | 50 | // Map strings for labels 51 | final Iterable argumentsLabels = 52 | argumentsLabelsTimestamps.map((timestamp) { 53 | return LabelEntry( 54 | (timestamp.millisecondsSinceEpoch - argumentsShift).toDouble(), 55 | ((timestamp.hour <= 9 ? '0' : '') + 56 | timestamp.hour.toString() + 57 | ':' + 58 | (timestamp.minute <= 9 ? '0' : '') + 59 | timestamp.minute.toString())); 60 | }); 61 | 62 | return Scaffold( 63 | appBar: AppBar( 64 | title: Text('Collected data'), 65 | actions: [ 66 | // Progress circle 67 | (task.inProgress 68 | ? FittedBox( 69 | child: Container( 70 | margin: new EdgeInsets.all(16.0), 71 | child: CircularProgressIndicator( 72 | valueColor: 73 | AlwaysStoppedAnimation(Colors.white)))) 74 | : Container(/* Dummy */)), 75 | // Start/stop buttons 76 | (task.inProgress 77 | ? IconButton(icon: Icon(Icons.pause), onPressed: task.pause) 78 | : IconButton( 79 | icon: Icon(Icons.play_arrow), onPressed: task.reasume)), 80 | ], 81 | ), 82 | body: ListView( 83 | children: [ 84 | Divider(), 85 | ListTile( 86 | leading: const Icon(Icons.brightness_7), 87 | title: const Text('Temperatures'), 88 | subtitle: const Text('In Celsius'), 89 | ), 90 | LineChart( 91 | constraints: const BoxConstraints.expand(height: 350), 92 | arguments: arguments, 93 | argumentsLabels: argumentsLabels, 94 | values: [ 95 | lastSamples.map((sample) => sample.temperature1), 96 | lastSamples.map((sample) => sample.temperature2), 97 | ], 98 | verticalLinesStyle: const PaintStyle(color: Colors.grey), 99 | additionalMinimalHorizontalLabelsInterval: 0, 100 | additionalMinimalVerticalLablesInterval: 0, 101 | seriesPointsStyles: [ 102 | null, 103 | null, 104 | //const PaintStyle(style: PaintingStyle.stroke, strokeWidth: 1.7*3, color: Colors.indigo, strokeCap: StrokeCap.round), 105 | ], 106 | seriesLinesStyles: [ 107 | const PaintStyle( 108 | style: PaintingStyle.stroke, 109 | strokeWidth: 1.7, 110 | color: Colors.indigoAccent), 111 | const PaintStyle( 112 | style: PaintingStyle.stroke, 113 | strokeWidth: 1.7, 114 | color: Colors.redAccent), 115 | ], 116 | ), 117 | Divider(), 118 | ListTile( 119 | leading: const Icon(Icons.filter_vintage), 120 | title: const Text('Water pH level'), 121 | ), 122 | LineChart( 123 | constraints: const BoxConstraints.expand(height: 200), 124 | arguments: arguments, 125 | argumentsLabels: argumentsLabels, 126 | values: [ 127 | lastSamples.map((sample) => sample.waterpHlevel), 128 | ], 129 | verticalLinesStyle: const PaintStyle(color: Colors.grey), 130 | additionalMinimalHorizontalLabelsInterval: 0, 131 | additionalMinimalVerticalLablesInterval: 0, 132 | seriesPointsStyles: [ 133 | null, 134 | ], 135 | seriesLinesStyles: [ 136 | const PaintStyle( 137 | style: PaintingStyle.stroke, 138 | strokeWidth: 1.7, 139 | color: Colors.greenAccent), 140 | ], 141 | ), 142 | ], 143 | )); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/services/BackgroundCollectingTask.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 4 | import 'package:scoped_model/scoped_model.dart'; 5 | 6 | class DataSample { 7 | double temperature1; 8 | double temperature2; 9 | double waterpHlevel; 10 | DateTime timestamp; 11 | 12 | DataSample({ 13 | this.temperature1, 14 | this.temperature2, 15 | this.waterpHlevel, 16 | this.timestamp, 17 | }); 18 | } 19 | 20 | class BackgroundCollectingTask extends Model { 21 | static BackgroundCollectingTask of( 22 | BuildContext context, { 23 | bool rebuildOnChange = false, 24 | }) => 25 | ScopedModel.of( 26 | context, 27 | rebuildOnChange: rebuildOnChange, 28 | ); 29 | 30 | final BluetoothConnection _connection; 31 | List _buffer = List(); 32 | 33 | // @TODO , Such sample collection in real code should be delegated 34 | // (via `Stream` preferably) and then saved for later 35 | // displaying on chart (or even stright prepare for displaying). 36 | // @TODO ? should be shrinked at some point, endless colleting data would cause memory shortage. 37 | List samples = List(); 38 | 39 | bool inProgress; 40 | 41 | BackgroundCollectingTask._fromConnection(this._connection) { 42 | _connection.input.listen((data) { 43 | _buffer += data; 44 | 45 | while (true) { 46 | // If there is a sample, and it is full sent 47 | int index = _buffer.indexOf('t'.codeUnitAt(0)); 48 | if (index >= 0 && _buffer.length - index >= 7) { 49 | final DataSample sample = DataSample( 50 | temperature1: (_buffer[index + 1] + _buffer[index + 2] / 100), 51 | temperature2: (_buffer[index + 3] + _buffer[index + 4] / 100), 52 | waterpHlevel: (_buffer[index + 5] + _buffer[index + 6] / 100), 53 | timestamp: DateTime.now()); 54 | _buffer.removeRange(0, index + 7); 55 | 56 | samples.add(sample); 57 | notifyListeners(); // Note: It shouldn't be invoked very often - in this example data comes at every second, but if there would be more data, it should update (including repaint of graphs) in some fixed interval instead of after every sample. 58 | //print("${sample.timestamp.toString()} -> ${sample.temperature1} / ${sample.temperature2}"); 59 | } 60 | // Otherwise break 61 | else { 62 | break; 63 | } 64 | } 65 | }).onDone(() { 66 | inProgress = false; 67 | notifyListeners(); 68 | }); 69 | } 70 | 71 | static Future connect( 72 | BluetoothDevice server) async { 73 | final BluetoothConnection connection = 74 | await BluetoothConnection.toAddress(server.address); 75 | return BackgroundCollectingTask._fromConnection(connection); 76 | } 77 | 78 | void dispose() { 79 | _connection.dispose(); 80 | } 81 | 82 | Future start() async { 83 | inProgress = true; 84 | _buffer.clear(); 85 | samples.clear(); 86 | notifyListeners(); 87 | _connection.output.add(ascii.encode('start')); 88 | await _connection.output.allSent; 89 | } 90 | 91 | Future cancel() async { 92 | inProgress = false; 93 | notifyListeners(); 94 | _connection.output.add(ascii.encode('stop')); 95 | await _connection.finish(); 96 | } 97 | 98 | Future pause() async { 99 | inProgress = false; 100 | notifyListeners(); 101 | _connection.output.add(ascii.encode('stop')); 102 | await _connection.output.allSent; 103 | } 104 | 105 | Future reasume() async { 106 | inProgress = true; 107 | notifyListeners(); 108 | _connection.output.add(ascii.encode('start')); 109 | await _connection.output.allSent; 110 | } 111 | 112 | Iterable getLastOf(Duration duration) { 113 | DateTime startingTime = DateTime.now().subtract(duration); 114 | int i = samples.length; 115 | do { 116 | i -= 1; 117 | if (i <= 0) { 118 | break; 119 | } 120 | } while (samples[i].timestamp.isAfter(startingTime)); 121 | return samples.getRange(i, samples.length); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/services/BluetoothDeviceListEntry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 3 | 4 | class BluetoothDeviceListEntry extends ListTile { 5 | BluetoothDeviceListEntry({ 6 | @required BluetoothDevice device, 7 | int rssi, 8 | GestureTapCallback onTap, 9 | GestureLongPressCallback onLongPress, 10 | bool enabled = true, 11 | }) : super( 12 | onTap: onTap, 13 | onLongPress: onLongPress, 14 | enabled: enabled, 15 | leading: 16 | Icon(Icons.devices), // @TODO . !BluetoothClass! class aware icon 17 | title: Text(device.name ?? "Unknown device"), 18 | subtitle: Text(device.address.toString()), 19 | trailing: Row( 20 | mainAxisSize: MainAxisSize.min, 21 | children: [ 22 | rssi != null 23 | ? Container( 24 | margin: new EdgeInsets.all(8.0), 25 | child: DefaultTextStyle( 26 | style: _computeTextStyle(rssi), 27 | child: Column( 28 | mainAxisSize: MainAxisSize.min, 29 | children: [ 30 | Text(rssi.toString()), 31 | Text('dBm'), 32 | ], 33 | ), 34 | ), 35 | ) 36 | : Container(width: 0, height: 0), 37 | device.isConnected 38 | ? Icon(Icons.import_export) 39 | : Container(width: 0, height: 0), 40 | device.isBonded 41 | ? Icon(Icons.link) 42 | : Container(width: 0, height: 0), 43 | ], 44 | ), 45 | ); 46 | 47 | static TextStyle _computeTextStyle(int rssi) { 48 | /**/ if (rssi >= -35) 49 | return TextStyle(color: Colors.greenAccent[700]); 50 | else if (rssi >= -45) 51 | return TextStyle( 52 | color: Color.lerp( 53 | Colors.greenAccent[700], Colors.lightGreen, -(rssi + 35) / 10)); 54 | else if (rssi >= -55) 55 | return TextStyle( 56 | color: Color.lerp( 57 | Colors.lightGreen, Colors.lime[600], -(rssi + 45) / 10)); 58 | else if (rssi >= -65) 59 | return TextStyle( 60 | color: Color.lerp(Colors.lime[600], Colors.amber, -(rssi + 55) / 10)); 61 | else if (rssi >= -75) 62 | return TextStyle( 63 | color: Color.lerp( 64 | Colors.amber, Colors.deepOrangeAccent, -(rssi + 65) / 10)); 65 | else if (rssi >= -85) 66 | return TextStyle( 67 | color: Color.lerp( 68 | Colors.deepOrangeAccent, Colors.redAccent, -(rssi + 75) / 10)); 69 | else 70 | /*code symetry*/ 71 | return TextStyle(color: Colors.redAccent); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/services/ChatPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:typed_data'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 6 | 7 | class ChatPage extends StatefulWidget { 8 | final BluetoothDevice server; 9 | 10 | const ChatPage({this.server}); 11 | 12 | @override 13 | _ChatPage createState() => new _ChatPage(); 14 | } 15 | 16 | class _Message { 17 | int whom; 18 | String text; 19 | 20 | _Message(this.whom, this.text); 21 | } 22 | 23 | class _ChatPage extends State { 24 | static final clientID = 0; 25 | BluetoothConnection connection; 26 | 27 | List<_Message> messages = List<_Message>(); 28 | String _messageBuffer = ''; 29 | 30 | final TextEditingController textEditingController = 31 | new TextEditingController(); 32 | final ScrollController listScrollController = new ScrollController(); 33 | 34 | bool isConnecting = true; 35 | bool get isConnected => connection != null && connection.isConnected; 36 | 37 | bool isDisconnecting = false; 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | 43 | BluetoothConnection.toAddress(widget.server.address).then((_connection) { 44 | print('Connected to the device'); 45 | connection = _connection; 46 | setState(() { 47 | isConnecting = false; 48 | isDisconnecting = false; 49 | }); 50 | 51 | connection.input.listen(_onDataReceived).onDone(() { 52 | // Example: Detect which side closed the connection 53 | // There should be `isDisconnecting` flag to show are we are (locally) 54 | // in middle of disconnecting process, should be set before calling 55 | // `dispose`, `finish` or `close`, which all causes to disconnect. 56 | // If we except the disconnection, `onDone` should be fired as result. 57 | // If we didn't except this (no flag set), it means closing by remote. 58 | if (isDisconnecting) { 59 | print('Disconnecting locally!'); 60 | } else { 61 | print('Disconnected remotely!'); 62 | } 63 | if (this.mounted) { 64 | setState(() {}); 65 | } 66 | }); 67 | }).catchError((error) { 68 | print('Cannot connect, exception occured'); 69 | print(error); 70 | }); 71 | } 72 | 73 | @override 74 | void dispose() { 75 | // Avoid memory leak (`setState` after dispose) and disconnect 76 | if (isConnected) { 77 | isDisconnecting = true; 78 | connection.dispose(); 79 | connection = null; 80 | } 81 | 82 | super.dispose(); 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | final List list = messages.map((_message) { 88 | return Row( 89 | children: [ 90 | Container( 91 | child: Text( 92 | (text) { 93 | return text == '/shrug' ? '¯\\_(ツ)_/¯' : text; 94 | }(_message.text.trim()), 95 | style: TextStyle(color: Colors.white)), 96 | padding: EdgeInsets.all(12.0), 97 | margin: EdgeInsets.only(bottom: 8.0, left: 8.0, right: 8.0), 98 | width: 222.0, 99 | decoration: BoxDecoration( 100 | color: 101 | _message.whom == clientID ? Colors.blueAccent : Colors.grey, 102 | borderRadius: BorderRadius.circular(7.0)), 103 | ), 104 | ], 105 | mainAxisAlignment: _message.whom == clientID 106 | ? MainAxisAlignment.end 107 | : MainAxisAlignment.start, 108 | ); 109 | }).toList(); 110 | 111 | return Scaffold( 112 | appBar: AppBar( 113 | title: (isConnecting 114 | ? Text('Connecting chat to ' + widget.server.name + '...') 115 | : isConnected 116 | ? Text('Live chat with ' + widget.server.name) 117 | : Text('Chat log with ' + widget.server.name))), 118 | body: SafeArea( 119 | child: Column( 120 | children: [ 121 | Flexible( 122 | child: ListView( 123 | padding: const EdgeInsets.all(12.0), 124 | controller: listScrollController, 125 | children: list), 126 | ), 127 | Row( 128 | children: [ 129 | Flexible( 130 | child: Container( 131 | margin: const EdgeInsets.only(left: 16.0), 132 | child: TextField( 133 | style: const TextStyle(fontSize: 15.0), 134 | controller: textEditingController, 135 | decoration: InputDecoration.collapsed( 136 | hintText: isConnecting 137 | ? 'Wait until connected...' 138 | : isConnected 139 | ? 'Type your message...' 140 | : 'Chat got disconnected', 141 | hintStyle: const TextStyle(color: Colors.grey), 142 | ), 143 | enabled: isConnected, 144 | ), 145 | ), 146 | ), 147 | Container( 148 | margin: const EdgeInsets.all(8.0), 149 | child: IconButton( 150 | icon: const Icon(Icons.send), 151 | onPressed: isConnected 152 | ? () => sendMessage(textEditingController.text) 153 | : null), 154 | ), 155 | ], 156 | ) 157 | ], 158 | ), 159 | ), 160 | ); 161 | } 162 | 163 | void _onDataReceived(Uint8List data) { 164 | // Allocate buffer for parsed data 165 | int backspacesCounter = 0; 166 | data.forEach((byte) { 167 | if (byte == 8 || byte == 127) { 168 | backspacesCounter++; 169 | } 170 | }); 171 | Uint8List buffer = Uint8List(data.length - backspacesCounter); 172 | int bufferIndex = buffer.length; 173 | 174 | // Apply backspace control character 175 | backspacesCounter = 0; 176 | for (int i = data.length - 1; i >= 0; i--) { 177 | if (data[i] == 8 || data[i] == 127) { 178 | backspacesCounter++; 179 | } else { 180 | if (backspacesCounter > 0) { 181 | backspacesCounter--; 182 | } else { 183 | buffer[--bufferIndex] = data[i]; 184 | } 185 | } 186 | } 187 | 188 | // Create message if there is new line character 189 | String dataString = String.fromCharCodes(buffer); 190 | int index = buffer.indexOf(13); 191 | if (~index != 0) { 192 | setState(() { 193 | messages.add( 194 | _Message( 195 | 1, 196 | backspacesCounter > 0 197 | ? _messageBuffer.substring( 198 | 0, _messageBuffer.length - backspacesCounter) 199 | : _messageBuffer + dataString.substring(0, index), 200 | ), 201 | ); 202 | _messageBuffer = dataString.substring(index); 203 | }); 204 | } else { 205 | _messageBuffer = (backspacesCounter > 0 206 | ? _messageBuffer.substring( 207 | 0, _messageBuffer.length - backspacesCounter) 208 | : _messageBuffer + dataString); 209 | } 210 | } 211 | 212 | void sendMessage(String text) async { 213 | text = text.trim(); 214 | textEditingController.clear(); 215 | 216 | if (text.length > 0) { 217 | try { 218 | connection.output.add(utf8.encode(text + "\r\n")); 219 | await connection.output.allSent; 220 | 221 | setState(() { 222 | messages.add(_Message(clientID, text)); 223 | }); 224 | 225 | Future.delayed(Duration(milliseconds: 333)).then((_) { 226 | listScrollController.animateTo( 227 | listScrollController.position.maxScrollExtent, 228 | duration: Duration(milliseconds: 333), 229 | curve: Curves.easeOut); 230 | }); 231 | } catch (e) { 232 | // Ignore error, but notify state 233 | setState(() {}); 234 | } 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /lib/services/DiscoveryPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 4 | 5 | import './BluetoothDeviceListEntry.dart'; 6 | 7 | class DiscoveryPage extends StatefulWidget { 8 | /// If true, discovery starts on page start, otherwise user must press action button. 9 | final bool start; 10 | 11 | const DiscoveryPage({this.start = true}); 12 | 13 | @override 14 | _DiscoveryPage createState() => new _DiscoveryPage(); 15 | } 16 | 17 | class _DiscoveryPage extends State { 18 | StreamSubscription _streamSubscription; 19 | List results = List(); 20 | bool isDiscovering; 21 | 22 | _DiscoveryPage(); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | 28 | isDiscovering = widget.start; 29 | if (isDiscovering) { 30 | _startDiscovery(); 31 | } 32 | } 33 | 34 | void _restartDiscovery() { 35 | setState(() { 36 | results.clear(); 37 | isDiscovering = true; 38 | }); 39 | 40 | _startDiscovery(); 41 | } 42 | 43 | void _startDiscovery() { 44 | _streamSubscription = 45 | FlutterBluetoothSerial.instance.startDiscovery().listen((r) { 46 | setState(() { 47 | results.add(r); 48 | }); 49 | }); 50 | 51 | _streamSubscription.onDone(() { 52 | setState(() { 53 | isDiscovering = false; 54 | }); 55 | }); 56 | } 57 | 58 | // @TODO . One day there should be `_pairDevice` on long tap on something... ;) 59 | 60 | @override 61 | void dispose() { 62 | // Avoid memory leak (`setState` after dispose) and cancel discovery 63 | _streamSubscription?.cancel(); 64 | 65 | super.dispose(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return Scaffold( 71 | appBar: AppBar( 72 | title: isDiscovering 73 | ? Text('Discovering devices') 74 | : Text('Discovered devices'), 75 | actions: [ 76 | isDiscovering 77 | ? FittedBox( 78 | child: Container( 79 | margin: new EdgeInsets.all(16.0), 80 | child: CircularProgressIndicator( 81 | valueColor: AlwaysStoppedAnimation(Colors.white), 82 | ), 83 | ), 84 | ) 85 | : IconButton( 86 | icon: Icon(Icons.replay), 87 | onPressed: _restartDiscovery, 88 | ) 89 | ], 90 | ), 91 | body: ListView.builder( 92 | itemCount: results.length, 93 | itemBuilder: (BuildContext context, index) { 94 | BluetoothDiscoveryResult result = results[index]; 95 | return BluetoothDeviceListEntry( 96 | device: result.device, 97 | rssi: result.rssi, 98 | onTap: () { 99 | Navigator.of(context).pop(result.device); 100 | }, 101 | onLongPress: () async { 102 | try { 103 | bool bonded = false; 104 | if (result.device.isBonded) { 105 | print('Unbonding from ${result.device.address}...'); 106 | await FlutterBluetoothSerial.instance 107 | .removeDeviceBondWithAddress(result.device.address); 108 | print('Unbonding from ${result.device.address} has succed'); 109 | } else { 110 | print('Bonding with ${result.device.address}...'); 111 | bonded = await FlutterBluetoothSerial.instance 112 | .bondDeviceAtAddress(result.device.address); 113 | print( 114 | 'Bonding with ${result.device.address} has ${bonded ? 'succed' : 'failed'}.'); 115 | } 116 | setState(() { 117 | results[results.indexOf(result)] = BluetoothDiscoveryResult( 118 | device: BluetoothDevice( 119 | name: result.device.name ?? '', 120 | address: result.device.address, 121 | type: result.device.type, 122 | bondState: bonded 123 | ? BluetoothBondState.bonded 124 | : BluetoothBondState.none, 125 | ), 126 | rssi: result.rssi); 127 | }); 128 | } catch (ex) { 129 | showDialog( 130 | context: context, 131 | builder: (BuildContext context) { 132 | return AlertDialog( 133 | title: const Text('Error occured while bonding'), 134 | content: Text("${ex.toString()}"), 135 | actions: [ 136 | new FlatButton( 137 | child: new Text("Close"), 138 | onPressed: () { 139 | Navigator.of(context).pop(); 140 | }, 141 | ), 142 | ], 143 | ); 144 | }, 145 | ); 146 | } 147 | }, 148 | ); 149 | }, 150 | ), 151 | ); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/services/SelectBondedDevicePage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 4 | 5 | import './BluetoothDeviceListEntry.dart'; 6 | 7 | class SelectBondedDevicePage extends StatefulWidget { 8 | /// If true, on page start there is performed discovery upon the bonded devices. 9 | /// Then, if they are not avaliable, they would be disabled from the selection. 10 | final bool checkAvailability; 11 | 12 | const SelectBondedDevicePage({this.checkAvailability = true}); 13 | 14 | @override 15 | _SelectBondedDevicePage createState() => new _SelectBondedDevicePage(); 16 | } 17 | 18 | enum _DeviceAvailability { 19 | no, 20 | maybe, 21 | yes, 22 | } 23 | 24 | class _DeviceWithAvailability extends BluetoothDevice { 25 | BluetoothDevice device; 26 | _DeviceAvailability availability; 27 | int rssi; 28 | 29 | _DeviceWithAvailability(this.device, this.availability, [this.rssi]); 30 | } 31 | 32 | class _SelectBondedDevicePage extends State { 33 | List<_DeviceWithAvailability> devices = List<_DeviceWithAvailability>(); 34 | 35 | // Availability 36 | StreamSubscription _discoveryStreamSubscription; 37 | bool _isDiscovering; 38 | 39 | _SelectBondedDevicePage(); 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | 45 | _isDiscovering = widget.checkAvailability; 46 | 47 | if (_isDiscovering) { 48 | _startDiscovery(); 49 | } 50 | 51 | // Setup a list of the bonded devices 52 | FlutterBluetoothSerial.instance 53 | .getBondedDevices() 54 | .then((List bondedDevices) { 55 | setState(() { 56 | devices = bondedDevices 57 | .map( 58 | (device) => _DeviceWithAvailability( 59 | device, 60 | widget.checkAvailability 61 | ? _DeviceAvailability.maybe 62 | : _DeviceAvailability.yes, 63 | ), 64 | ) 65 | .toList(); 66 | }); 67 | }); 68 | } 69 | 70 | void _restartDiscovery() { 71 | setState(() { 72 | _isDiscovering = true; 73 | }); 74 | 75 | _startDiscovery(); 76 | } 77 | 78 | void _startDiscovery() { 79 | _discoveryStreamSubscription = 80 | FlutterBluetoothSerial.instance.startDiscovery().listen((r) { 81 | setState(() { 82 | Iterator i = devices.iterator; 83 | while (i.moveNext()) { 84 | var _device = i.current; 85 | if (_device.device == r.device) { 86 | _device.availability = _DeviceAvailability.yes; 87 | _device.rssi = r.rssi; 88 | } 89 | } 90 | }); 91 | }); 92 | 93 | _discoveryStreamSubscription.onDone(() { 94 | setState(() { 95 | _isDiscovering = false; 96 | }); 97 | }); 98 | } 99 | 100 | @override 101 | void dispose() { 102 | // Avoid memory leak (`setState` after dispose) and cancel discovery 103 | _discoveryStreamSubscription?.cancel(); 104 | 105 | super.dispose(); 106 | } 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | List list = devices 111 | .map((_device) => BluetoothDeviceListEntry( 112 | device: _device.device, 113 | rssi: _device.rssi, 114 | enabled: _device.availability == _DeviceAvailability.yes, 115 | onTap: () { 116 | Navigator.of(context).pop(_device.device); 117 | }, 118 | )) 119 | .toList(); 120 | return Scaffold( 121 | appBar: AppBar( 122 | title: Text('Select device'), 123 | actions: [ 124 | _isDiscovering 125 | ? FittedBox( 126 | child: Container( 127 | margin: new EdgeInsets.all(16.0), 128 | child: CircularProgressIndicator( 129 | valueColor: AlwaysStoppedAnimation( 130 | Colors.white, 131 | ), 132 | ), 133 | ), 134 | ) 135 | : IconButton( 136 | icon: Icon(Icons.replay), 137 | onPressed: _restartDiscovery, 138 | ) 139 | ], 140 | ), 141 | body: ListView(children: list), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/services/bluetoothSerial.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; 3 | -------------------------------------------------------------------------------- /lib/services/helpers/PaintStyle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | /// A description of the style to use when drawing on a [Canvas]. 4 | /// 5 | /// Most APIs on [Canvas] take a [Paint] object to describe the style 6 | /// to use for that operation. [PaintStyle] allows to be const 7 | /// constructed and later in runtime forged into the [Paint] object. 8 | class PaintStyle { 9 | /// Whether to apply anti-aliasing to lines and images drawn on the 10 | /// canvas. 11 | /// 12 | /// Defaults to true. 13 | final bool isAntiAlias; 14 | 15 | // Must be kept in sync with the default in paint.cc. 16 | static const int _kColorDefault = 0xFF000000; 17 | 18 | /// The color to use when stroking or filling a shape. 19 | /// 20 | /// Defaults to opaque black. 21 | /// 22 | /// See also: 23 | /// 24 | /// * [style], which controls whether to stroke or fill (or both). 25 | /// * [colorFilter], which overrides [color]. 26 | /// * [shader], which overrides [color] with more elaborate effects. 27 | /// 28 | /// This color is not used when compositing. To colorize a layer, use 29 | /// [colorFilter]. 30 | final Color color; 31 | 32 | // Must be kept in sync with the default in paint.cc. 33 | static final int _kBlendModeDefault = BlendMode.srcOver.index; 34 | 35 | /// A blend mode to apply when a shape is drawn or a layer is composited. 36 | /// 37 | /// The source colors are from the shape being drawn (e.g. from 38 | /// [Canvas.drawPath]) or layer being composited (the graphics that were drawn 39 | /// between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying 40 | /// the [colorFilter], if any. 41 | /// 42 | /// The destination colors are from the background onto which the shape or 43 | /// layer is being composited. 44 | /// 45 | /// Defaults to [BlendMode.srcOver]. 46 | /// 47 | /// See also: 48 | /// 49 | /// * [Canvas.saveLayer], which uses its [Paint]'s [blendMode] to composite 50 | /// the layer when [restore] is called. 51 | /// * [BlendMode], which discusses the user of [saveLayer] with [blendMode]. 52 | final BlendMode blendMode; 53 | 54 | /// Whether to paint inside shapes, the edges of shapes, or both. 55 | /// 56 | /// Defaults to [PaintingStyle.fill]. 57 | final PaintingStyle style; 58 | 59 | /// How wide to make edges drawn when [style] is set to 60 | /// [PaintingStyle.stroke]. The width is given in logical pixels measured in 61 | /// the direction orthogonal to the direction of the path. 62 | /// 63 | /// Defaults to 0.0, which correspond to a hairline width. 64 | final double strokeWidth; 65 | 66 | /// The kind of finish to place on the end of lines drawn when 67 | /// [style] is set to [PaintingStyle.stroke]. 68 | /// 69 | /// Defaults to [StrokeCap.butt], i.e. no caps. 70 | final StrokeCap strokeCap; 71 | 72 | /// The kind of finish to place on the joins between segments. 73 | /// 74 | /// This applies to paths drawn when [style] is set to [PaintingStyle.stroke], 75 | /// It does not apply to points drawn as lines with [Canvas.drawPoints]. 76 | /// 77 | /// Defaults to [StrokeJoin.miter], i.e. sharp corners. 78 | /// 79 | /// Some examples of joins: 80 | /// 81 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4} 82 | /// 83 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/round_join.mp4} 84 | /// 85 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/bevel_join.mp4} 86 | /// 87 | /// The centers of the line segments are colored in the diagrams above to 88 | /// highlight the joins, but in normal usage the join is the same color as the 89 | /// line. 90 | /// 91 | /// See also: 92 | /// 93 | /// * [strokeMiterLimit] to control when miters are replaced by bevels when 94 | /// this is set to [StrokeJoin.miter]. 95 | /// * [strokeCap] to control what is drawn at the ends of the stroke. 96 | /// * [StrokeJoin] for the definitive list of stroke joins. 97 | final StrokeJoin strokeJoin; 98 | 99 | // Must be kept in sync with the default in paint.cc. 100 | static const double _kStrokeMiterLimitDefault = 4.0; 101 | 102 | /// The limit for miters to be drawn on segments when the join is set to 103 | /// [StrokeJoin.miter] and the [style] is set to [PaintingStyle.stroke]. If 104 | /// this limit is exceeded, then a [StrokeJoin.bevel] join will be drawn 105 | /// instead. This may cause some 'popping' of the corners of a path if the 106 | /// angle between line segments is animated, as seen in the diagrams below. 107 | /// 108 | /// This limit is expressed as a limit on the length of the miter. 109 | /// 110 | /// Defaults to 4.0. Using zero as a limit will cause a [StrokeJoin.bevel] 111 | /// join to be used all the time. 112 | /// 113 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_0_join.mp4} 114 | /// 115 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4} 116 | /// 117 | /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_6_join.mp4} 118 | /// 119 | /// The centers of the line segments are colored in the diagrams above to 120 | /// highlight the joins, but in normal usage the join is the same color as the 121 | /// line. 122 | /// 123 | /// See also: 124 | /// 125 | /// * [strokeJoin] to control the kind of finish to place on the joins 126 | /// between segments. 127 | /// * [strokeCap] to control what is drawn at the ends of the stroke. 128 | final double strokeMiterLimit; 129 | 130 | /// A mask filter (for example, a blur) to apply to a shape after it has been 131 | /// drawn but before it has been composited into the image. 132 | /// 133 | /// See [MaskFilter] for details. 134 | final MaskFilter maskFilter; 135 | 136 | /// Controls the performance vs quality trade-off to use when applying 137 | /// filters, such as [maskFilter], or when drawing images, as with 138 | /// [Canvas.drawImageRect] or [Canvas.drawImageNine]. 139 | /// 140 | /// Defaults to [FilterQuality.none]. 141 | // TODO(ianh): verify that the image drawing methods actually respect this 142 | final FilterQuality filterQuality; 143 | 144 | /// The shader to use when stroking or filling a shape. 145 | /// 146 | /// When this is null, the [color] is used instead. 147 | /// 148 | /// See also: 149 | /// 150 | /// * [Gradient], a shader that paints a color gradient. 151 | /// * [ImageShader], a shader that tiles an [Image]. 152 | /// * [colorFilter], which overrides [shader]. 153 | /// * [color], which is used if [shader] and [colorFilter] are null. 154 | final Shader shader; 155 | 156 | /// A color filter to apply when a shape is drawn or when a layer is 157 | /// composited. 158 | /// 159 | /// See [ColorFilter] for details. 160 | /// 161 | /// When a shape is being drawn, [colorFilter] overrides [color] and [shader]. 162 | final ColorFilter colorFilter; 163 | 164 | /// Whether the colors of the image are inverted when drawn. 165 | /// 166 | /// Inverting the colors of an image applies a new color filter that will 167 | /// be composed with any user provided color filters. This is primarily 168 | /// used for implementing smart invert on iOS. 169 | final bool invertColors; 170 | 171 | const PaintStyle({ 172 | this.isAntiAlias = true, 173 | this.color = const Color(_kColorDefault), 174 | this.blendMode = BlendMode.srcOver, 175 | this.style = PaintingStyle.fill, 176 | this.strokeWidth = 0.0, 177 | this.strokeCap = StrokeCap.butt, 178 | this.strokeJoin = StrokeJoin.miter, 179 | this.strokeMiterLimit = 4.0, 180 | this.maskFilter, // null 181 | this.filterQuality = FilterQuality.none, 182 | this.shader, // null 183 | this.colorFilter, // null 184 | this.invertColors = false, 185 | }); 186 | 187 | @override 188 | String toString() { 189 | final StringBuffer result = StringBuffer(); 190 | String semicolon = ''; 191 | result.write('PaintStyle('); 192 | if (style == PaintingStyle.stroke) { 193 | result.write('$style'); 194 | if (strokeWidth != 0.0) 195 | result.write(' ${strokeWidth.toStringAsFixed(1)}'); 196 | else 197 | result.write(' hairline'); 198 | if (strokeCap != StrokeCap.butt) result.write(' $strokeCap'); 199 | if (strokeJoin == StrokeJoin.miter) { 200 | if (strokeMiterLimit != _kStrokeMiterLimitDefault) 201 | result.write( 202 | ' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}'); 203 | } else { 204 | result.write(' $strokeJoin'); 205 | } 206 | semicolon = '; '; 207 | } 208 | if (isAntiAlias != true) { 209 | result.write('${semicolon}antialias off'); 210 | semicolon = '; '; 211 | } 212 | if (color != const Color(_kColorDefault)) { 213 | if (color != null) 214 | result.write('$semicolon$color'); 215 | else 216 | result.write('${semicolon}no color'); 217 | semicolon = '; '; 218 | } 219 | if (blendMode.index != _kBlendModeDefault) { 220 | result.write('$semicolon$blendMode'); 221 | semicolon = '; '; 222 | } 223 | if (colorFilter != null) { 224 | result.write('${semicolon}colorFilter: $colorFilter'); 225 | semicolon = '; '; 226 | } 227 | if (maskFilter != null) { 228 | result.write('${semicolon}maskFilter: $maskFilter'); 229 | semicolon = '; '; 230 | } 231 | if (filterQuality != FilterQuality.none) { 232 | result.write('${semicolon}filterQuality: $filterQuality'); 233 | semicolon = '; '; 234 | } 235 | if (shader != null) { 236 | result.write('${semicolon}shader: $shader'); 237 | semicolon = '; '; 238 | } 239 | if (invertColors) result.write('${semicolon}invert: $invertColors'); 240 | result.write(')'); 241 | return result.toString(); 242 | } 243 | 244 | Paint toPaint() { 245 | Paint paint = Paint(); 246 | if (this.isAntiAlias != true) paint.isAntiAlias = this.isAntiAlias; 247 | if (this.color != const Color(_kColorDefault)) paint.color = this.color; 248 | if (this.blendMode != BlendMode.srcOver) paint.blendMode = this.blendMode; 249 | if (this.style != PaintingStyle.fill) paint.style = this.style; 250 | if (this.strokeWidth != 0.0) paint.strokeWidth = this.strokeWidth; 251 | if (this.strokeCap != StrokeCap.butt) paint.strokeCap = this.strokeCap; 252 | if (this.strokeJoin != StrokeJoin.miter) paint.strokeJoin = this.strokeJoin; 253 | if (this.strokeMiterLimit != 4.0) 254 | paint.strokeMiterLimit = this.strokeMiterLimit; 255 | if (this.maskFilter != null) paint.maskFilter = this.maskFilter; 256 | if (this.filterQuality != FilterQuality.none) 257 | paint.filterQuality = this.filterQuality; 258 | if (this.shader != null) paint.shader = this.shader; 259 | if (this.colorFilter != null) paint.colorFilter = this.colorFilter; 260 | if (this.invertColors != false) paint.invertColors = this.invertColors; 261 | return paint; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/utils/customIcons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomIcons { 4 | static const IconData menu = IconData(0xe900, fontFamily: "CustomIcons"); 5 | static const IconData option = IconData(0xe902, fontFamily: "CustomIcons"); 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:sign_in/pages/about.dart'; 3 | //import 'package:sign_in/pages/process_page.dart'; 4 | import 'package:sign_in/pages/tf.dart'; 5 | import 'package:sign_in/pages/map_page.dart'; 6 | import 'package:sign_in/pages/video_page.dart'; 7 | import 'package:sign_in/pages/profile.dart'; 8 | import 'package:sign_in/pages/new_process_page.dart'; 9 | 10 | List images = [ 11 | "assets/about.gif", 12 | "assets/profileCard.jpg", 13 | "assets/Reports.gif", 14 | "assets/locate.jpg", 15 | ]; 16 | 17 | List title = [ 18 | "About", 19 | "User Profile", 20 | "Reports", 21 | "Map", 22 | ]; 23 | 24 | List pushTo = [ 25 | AboutUs(), 26 | ProfilePage(), 27 | ProcessPage(), 28 | MapPage(), 29 | ]; 30 | -------------------------------------------------------------------------------- /lib/utils/themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Styles { 4 | static final LinearGradient background = LinearGradient(colors: [Color(0xFF1b1e44), Color(0xFF2d3447),],begin: Alignment.bottomCenter, 5 | end: Alignment.topCenter, 6 | tileMode: TileMode.clamp); 7 | 8 | static ThemeData themeData(BuildContext context) { 9 | return ThemeData( 10 | primarySwatch: Colors.blue, 11 | splashColor: Colors.purple, 12 | accentColor: Colors.blue, 13 | textTheme: Texts.textTheme(context), 14 | backgroundColor: Color(0xFF1b1e44), 15 | ); 16 | } 17 | } 18 | 19 | class Texts { 20 | static TextTheme textTheme(BuildContext context) { 21 | return TextTheme( 22 | headline1: TextStyle( 23 | color: Colors.white, 24 | fontSize: 42.0, 25 | fontFamily: "Calibre-Semibold", 26 | letterSpacing: 1.0, 27 | ), 28 | headline3: TextStyle( 29 | fontWeight: FontWeight.w900, 30 | fontFamily: "Calibre-Semibold", 31 | fontSize: 32.0, 32 | color: Colors.white, 33 | ), 34 | headline5: TextStyle( 35 | fontWeight: FontWeight.w900, 36 | fontFamily: "Calibre-Semibold", 37 | fontSize: 24.0, 38 | color: Colors.white), 39 | headline6: TextStyle( 40 | fontWeight: FontWeight.w600, 41 | fontFamily: "Calibre-Semibold", 42 | fontSize: 24.0, 43 | color: Colors.white), 44 | subtitle2: TextStyle( 45 | fontWeight: FontWeight.w600, 46 | fontFamily: "Calibre-Semibold", 47 | fontSize: 20.0, 48 | color: Colors.white), 49 | subtitle1: TextStyle( 50 | fontWeight: FontWeight.w400, 51 | fontFamily: "Calibre-Semibold", 52 | fontSize: 18.0, 53 | color: Colors.black), 54 | bodyText1: TextStyle( 55 | fontWeight: FontWeight.w300, 56 | fontFamily: "Calibre-Semibold", 57 | fontSize: 16.0, 58 | color: Colors.white), 59 | bodyText2: TextStyle( 60 | fontSize: 16.0, 61 | fontFamily: "Calibre-Semibold", 62 | color: Colors.white), 63 | caption: TextStyle( 64 | fontWeight: FontWeight.w400, 65 | fontStyle: FontStyle.italic, 66 | fontFamily: "Calibre-Semibold", 67 | fontSize: 14.0, 68 | color: Colors.white), 69 | ); 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /lib/utils/utils_export.dart: -------------------------------------------------------------------------------- 1 | export 'themes.dart'; 2 | export 'data.dart'; 3 | export 'customIcons.dart'; 4 | -------------------------------------------------------------------------------- /lib/wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'animations.dart'; 6 | import 'pages/home_page.dart'; 7 | import 'utils/themes.dart'; 8 | 9 | 10 | class Wrapper extends StatefulWidget { 11 | @override 12 | _WrapperState createState() => _WrapperState(); 13 | } 14 | 15 | class _WrapperState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | SystemChrome.setPreferredOrientations([ 19 | DeviceOrientation.portraitUp, 20 | DeviceOrientation.portraitDown, 21 | ]); 22 | final user = Provider.of(context); 23 | print(user); 24 | SystemChrome.setPreferredOrientations([ 25 | DeviceOrientation.portraitUp, 26 | DeviceOrientation.portraitDown, 27 | ]); 28 | return MaterialApp( 29 | theme: Styles.themeData(context), 30 | home: user != null ? HomePage() : AuthDemo(), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sign_in 2 | description: Auth for RoverPy 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^0.1.3 31 | shared_preferences: ^0.5.8 32 | provider: ^4.0.0 33 | flare_flutter: ^1.8.3 34 | firebase_core: ^0.4.0+9 35 | flutter_screenutil: ^2.2.0 36 | firebase_auth: ^0.14.0+5 37 | flutter_bluetooth_serial: ^0.2.2 38 | scoped_model: ^1.0.1 39 | url_launcher: ^5.5.0 40 | # firebase_ml_vision: ^0.9.6+2 41 | tflite: ^1.0.4 42 | image_picker: ^0.6.2+3 43 | firebase_ml_custom: ^0.0.2 44 | image: ^2.1.14 45 | cloud_firestore: ^0.12.9+5 46 | firebase_storage: ^3.1.5 47 | quiver: ^2.1.3 48 | uuid: ^2.2.2 49 | google_maps_flutter: ^0.5.27+3 50 | fluster: ^1.1.2 51 | flutter_cache_manager: ^1.1.1 52 | video_player: ^0.11.0 53 | flutter_mjpeg: ^1.2.9 54 | intl: ^0.16.1 55 | charts_flutter: ^0.9.0 56 | 57 | #flutter_native_image: 58 | #git: https://github.com/btastic/flutter_native_image.git 59 | #mlkit: ^0.15.1 60 | 61 | dev_dependencies: 62 | flutter_test: 63 | sdk: flutter 64 | http: ^0.12.1 65 | path_provider: ^1.6.11 66 | 67 | # For information on the generic Dart part of this file, see the 68 | # following page: https://dart.dev/tools/pub/pubspec 69 | 70 | # The following section is specific to Flutter. 71 | flutter: 72 | # The following line ensures that the Material Icons font is 73 | # included with your application, so that you can use the icons in 74 | # the material Icons class. 75 | uses-material-design: true 76 | 77 | # To add assets to your application, add an assets section, like this: 78 | assets: 79 | - assets/ 80 | 81 | # An image asset can refer to one or more resolution-specific "variants", see 82 | # https://flutter.dev/assets-and-images/#resolution-aware. 83 | 84 | # For details regarding adding assets from package dependencies, see 85 | # https://flutter.dev/assets-and-images/#from-packages 86 | 87 | # To add custom fonts to your application, add a fonts section here, 88 | # in this "flutter" section. Each entry in this list should have a 89 | # "family" key with the font family name, and a "fonts" key with a 90 | # list giving the asset and other descriptors for the font. For 91 | # example: 92 | fonts: 93 | - family: Lexend_Deca 94 | fonts: 95 | - asset: fonts/LexendDeca-Regular.ttf 96 | - family: Poppins 97 | fonts: 98 | - asset: fonts/Poppins-Medium.ttf 99 | - family: Raleway 100 | fonts: 101 | - asset: fonts/Raleway-SemiBold.ttf 102 | - family: SF-Pro-Text-Regular 103 | fonts: 104 | - asset: assets/SF-Pro-Text-Regular.otf 105 | 106 | - family: SF-Pro-Text-Bold 107 | fonts: 108 | - asset: assets/SF-Pro-Text-Bold.otf 109 | 110 | - family: Calibre-Semibold 111 | fonts: 112 | - asset: assets/Calibre Semibold.otf 113 | 114 | - family: CustomIcons 115 | fonts: 116 | - asset: assets/custom_icons.ttf 117 | # - family: Trajan Pro 118 | # fonts: 119 | # - asset: fonts/TrajanPro.ttf 120 | # - asset: fonts/TrajanPro_Bold.ttf 121 | # weight: 700 122 | # 123 | # For details regarding fonts from package dependencies, 124 | # see https://flutter.dev/custom-fonts/#from-packages 125 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:sign_in/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------