├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── .gitignore ├── assets ├── Quote 1.jpg ├── arteev.jpeg ├── Quote 2.jpeg ├── Quote 3.jpeg ├── Quote 4.jpeg ├── Quote 5.jpeg ├── drop.svg └── girl.svg ├── readme_pictures ├── login.jpg ├── register.jpg ├── donor_box.jpg ├── donor_form.jpg ├── home_page.jpg ├── seeker_box.jpg ├── seeker_form.jpg ├── seeker_map.jpg └── side_drawer.jpg ├── android ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ ├── drop.png │ │ │ │ │ └── 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 │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── knowyourdonor │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ ├── google-services.json │ └── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings_aar.gradle ├── build.gradle └── settings.gradle ├── .metadata ├── lib ├── provider │ ├── bottom_navigation_provider.dart │ └── auth_provider.dart ├── repository │ ├── location_repository.dart │ ├── donorRepository.dart │ └── seekerRepository.dart ├── components │ ├── appbar.dart │ ├── alertButton.dart │ ├── formbutton.dart │ ├── button.dart │ ├── alertbox.dart │ ├── textbox.dart │ └── loader.dart ├── models │ ├── Donor.dart │ └── Seeker.dart ├── constants │ ├── colors.dart │ ├── validators.dart │ └── text_styles.dart ├── main.dart └── views │ ├── about.dart │ ├── register_page.dart │ ├── login_page.dart │ ├── wrapper.dart │ ├── home_page.dart │ ├── donate_blood_form.dart │ ├── request_blood_form.dart │ ├── donors_list.dart │ └── seekers_list.dart ├── .github ├── ISSUE_TEMPLATE │ ├── proposal.md │ ├── feature.md │ ├── bug.md │ └── documentation.md ├── pull_request_template.md └── workflows │ └── flutter-ci.yml ├── test ├── widget_tests │ ├── about_test.dart │ ├── login_page_tests.dart │ ├── register_page_test.dart │ └── donote_blood_form_test.dart ├── mock.dart └── unit_tests │ ├── auth_provider_test.dart │ └── validator_test.dart ├── .gitignore ├── pubspec.yaml ├── LICENSE ├── CONTRIBUTING.md └── README.md /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/Quote 1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/Quote 1.jpg -------------------------------------------------------------------------------- /assets/arteev.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/arteev.jpeg -------------------------------------------------------------------------------- /assets/Quote 2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/Quote 2.jpeg -------------------------------------------------------------------------------- /assets/Quote 3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/Quote 3.jpeg -------------------------------------------------------------------------------- /assets/Quote 4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/Quote 4.jpeg -------------------------------------------------------------------------------- /assets/Quote 5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/assets/Quote 5.jpeg -------------------------------------------------------------------------------- /readme_pictures/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/login.jpg -------------------------------------------------------------------------------- /readme_pictures/register.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/register.jpg -------------------------------------------------------------------------------- /readme_pictures/donor_box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/donor_box.jpg -------------------------------------------------------------------------------- /readme_pictures/donor_form.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/donor_form.jpg -------------------------------------------------------------------------------- /readme_pictures/home_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/home_page.jpg -------------------------------------------------------------------------------- /readme_pictures/seeker_box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/seeker_box.jpg -------------------------------------------------------------------------------- /readme_pictures/seeker_form.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/seeker_form.jpg -------------------------------------------------------------------------------- /readme_pictures/seeker_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/seeker_map.jpg -------------------------------------------------------------------------------- /readme_pictures/side_drawer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/readme_pictures/side_drawer.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/drawable/drop.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arteevraina/KnowYourDonor/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=false 5 | android.useAndroidX=true 6 | android.enableJetifier=true 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/knowyourdonor/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.knowyourdonor 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | -ignorewarnings -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 78910062997c3a836feee883712c241a5fd22983 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/provider/bottom_navigation_provider.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class BottomNavigationBarProvider with ChangeNotifier { 5 | int _currentIndex = 0; 6 | 7 | get currentIndex => _currentIndex; 8 | 9 | set currentIndex(int index) { 10 | _currentIndex = index; 11 | notifyListeners(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💥 Proposal 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## 💥 Proposal 10 | 11 | ## Reason or Problem 12 | 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | ## Proposal or Solution 16 | 17 | A clear and concise description of what you want to happen. 18 | -------------------------------------------------------------------------------- /lib/repository/location_repository.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:geolocator/geolocator.dart'; 3 | 4 | /// Repository containing functions for communication with 5 | /// [Cloud Firestore]. 6 | /// This class will help to fetch [Donor's] information. 7 | 8 | class LocationRepository { 9 | Future getCurrentLocation() async { 10 | Position res = await Geolocator.getCurrentPosition(); 11 | return res; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature 3 | about: Submit a proposal for a new feature 4 | labels: "feature" 5 | --- 6 | 7 | ## Is your feature request related to a problem? Please describe. 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | ## Describe the solution you'd like 11 | A clear and concise description of what you want to happen. 12 | 13 | ## Additional context 14 | Add any other context about the feature request here. 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/components/appbar.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/text_styles.dart'; 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | // Component for AppBar. 9 | // ignore: non_constant_identifier_names 10 | Widget Appbar({@required String title}) { 11 | return AppBar( 12 | title: Text( 13 | title, 14 | style: appBarTextStyle(), 15 | ), 16 | elevation: 0, 17 | backgroundColor: appBarColor, 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Submit a bug report to help us improve 4 | labels: "bug" 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | What have you tried to diagnose or workaround this issue? 10 | 11 | **Expected behavior** 12 | A clear and concise description of what you expected to happen 13 | 14 | **Impact** 15 | What impact does this issue have on your progress (e.g., annoyance, showstopper) 16 | 17 | **Additional context** 18 | Add any other context about the problem here. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📚 Documentation 3 | about: Report an issue related to documentation 4 | labels: "documentation" 5 | --- 6 | 7 | ## 📚 Documentation 8 | 9 | (A clear and concise description of what the issue is.) 10 | 11 | ## Have you spent some time to check if this issue has been raised before? 12 | 13 | (Have you googled for a similar issue or checked our older issues for a similar bug) 14 | 15 | ### Have you read the [Contribution Guidelines](https://github.com/arteevraina/KnowYourDonor/blob/master/CONTRIBUTING.md)? 16 | 17 | (Write your answer here.) 18 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | 7 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 8 | if (pluginsFile.exists()) { 9 | 10 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 11 | } 12 | 13 | plugins.each { name, path -> 14 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 15 | include ":$name" 16 | project(":$name").projectDir = pluginDirectory 17 | } 18 | -------------------------------------------------------------------------------- /test/widget_tests/about_test.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | // Local imports. 6 | import 'package:knowyourdonor/views/about.dart'; 7 | 8 | Widget createAboutDemoScreen() => MaterialApp( 9 | home: About(), 10 | ); 11 | 12 | void main() { 13 | group("About Page Tests", () { 14 | testWidgets("Check if About Page shows up", (tester) async { 15 | await tester.pumpWidget(createAboutDemoScreen()); 16 | 17 | /// Check if [About Page] shows up. 18 | expect( 19 | find.byType(About), 20 | findsOneWidget, 21 | ); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/models/Donor.dart: -------------------------------------------------------------------------------- 1 | // Model for Seekers. 2 | class Donor { 3 | Donor( 4 | this.name, 5 | this.email, 6 | this.address, 7 | this.bloodGroup, 8 | this.phoneNumber, 9 | this.lat, 10 | this.long, 11 | ); 12 | 13 | Map toJson() { 14 | return { 15 | 'name': name, 16 | 'email': email, 17 | 'address': address, 18 | 'bloodGroup': bloodGroup, 19 | 'phoneNumber': phoneNumber, 20 | 'latitude': lat, 21 | 'longitude': long, 22 | }; 23 | } 24 | 25 | final String name; 26 | final String email; 27 | final String address; 28 | final String bloodGroup; 29 | final int phoneNumber; 30 | final double lat; 31 | final double long; 32 | } 33 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.google.gms:google-services:4.3.0' 10 | classpath 'com.android.tools.build:gradle:4.1.1' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | pubspec.lock 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Credentials 45 | lib/credentials.dart -------------------------------------------------------------------------------- /lib/models/Seeker.dart: -------------------------------------------------------------------------------- 1 | // Model for Seekers. 2 | class Seeker { 3 | Seeker( 4 | this.name, 5 | this.email, 6 | this.address, 7 | this.bloodGroup, 8 | this.units, 9 | this.phoneNumber, 10 | this.lat, 11 | this.long, 12 | this.isPlatelet, 13 | ); 14 | 15 | Map toJson() { 16 | return { 17 | 'name': name, 18 | 'email': email, 19 | 'address': address, 20 | 'bloodGroup': bloodGroup, 21 | 'units': units, 22 | 'phoneNumber': phoneNumber, 23 | 'latitude': lat, 24 | 'longitude': long, 25 | 'isPlatelet': isPlatelet 26 | }; 27 | } 28 | 29 | final String name; 30 | final String email; 31 | final String address; 32 | final String bloodGroup; 33 | final int units; 34 | final int phoneNumber; 35 | final double lat; 36 | final double long; 37 | final bool isPlatelet; 38 | } 39 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: knowyourdonor 2 | description: A new Flutter project. 3 | 4 | publish_to: "none" 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.7.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | cupertino_icons: ^1.0.0 16 | google_fonts: ^1.1.1 17 | flutter_svg: ^0.20.0-nullsafety.3 18 | font_awesome_flutter: ^8.12.0 19 | google_maps_flutter: ^2.0.1 20 | firebase_core: ^0.7.0 21 | firebase_analytics: ^5.0.2 22 | firebase_auth: ^0.20.0 23 | cloud_firestore: ^0.16.0 24 | provider: ^4.3.2+3 25 | fluttertoast: ^7.1.6 26 | carousel_slider: ^3.0.0 27 | geocoding: ^1.0.5 28 | geolocator: ^6.2.0 29 | url_launcher: ^5.0.0 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | mockito: ^5.0.2 35 | rxdart: ^0.26.0 36 | 37 | flutter: 38 | uses-material-design: true 39 | 40 | assets: 41 | - assets/ 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Issue 2 | - Info about Issue or bug 3 | 4 | Closes: #[issue number that will be closed through this PR] 5 | 6 | ### Describe the changes you've made 7 | A clear and concise description of what you have done to successfully close your assigned issue. Any new files? or anything you feel to let us know! 8 | 9 | ### Describe if there is any unusual behaviour of your code(Write `NA` if there isn't) 10 | A clear and concise description of it. 11 | 12 | ### Checklist: 13 | 17 | - [ ] My code follows the style guidelines of this project. 18 | - [ ] I have performed a self-review of my own code. 19 | - [ ] I have commented my code, particularly in hard-to-understand areas. 20 | - [ ] I have made corresponding changes to the documentation. 21 | - [ ] New and existing unit tests pass locally with my changes. 22 | 23 | ### Screenshots 24 | Put any screenshot(s) of the project here. 25 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | 13 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 14 | 15 | def plugins = new Properties() 16 | 17 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 18 | if (pluginsFile.exists()) { 19 | 20 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 21 | } 22 | 23 | plugins.each { name, path -> 24 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 25 | include ":$name" 26 | project(":$name").projectDir = pluginDirectory 27 | } 28 | -------------------------------------------------------------------------------- /lib/components/alertButton.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/colors.dart'; 6 | import 'package:knowyourdonor/constants/text_styles.dart'; 7 | 8 | // Button for Custom Dialog. 9 | // ignore: non_constant_identifier_names 10 | Container AlertButton(String task, IconData icon) { 11 | return Container( 12 | decoration: BoxDecoration( 13 | color: buttonBackgroundColor, 14 | borderRadius: BorderRadius.all( 15 | Radius.circular(30), 16 | ), 17 | ), 18 | child: Padding( 19 | padding: const EdgeInsets.only( 20 | left: 12.0, 21 | right: 12.0, 22 | top: 8.0, 23 | bottom: 8.0, 24 | ), 25 | child: Row( 26 | children: [ 27 | Icon( 28 | icon, 29 | color: iconColor, 30 | ), 31 | SizedBox( 32 | width: 10.0, 33 | ), 34 | Text( 35 | task, 36 | style: bloodGroupTextStyle(), 37 | ), 38 | ], 39 | ), 40 | ), 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Arteev Raina 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 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "323588121894", 4 | "project_id": "know-your-donor-7827f", 5 | "storage_bucket": "know-your-donor-7827f.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:323588121894:android:c7776eda904216585d4de7", 11 | "android_client_info": { 12 | "package_name": "com.example.knowyourdonor" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "323588121894-lll12gs2ek3basf64kggbdemf8fqhuc1.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyCRI6XggOjexcbfIjTqaE79rsLSb-xgsH0" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "323588121894-lll12gs2ek3basf64kggbdemf8fqhuc1.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /assets/drop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 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 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/components/formbutton.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/text_styles.dart'; 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | // Button for Donor and Recipient Forms. 9 | // ignore: non_constant_identifier_names 10 | Widget FormButton( 11 | {@required String buttonText, 12 | Color textColor, 13 | Color backgroundColor, 14 | int colorDifference}) { 15 | return Container( 16 | padding: EdgeInsets.symmetric(vertical: 15), 17 | width: 150.0, 18 | decoration: BoxDecoration( 19 | gradient: LinearGradient(colors: [ 20 | buttonColor, 21 | Color.fromARGB( 22 | buttonColor.alpha, 23 | buttonColor.red - colorDifference, 24 | buttonColor.green - colorDifference, 25 | buttonColor.blue - colorDifference) 26 | ], begin: Alignment.centerLeft, end: Alignment.centerRight), 27 | // color: buttonColor, 28 | borderRadius: BorderRadius.circular(30), 29 | ), 30 | child: Center( 31 | child: Text( 32 | buttonText, 33 | style: buttonTextStyle(), 34 | ), 35 | ), 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /lib/components/button.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/text_styles.dart'; 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | // Multipurpose Button for login and other tasks. 9 | // ignore: non_constant_identifier_names 10 | Widget Button( 11 | {BuildContext context, 12 | @required String buttonText, 13 | Color textColor, 14 | Color backgroundColor, 15 | int colorDifference}) { 16 | return Container( 17 | padding: EdgeInsets.symmetric(vertical: 15), 18 | width: MediaQuery.of(context).size.width, 19 | decoration: BoxDecoration( 20 | gradient: LinearGradient(colors: [ 21 | buttonColor, 22 | Color.fromARGB( 23 | buttonColor.alpha, 24 | buttonColor.red - colorDifference, 25 | buttonColor.green - colorDifference, 26 | buttonColor.blue - colorDifference) 27 | ], begin: Alignment.centerLeft, end: Alignment.centerRight), 28 | // color: buttonColor, 29 | borderRadius: BorderRadius.circular(30), 30 | ), 31 | child: Center( 32 | child: Text( 33 | buttonText, 34 | style: buttonTextStyle(), 35 | ), 36 | ), 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/constants/colors.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | const Color backgroundColor = Color(0xFFF6F7EB); 5 | const Color normalTextColor = Color(0xFF393E41); 6 | const Color errorTextColor = Color(0xFFE94F37); 7 | const Color buttonColor = Color(0xFF44BBA4); 8 | const Color buttonTextColor = Color(0xFFF6F7EB); 9 | const Color textFieldBorderColor = Color(0xFF3F88C5); 10 | const Color buttonShadowColor = Color(0xFF256b5d); 11 | 12 | // Colors for AppBar. 13 | const Color appBarColor = Color(0xFFE94F37); 14 | const Color appBarTitleColor = Color(0xFFF6F7EB); 15 | 16 | // Colors for Home Page. 17 | const Color upperHalfColor = Color(0xFFE94F37); 18 | const Color lowerHalfColor = Color(0xFFF6F7EB); 19 | const Color borderColor = Color(0xFF44BBA4); 20 | const Color floatingButtonColor = Color(0xFFE94F37); 21 | const Color selectedItemColor = Color(0xFF3F88C5); 22 | const Color unSelectedItemColor = Color(0xFF393E41); 23 | 24 | // Colors for Dialog Box. 25 | const Color circleColor = Color(0xFFE94F37); 26 | const Color bloodGroupColor = Color(0xFFF6F7EB); 27 | const Color iconColor = Color(0xFF44BBA4); 28 | const Color buttonBackgroundColor = Color(0xFF256b5d); 29 | const Color textColor = Color(0xFF44BBA4); 30 | 31 | // Colors for Drawer. 32 | const Color headerColor = Color(0xFFE94F37); 33 | const Color headerTextColor = Color(0xFFF6F7EB); 34 | const Color drawerIconColor = Color(0xFF393E41); 35 | const Color drawerListTextColor = Color(0xFF393E41); 36 | -------------------------------------------------------------------------------- /test/mock.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:firebase_core/firebase_core.dart'; 3 | import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | typedef Callback = void Function(MethodCall call); 8 | 9 | void setupFirebaseAuthMocks([Callback customHandlers]) { 10 | TestWidgetsFlutterBinding.ensureInitialized(); 11 | 12 | MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { 13 | if (call.method == 'Firebase#initializeCore') { 14 | return [ 15 | { 16 | 'name': defaultFirebaseAppName, 17 | 'options': { 18 | 'apiKey': '123', 19 | 'appId': '123', 20 | 'messagingSenderId': '123', 21 | 'projectId': '123', 22 | }, 23 | 'pluginConstants': {}, 24 | } 25 | ]; 26 | } 27 | 28 | if (call.method == 'Firebase#initializeApp') { 29 | return { 30 | 'name': call.arguments['appName'], 31 | 'options': call.arguments['options'], 32 | 'pluginConstants': {}, 33 | }; 34 | } 35 | 36 | if (customHandlers != null) { 37 | customHandlers(call); 38 | } 39 | 40 | return null; 41 | }); 42 | } 43 | 44 | Future neverEndingFuture() async { 45 | // ignore: literal_only_boolean_expressions 46 | while (true) { 47 | await Future.delayed(const Duration(minutes: 5)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/flutter-ci.yml: -------------------------------------------------------------------------------- 1 | name: Flutter CI 2 | 3 | # This workflow is triggered on pushes to the repository. 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | # This job will run on ubuntu virtual machine 16 | runs-on: ubuntu-latest 17 | steps: 18 | # Setup Java environment in order to build the Android app. 19 | - uses: actions/checkout@v1 20 | - uses: actions/setup-java@v1 21 | with: 22 | java-version: "12.x" 23 | 24 | # Setup the flutter environment. 25 | - uses: subosito/flutter-action@v1 26 | with: 27 | channel: "stable" # 'dev', 'alpha', default to: 'stable' 28 | # flutter-version: '1.12.x' # you can also specify exact version of flutter 29 | 30 | # Get flutter dependencies. 31 | - run: flutter pub get 32 | 33 | # Check for any formatting issues in the code. 34 | - run: flutter format -n --set-exit-if-changed . 35 | 36 | # Statically analyze the Dart code for any errors. 37 | - run: flutter analyze . 38 | 39 | # Run widget tests for our flutter project. 40 | - run: flutter test 41 | 42 | # Build apk. 43 | # - run: flutter build apk 44 | 45 | # # Upload generated apk to the artifacts. 46 | # - uses: actions/upload-artifact@v1 47 | # with: 48 | # name: release-apk 49 | # path: build/app/outputs/apk/release/app-release.apk 50 | -------------------------------------------------------------------------------- /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 | knowyourdonor 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/components/alertbox.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/text_styles.dart'; 6 | import 'package:knowyourdonor/components/textbox.dart'; 7 | 8 | // ignore: non_constant_identifier_names 9 | Widget AlertBox( 10 | {@required BuildContext context, 11 | @required String inputText, 12 | @required String buttonText, 13 | @required String title, 14 | @required TextEditingController inputController, 15 | @required GestureDetector gestureDetector, 16 | TextInputType keyboardtype}) { 17 | return Center( 18 | child: SizedBox( 19 | width: MediaQuery.of(context).size.width, 20 | height: MediaQuery.of(context).size.height * 0.5, 21 | child: SingleChildScrollView( 22 | child: AlertDialog( 23 | shape: 24 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), 25 | content: Column( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | children: [ 28 | Text( 29 | title, 30 | style: largeTextStyle(), 31 | ), 32 | SizedBox( 33 | height: MediaQuery.of(context).size.height * 0.05, 34 | ), 35 | TextBox( 36 | context: context, 37 | keyboardtype: keyboardtype, 38 | hintText: inputText, 39 | inputController: 40 | inputController, //make it @required if necessary 41 | validator: null, //Add an otp validator 42 | isPassword: false), 43 | ], 44 | ), 45 | actions: [ 46 | gestureDetector, 47 | ], 48 | ), 49 | ), 50 | ), 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /lib/repository/donorRepository.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | 5 | // Local imports. 6 | import 'package:knowyourdonor/models/Donor.dart'; 7 | 8 | /// Different Submission states. 9 | enum SubmitState { 10 | NotSubmitted, 11 | Submitting, 12 | Submitted, 13 | } 14 | 15 | /// Repository containing functions for communication with 16 | /// [Cloud Firestore]. 17 | class DonorRepository with ChangeNotifier { 18 | DonorRepository(this._firestore) : assert(_firestore != null); 19 | 20 | final FirebaseFirestore _firestore; 21 | 22 | SubmitState _state = SubmitState.NotSubmitted; 23 | 24 | SubmitState get state => _state; 25 | 26 | Future postDonor(Donor seeker) async { 27 | try { 28 | /// Converts seeker to Map using [toJson()] method and adds it 29 | /// to firebase while return the document refernece to [document]. 30 | _state = SubmitState.Submitting; 31 | notifyListeners(); 32 | 33 | await _firestore.collection('donors').add(seeker.toJson()); 34 | 35 | _state = SubmitState.Submitted; 36 | notifyListeners(); 37 | 38 | // If document gets successfully added return true. 39 | // Else return false. 40 | return true; 41 | } catch (e) { 42 | return false; 43 | } 44 | } 45 | 46 | Stream getDonors() { 47 | /// Gets the snapshots from [Cloud Firestore] 48 | /// and returns them as List. 49 | try { 50 | return _firestore.collection('donors').snapshots(); 51 | } catch (e) { 52 | return null; 53 | } 54 | } 55 | 56 | Future getDonorById(String id) async { 57 | /// Gets the document from [Cloud Firestore] 58 | /// and returns. 59 | try { 60 | return _firestore.collection('donors').doc(id).get(); 61 | } catch (e) { 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/repository/seekerRepository.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | // Local imports. 6 | import 'package:knowyourdonor/models/Seeker.dart'; 7 | 8 | /// Different Submission states. 9 | enum SubmitState { 10 | NotSubmitted, 11 | Submitting, 12 | Submitted, 13 | } 14 | 15 | /// Repository containing functions for communication with 16 | /// [Cloud Firestore]. 17 | class SeekerRepository with ChangeNotifier { 18 | SeekerRepository(this._firestore) : assert(_firestore != null); 19 | 20 | final FirebaseFirestore _firestore; 21 | 22 | SubmitState _state = SubmitState.NotSubmitted; 23 | 24 | SubmitState get state => _state; 25 | 26 | Future postSeeker(Seeker seeker) async { 27 | try { 28 | /// Converts seeker to Map using [toJson()] method and adds it 29 | /// to firebase while return the document refernece to [document]. 30 | _state = SubmitState.Submitting; 31 | notifyListeners(); 32 | 33 | await _firestore.collection('seekers').add(seeker.toJson()); 34 | 35 | _state = SubmitState.Submitted; 36 | notifyListeners(); 37 | 38 | // If document gets successfully added return true. 39 | // Else return false. 40 | return true; 41 | } catch (e) { 42 | return false; 43 | } 44 | } 45 | 46 | Stream getSeekers() { 47 | /// Gets the snapshots from [Cloud Firestore] 48 | /// and returns them as List. 49 | try { 50 | return _firestore.collection('seekers').snapshots(); 51 | } catch (e) { 52 | return null; 53 | } 54 | } 55 | 56 | Future getSeekerById(String id) async { 57 | /// Gets the document from [Cloud Firestore] 58 | /// and returns. 59 | try { 60 | return _firestore.collection('seekers').doc(id).get(); 61 | } catch (e) { 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/components/textbox.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/text_styles.dart'; 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | // TextBox for Emails, Password, Phone Number 9 | // ignore: non_constant_identifier_names 10 | Widget TextBox({ 11 | @required BuildContext context, 12 | @required String hintText, 13 | @required TextEditingController inputController, 14 | @required Function validator, 15 | @required bool isPassword, 16 | TextInputType keyboardtype, 17 | Icon fieldIcon, 18 | }) { 19 | return TextFormField( 20 | keyboardType: keyboardtype, 21 | obscureText: isPassword, 22 | validator: validator, 23 | controller: inputController, 24 | style: smallTextStyle(), 25 | decoration: InputDecoration( 26 | prefixStyle: mediumTextStyle(), 27 | prefixIcon: Padding( 28 | padding: const EdgeInsets.only( 29 | left: 8.0, 30 | right: 8.0, 31 | ), 32 | child: fieldIcon, 33 | ), 34 | hintStyle: smallTextStyle(), 35 | hintText: hintText, 36 | border: OutlineInputBorder( 37 | borderSide: BorderSide( 38 | style: BorderStyle.solid, 39 | ), 40 | borderRadius: BorderRadius.all( 41 | Radius.circular(30), 42 | ), 43 | ), 44 | errorBorder: OutlineInputBorder( 45 | borderSide: BorderSide(color: errorTextColor), 46 | borderRadius: BorderRadius.all( 47 | Radius.circular(30), 48 | ), 49 | ), 50 | enabledBorder: OutlineInputBorder( 51 | borderSide: BorderSide( 52 | color: textFieldBorderColor, 53 | ), 54 | borderRadius: BorderRadius.all( 55 | Radius.circular(30), 56 | ), 57 | ), 58 | focusedBorder: OutlineInputBorder( 59 | borderSide: BorderSide( 60 | color: normalTextColor, 61 | width: 2, 62 | ), 63 | borderRadius: BorderRadius.all( 64 | Radius.circular(30), 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'com.google.gms.google-services' 26 | apply plugin: 'kotlin-android' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | def keystoreProperties = new Properties() 30 | def keystorePropertiesFile = rootProject.file('key.properties') 31 | if (keystorePropertiesFile.exists()) { 32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 33 | } 34 | 35 | android { 36 | compileSdkVersion 30 37 | 38 | sourceSets { 39 | main.java.srcDirs += 'src/main/kotlin' 40 | } 41 | 42 | lintOptions { 43 | disable 'InvalidPackage' 44 | } 45 | 46 | defaultConfig { 47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 48 | applicationId "com.example.knowyourdonor" 49 | minSdkVersion 16 50 | targetSdkVersion 29 51 | multiDexEnabled true 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | implementation "androidx.browser:browser:1.2.0" 72 | implementation 'com.android.support:multidex:1.0.3' 73 | } 74 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/components/loader.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'dart:math'; 3 | import 'package:flutter/material.dart'; 4 | 5 | // Local imports. 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | // ignore: non_constant_identifier_names 9 | class Loader extends StatefulWidget { 10 | @override 11 | _LoaderState createState() => _LoaderState(); 12 | } 13 | 14 | class _LoaderState extends State with SingleTickerProviderStateMixin { 15 | final Color loaderColor = buttonColor; 16 | AnimationController controller; 17 | // ignore: non_constant_identifier_names 18 | Animation rotation_animation; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | controller = 24 | AnimationController(vsync: this, duration: Duration(seconds: 5)); 25 | rotation_animation = Tween( 26 | begin: 0.0, 27 | end: 1.0, 28 | ).animate( 29 | CurvedAnimation( 30 | parent: controller, 31 | curve: Interval(0.0, 1.0, curve: Curves.linear), 32 | ), 33 | ); 34 | controller.repeat(); 35 | } 36 | 37 | @override 38 | dispose() { 39 | controller.dispose(); // you need this 40 | super.dispose(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | width: 80, 47 | height: 80, 48 | child: Center( 49 | child: RotationTransition( 50 | turns: rotation_animation, 51 | child: Stack( 52 | children: [ 53 | for (var i = 0; i < 8; i++) 54 | Transform.translate( 55 | offset: Offset(30 * cos(i * pi / 4), 30 * sin(i * pi / 4)), 56 | child: Dot( 57 | radius: 2 + i.toDouble() * 1.5, 58 | color: Color.fromARGB( 59 | buttonColor.alpha, 60 | buttonColor.red - (5 * i), 61 | buttonColor.green - (5 * i), 62 | buttonColor.blue - (5 * i)), 63 | ), 64 | ), 65 | ], 66 | ), 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | 73 | class Dot extends StatelessWidget { 74 | final double radius; 75 | final Color color; 76 | Dot({this.radius, this.color}); 77 | @override 78 | Widget build(BuildContext context) { 79 | return Center( 80 | child: Container( 81 | width: this.radius, 82 | height: this.radius, 83 | decoration: BoxDecoration(color: this.color, shape: BoxShape.circle)), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:firebase_core/firebase_core.dart'; 6 | import 'package:cloud_firestore/cloud_firestore.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/provider/auth_provider.dart'; 10 | import 'package:knowyourdonor/provider/bottom_navigation_provider.dart'; 11 | import 'package:knowyourdonor/views/login_page.dart'; 12 | import 'package:knowyourdonor/views/register_page.dart'; 13 | import 'package:knowyourdonor/views/wrapper.dart'; 14 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 15 | import 'package:knowyourdonor/repository/donorRepository.dart'; 16 | 17 | void main() async { 18 | WidgetsFlutterBinding.ensureInitialized(); 19 | await Firebase.initializeApp(); 20 | runApp( 21 | MultiProvider( 22 | providers: [ 23 | ChangeNotifierProvider( 24 | create: (_) => AuthProvider.instance(), 25 | ), 26 | ChangeNotifierProvider( 27 | create: (_) => SeekerRepository(FirebaseFirestore.instance), 28 | ), 29 | ChangeNotifierProvider( 30 | create: (_) => DonorRepository(FirebaseFirestore.instance), 31 | ), 32 | ChangeNotifierProvider( 33 | create: (_) => BottomNavigationBarProvider(), 34 | ) 35 | ], 36 | child: MyApp(), 37 | ), 38 | ); 39 | } 40 | 41 | // Calling the Material App at the root of Widget Tree. 42 | // This will serve as the root of the App. 43 | class MyApp extends StatelessWidget { 44 | @override 45 | Widget build(BuildContext context) { 46 | SystemChrome.setPreferredOrientations([ 47 | DeviceOrientation.portraitUp, 48 | DeviceOrientation.portraitDown, 49 | ]); 50 | return MaterialApp( 51 | home: _showScreen(context), 52 | debugShowCheckedModeBanner: false, 53 | ); 54 | } 55 | } 56 | 57 | Widget _showScreen(BuildContext context) { 58 | switch (context.watch().authState) { 59 | case AuthState.LoggedOut: 60 | return LoginPage(); 61 | case AuthState.Initial: 62 | case AuthState.NotRegistered: 63 | return RegisterPage(); 64 | case AuthState.NotLoggedIn: 65 | return LoginPage(); 66 | case AuthState.Registered: 67 | return LoginPage(); 68 | case AuthState.LoggedIn: 69 | return Wrapper(); 70 | case AuthState.Registering: 71 | return RegisterPage(); 72 | case AuthState.LoggingIn: 73 | return LoginPage(); 74 | } 75 | 76 | return Container(); 77 | } 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Before you contribute 4 | 5 | ### File an issue first! 6 | 7 | If you see a bug or have an idea for a feature that you feel would improve the repo, **please 8 | [file an issue](https://github.com/arteevraina/knowyourdonor/issues/new) before you begin 9 | coding or send a PR**. This will help prevent duplicate work by letting us know 10 | what you're up to. It will help avoid a situation in which you spend a lot of 11 | time coding something that's not quite right for the repo or its goals. 12 | 13 | ## Issue Submission Guidelines 14 | 15 | Before you submit your issue search the archive, maybe your question was already answered. 16 | 17 | If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. 18 | 19 | The "new issue" form contains a number of prompts that you should fill out to make it easier to understand and categorize the issue. 20 | 21 | **If you get help, help others. Good karma rulez!** 22 | 23 | ## Pull Request Submission Guidelines 24 | 25 | Before you submit your pull request consider the following guidelines: 26 | 27 | - First check whether there is an open Issue for what you will be working on. If there is not, open one up by going through issue submission guidelines. 28 | - Search for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort. 29 | - Make your changes in a new git branch: 30 | 31 | ```text 32 | git checkout -b name-issue-tracker-short-description 33 | ``` 34 | 35 | Name can be initials or GitHub username. An example of this could be: 36 | 37 | ```text 38 | git checkout -b arteevraina-issue-75-readme-typos master 39 | ``` 40 | 41 | ## Git Commit Guidelines 42 | 43 | Write meaningful commit messages. 44 | 45 | ### Commit Message Format 46 | 47 | Each commit message consists of a mandatory **type** and **subject**. This is a specific format: 48 | 49 | ```shell 50 | : 51 | ``` 52 | 53 | ### Type 54 | 55 | Must be one of the following: 56 | 57 | - **`feat`**: A new feature 58 | - **`fix`**: A bug fix 59 | - **`docs`**: Documentation only changes 60 | - **`style`**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 61 | - **`refactor`**: A code change that neither fixes a bug nor adds a feature 62 | - **`perf`**: A code change that improves performance 63 | - **`test`**: Adding missing or correcting existing tests 64 | - **`chore`**: Changes to the build process or auxiliary tools and libraries such as documentation generation 65 | -------------------------------------------------------------------------------- /lib/provider/auth_provider.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | 5 | // Different Auth States. 6 | enum AuthState { 7 | Initial, 8 | NotRegistered, 9 | Registering, 10 | Registered, 11 | NotLoggedIn, 12 | LoggingIn, 13 | LoggedIn, 14 | LoggedOut, 15 | } 16 | 17 | // Auth Provider Class. 18 | class AuthProvider with ChangeNotifier { 19 | /// Inject the FirebaseAuth here [Dependency Injection]. 20 | AuthProvider(this._auth) : assert(_auth != null); 21 | FirebaseAuth _auth; 22 | User _user; 23 | 24 | AuthState _authState = AuthState.Initial; 25 | 26 | AuthState get authState => _authState; 27 | User get user => _user; 28 | 29 | // Function for listening to auth changes. 30 | AuthProvider.instance() : _auth = FirebaseAuth.instance { 31 | _auth.authStateChanges().listen((firebaseUser) { 32 | if (firebaseUser == null) { 33 | _authState = AuthState.NotLoggedIn; 34 | } else { 35 | _user = firebaseUser; 36 | print(user.email); 37 | _authState = AuthState.LoggedIn; 38 | } 39 | 40 | notifyListeners(); 41 | }); 42 | } 43 | 44 | // Function for logging in user. 45 | Future login(String email, String password) async { 46 | try { 47 | _authState = AuthState.LoggingIn; 48 | notifyListeners(); 49 | 50 | await _auth.signInWithEmailAndPassword( 51 | email: email, 52 | password: password, 53 | ); 54 | 55 | _authState = AuthState.LoggedIn; 56 | 57 | return true; 58 | } catch (e) { 59 | print(e); 60 | _authState = AuthState.NotLoggedIn; 61 | notifyListeners(); 62 | return false; 63 | } 64 | } 65 | 66 | // Function for registering user. 67 | Future register(String email, String password) async { 68 | try { 69 | _authState = AuthState.Registering; 70 | notifyListeners(); 71 | 72 | await _auth.createUserWithEmailAndPassword( 73 | email: email, 74 | password: password, 75 | ); 76 | 77 | _authState = AuthState.Registered; 78 | 79 | return true; 80 | } catch (e) { 81 | print(e); 82 | _authState = AuthState.NotRegistered; 83 | notifyListeners(); 84 | return false; 85 | } 86 | } 87 | 88 | // Function for logging out user. 89 | Future logout() async { 90 | try { 91 | await _auth.signOut(); 92 | _authState = AuthState.LoggedOut; 93 | notifyListeners(); 94 | return true; 95 | } catch (e) { 96 | print(e); 97 | return false; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/constants/validators.dart: -------------------------------------------------------------------------------- 1 | // Functions for validations of usernames, passwords, phonenumbers, bloodgroups ,emailaddress. 2 | // More functions will be added as the app grows. 3 | 4 | // Function for validating userName. 5 | Function(String) userNameValidator = (String userName) { 6 | if (userName.isEmpty) { 7 | return "Username is empty"; 8 | } 9 | 10 | if (userName.length < 3) { 11 | return "Username cannot be less than 3 characters"; 12 | } 13 | 14 | return null; 15 | }; 16 | 17 | // Function for validating passwords. 18 | Function(String) passwordValidator = (String password) { 19 | if (password.isEmpty) { 20 | return "Password is empty"; 21 | } 22 | 23 | if (password.length < 5) { 24 | return "Password cannot be less than 5 characters"; 25 | } 26 | 27 | return null; 28 | }; 29 | 30 | // Function for validating phoneNumbers. 31 | Function(String) phoneNumberValidator = (String phoneNumber) { 32 | String p = r'^[2-9]{1}[0-9]{9}$'; 33 | RegExp regExp = RegExp(p); 34 | 35 | if (phoneNumber.isEmpty) { 36 | return "Please enter your Phone Number"; 37 | } 38 | 39 | if (!regExp.hasMatch(phoneNumber)) { 40 | return "Phone Number is not valid"; 41 | } 42 | 43 | return null; 44 | }; 45 | 46 | // Function for validating bloodgroup. 47 | Function(String) bloodGroupValidator = (String bloodGroup) { 48 | String p = r'^(A|B|AB|O)[+-]$'; 49 | RegExp regExp = RegExp(p); 50 | 51 | if (bloodGroup.isEmpty) { 52 | return "Please enter your Blood Group"; 53 | } 54 | 55 | if (!regExp.hasMatch(bloodGroup)) { 56 | return "BloodGroup is not valid"; 57 | } 58 | 59 | return null; 60 | }; 61 | 62 | // Function for validating emails. 63 | Function(String) emailValidator = (String emailAddress) { 64 | String p = 65 | r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"; 66 | RegExp regExp = RegExp(p); 67 | 68 | if (emailAddress.isEmpty) { 69 | return "Please enter your email"; 70 | } 71 | 72 | if (!regExp.hasMatch(emailAddress)) { 73 | return "Email address is not valid"; 74 | } 75 | 76 | return null; 77 | }; 78 | 79 | // Function for validating address. 80 | Function(String) addressValidator = (String address) { 81 | String p = r"^[#.0-9a-zA-Z\s,-]+$"; 82 | RegExp regExp = RegExp(p); 83 | 84 | if (address.isEmpty) { 85 | return "Address is empty"; 86 | } 87 | 88 | if (!regExp.hasMatch(address)) { 89 | return "Address is not valid"; 90 | } 91 | 92 | return null; 93 | }; 94 | 95 | // Function for validating units of Blood. 96 | Function(String) unitsValidator = (String units) { 97 | if (units.isEmpty) { 98 | return "Units are empty"; 99 | } 100 | 101 | return null; 102 | }; 103 | -------------------------------------------------------------------------------- /lib/constants/text_styles.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | 5 | // Local imports. 6 | import 'package:knowyourdonor/constants/colors.dart'; 7 | 8 | TextStyle smallTextStyle() { 9 | return GoogleFonts.josefinSlab( 10 | textStyle: TextStyle( 11 | color: normalTextColor, 12 | fontSize: 15, 13 | ), 14 | ); 15 | } 16 | 17 | TextStyle mediumTextStyle() { 18 | return GoogleFonts.josefinSlab( 19 | textStyle: TextStyle( 20 | color: errorTextColor, 21 | fontSize: 25, 22 | fontWeight: FontWeight.bold, 23 | ), 24 | ); 25 | } 26 | 27 | TextStyle largeTextStyle() { 28 | return GoogleFonts.josefinSlab( 29 | textStyle: TextStyle( 30 | color: errorTextColor, 31 | fontSize: 30, 32 | fontWeight: FontWeight.bold, 33 | ), 34 | ); 35 | } 36 | 37 | // Text Styles for use in buttons. 38 | TextStyle buttonTextStyle() { 39 | return GoogleFonts.josefinSlab( 40 | textStyle: TextStyle( 41 | color: buttonTextColor, 42 | fontSize: 30, 43 | fontWeight: FontWeight.bold, 44 | ), 45 | ); 46 | } 47 | 48 | // Text Style for AppBar Title. 49 | TextStyle appBarTextStyle() { 50 | return GoogleFonts.josefinSlab( 51 | textStyle: TextStyle( 52 | color: appBarTitleColor, 53 | fontSize: 25, 54 | ), 55 | ); 56 | } 57 | 58 | // Text Style for use in Home Page Button. 59 | TextStyle homePageButtonTextStyle() { 60 | return GoogleFonts.josefinSlab( 61 | textStyle: TextStyle( 62 | color: errorTextColor, 63 | letterSpacing: 1, 64 | fontSize: 20, 65 | fontWeight: FontWeight.bold, 66 | ), 67 | ); 68 | } 69 | 70 | // Text Style for bloodGroup inside Container. 71 | TextStyle bloodGroupTextStyle() { 72 | return GoogleFonts.josefinSlab( 73 | textStyle: TextStyle(color: bloodGroupColor), 74 | letterSpacing: 2, 75 | fontSize: 25, 76 | fontWeight: FontWeight.bold, 77 | ); 78 | } 79 | 80 | // Text Style for Drawer Header. 81 | TextStyle drawerHeaderTextStyle() { 82 | return GoogleFonts.josefinSlab( 83 | textStyle: TextStyle(color: headerTextColor), 84 | letterSpacing: 0, 85 | fontSize: 25, 86 | fontWeight: FontWeight.bold, 87 | ); 88 | } 89 | 90 | // Text Style for Drawer List Tiles. 91 | TextStyle drawerListTextStyle() { 92 | return GoogleFonts.josefinSlab( 93 | textStyle: TextStyle( 94 | color: drawerListTextColor, 95 | fontSize: 20, 96 | fontWeight: FontWeight.bold, 97 | ), 98 | ); 99 | } 100 | 101 | // Text Style for showing in Cards. 102 | TextStyle cardTextStyle() { 103 | return GoogleFonts.josefinSlab( 104 | textStyle: TextStyle( 105 | color: headerColor, 106 | fontSize: 15, 107 | fontWeight: FontWeight.bold, 108 | ), 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 14 | 16 | 23 | 27 | 31 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/views/about.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/components/appbar.dart'; 10 | 11 | class About extends StatelessWidget { 12 | // Email Launch URI. 13 | final Uri _emailLaunchUri = Uri( 14 | scheme: 'mailto', 15 | path: 'arteevraina@gmail.com', 16 | ); 17 | 18 | // URL for Github. 19 | final String _githubURL = "https://github.com/arteevraina"; 20 | 21 | // URL for Twitter. 22 | final String _twitterURL = "https://twitter.com/RainaArteev"; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: Appbar( 28 | title: "About Me", 29 | ), 30 | body: SafeArea( 31 | child: Center( 32 | child: Column( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | children: [ 35 | CircleAvatar( 36 | radius: 50.0, 37 | backgroundImage: AssetImage('assets/arteev.jpeg'), 38 | ), 39 | Text( 40 | 'Arteev Raina', 41 | style: GoogleFonts.pacifico( 42 | color: Colors.black, 43 | fontSize: 30, 44 | ), 45 | ), 46 | Text( 47 | 'FLUTTER & OPEN SOURCE DEV', 48 | style: GoogleFonts.sourceSansPro( 49 | color: Colors.black54, 50 | fontSize: 20.0, 51 | letterSpacing: 2.5, 52 | fontWeight: FontWeight.bold, 53 | ), 54 | ), 55 | SizedBox( 56 | height: 20.0, 57 | width: 150.0, 58 | child: Divider( 59 | color: Colors.black38, 60 | ), 61 | ), 62 | SizedBox( 63 | height: 10.0, 64 | ), 65 | Row( 66 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 67 | children: [ 68 | GestureDetector( 69 | onTap: () { 70 | launch(_emailLaunchUri.toString()); 71 | }, 72 | child: FaIcon( 73 | Icons.email, 74 | color: Colors.black, 75 | ), 76 | ), 77 | GestureDetector( 78 | onTap: () async { 79 | await canLaunch(_twitterURL) 80 | ? await launch(_twitterURL) 81 | : Fluttertoast.showToast( 82 | msg: "Could not launch twitter", 83 | ); 84 | }, 85 | child: FaIcon( 86 | FontAwesomeIcons.twitter, 87 | color: Colors.black, 88 | ), 89 | ), 90 | GestureDetector( 91 | onTap: () async { 92 | await canLaunch(_githubURL) 93 | ? await launch(_githubURL) 94 | : Fluttertoast.showToast( 95 | msg: "Could not launch github", 96 | ); 97 | }, 98 | child: FaIcon( 99 | FontAwesomeIcons.github, 100 | color: Colors.black, 101 | ), 102 | ) 103 | ], 104 | ) 105 | ], 106 | ), 107 | ), 108 | ), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/unit_tests/auth_provider_test.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | import 'package:firebase_core/firebase_core.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/provider/auth_provider.dart'; 10 | import '../mock.dart'; 11 | 12 | // Mock Firebase [Core or Auth] Classes for unit tests. 13 | class MockFirebaseAuth extends Mock implements FirebaseAuth {} 14 | 15 | class MockUser extends Mock implements User {} 16 | 17 | class MockUserCredential extends Mock implements UserCredential {} 18 | 19 | void main() { 20 | /// Initializing [FirebaseApp] just as in [main.dart]. 21 | setupFirebaseAuthMocks(); 22 | setUpAll(() async { 23 | await Firebase.initializeApp(); 24 | }); 25 | 26 | // Creating instances from the mocked classes. 27 | MockFirebaseAuth _auth = MockFirebaseAuth(); 28 | BehaviorSubject _user = BehaviorSubject(); 29 | 30 | // Mocking on authStateChanges function. 31 | when(_auth.authStateChanges()).thenAnswer((_) { 32 | return _user; 33 | }); 34 | 35 | AuthProvider _provider = AuthProvider(_auth); 36 | group('Auth Provider Tests', () { 37 | /// Mocking [signInWithEmailAndPassword] function 38 | /// This means if [email = "email"] and [password="password"] 39 | /// is passed to the function. 40 | /// Then it will add the user to the Stream. 41 | when( 42 | _auth.signInWithEmailAndPassword( 43 | email: "email", 44 | password: "password", 45 | ), 46 | ).thenAnswer((_) async { 47 | _user.add(MockUser()); 48 | return MockUserCredential(); 49 | }); 50 | 51 | /// Mocking [signInWithEmailAndPassword] function 52 | /// This means if [email = "emails"] and [password="passwords"] 53 | /// is passed to the function. 54 | /// Then it will throw an error. 55 | when( 56 | _auth.signInWithEmailAndPassword( 57 | email: "emails", 58 | password: "passwords", 59 | ), 60 | ).thenThrow((_) { 61 | return null; 62 | }); 63 | 64 | /// Mocking [createUserWithEmailAndPassword] function 65 | /// This means if [email = "email"] and [password="password"] 66 | /// is passed to the function. 67 | /// Then it will add user to the Stream. 68 | when( 69 | _auth.createUserWithEmailAndPassword( 70 | email: "email", 71 | password: "password", 72 | ), 73 | ).thenAnswer((_) async { 74 | _user.add(MockUser()); 75 | return MockUserCredential(); 76 | }); 77 | test("Login with correct email and password", () async { 78 | bool isLogin = await _provider.login( 79 | "email", 80 | "password", 81 | ); 82 | 83 | // Verify that it returns true. 84 | expect(isLogin, true); 85 | 86 | /// Verify that [AuthState] is [LoggedIn]. 87 | expect(_provider.authState, AuthState.LoggedIn); 88 | }); 89 | 90 | test("Login with incorrect email and password", () async { 91 | bool isLogin = await _provider.login( 92 | "emails", 93 | "passwords", 94 | ); 95 | 96 | // Verify that it returns false. 97 | expect(isLogin, false); 98 | 99 | /// Verify that [AuthState] is [NotLoggedIn]. 100 | expect(_provider.authState, AuthState.NotLoggedIn); 101 | }); 102 | 103 | test("Register user with email and password", () async { 104 | bool isLogin = await _provider.register( 105 | "email", 106 | "password", 107 | ); 108 | 109 | // Verify that it returns true. 110 | expect(isLogin, true); 111 | 112 | /// Verify that [AuthState] is [Registered]. 113 | expect(_provider.authState, AuthState.Registered); 114 | }); 115 | test("Log Out user", () async { 116 | bool isLoggedOut = await _provider.logout(); 117 | 118 | // Verify that it returns true. 119 | expect(isLoggedOut, true); 120 | 121 | /// Verify that [AuthState] is [LoggedOut]. 122 | expect(_provider.authState, AuthState.LoggedOut); 123 | }); 124 | }); 125 | } 126 | -------------------------------------------------------------------------------- /test/unit_tests/validator_test.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | // Local imports. 5 | import 'package:knowyourdonor/constants/validators.dart'; 6 | 7 | void main() { 8 | group('Validators Tests', () { 9 | ///[userNameValidator] tests. 10 | test('userNameValidator test when empty string is passed', () { 11 | // Verify that it returns correct string. 12 | expect( 13 | "Username is empty", 14 | userNameValidator(""), 15 | ); 16 | }); 17 | 18 | test( 19 | 'userNameValidator test when string of less than 3 characteres is passed', 20 | () { 21 | // Verify that it returns correct string. 22 | expect( 23 | "Username cannot be less than 3 characters", 24 | userNameValidator("ar"), 25 | ); 26 | }); 27 | 28 | test('userNameValidator test when correct string is passed', () { 29 | // Verify that it returns null. 30 | expect( 31 | null, 32 | userNameValidator("arteev"), 33 | ); 34 | }); 35 | 36 | ///[passwordValidator] tests. 37 | test("passwordValidator test when empty string is passed", () { 38 | // Verify that it returns correct string. 39 | expect( 40 | "Password is empty", 41 | passwordValidator(""), 42 | ); 43 | }); 44 | 45 | test("passwordValidator test when string less than 5 characters is passed", 46 | () { 47 | // Verify that it returns correct string. 48 | expect( 49 | "Password cannot be less than 5 characters", 50 | passwordValidator("abcd"), 51 | ); 52 | }); 53 | 54 | test("passwordValidator test when correct string is passed", () { 55 | // Verify that it returns null. 56 | expect( 57 | null, 58 | passwordValidator("abcde"), 59 | ); 60 | }); 61 | 62 | /// [phoneNumberValidator] tests. 63 | test("phoneNumberValidator test when empty string is passed", () { 64 | // Verify that it returns correct string. 65 | expect( 66 | "Please enter your Phone Number", 67 | phoneNumberValidator(""), 68 | ); 69 | }); 70 | 71 | test("phoneNumberValidator test when incorrect string is passed", () { 72 | // Verify that it returns correct string. 73 | expect( 74 | "Phone Number is not valid", 75 | phoneNumberValidator("1234565"), 76 | ); 77 | }); 78 | 79 | test("phoneNumberValidator test when correct string is passed", () { 80 | // Verify that it returns null. 81 | expect( 82 | null, 83 | phoneNumberValidator("7006480030"), 84 | ); 85 | }); 86 | 87 | /// [bloodGroupValidator] tests. 88 | test("bloodGroupValidator test when empty string is passed", () { 89 | // Verify that it returns correct string. 90 | expect( 91 | "Please enter your Blood Group", 92 | bloodGroupValidator(""), 93 | ); 94 | }); 95 | 96 | test("bloodGroupValidator test when incorrect string is passed", () { 97 | // Verify that it returns correct string. 98 | expect( 99 | "BloodGroup is not valid", 100 | bloodGroupValidator("a"), 101 | ); 102 | }); 103 | 104 | test("bloodGroupValidator test when correct string is passed", () { 105 | // Verify that it returns null. 106 | expect( 107 | null, 108 | bloodGroupValidator("A+"), 109 | ); 110 | }); 111 | 112 | /// [emailValidator] tests. 113 | test("emailValidator test when empty string is passed", () { 114 | // Verify that it returns correct string. 115 | expect( 116 | "Please enter your email", 117 | emailValidator(""), 118 | ); 119 | }); 120 | test("emailValidator test when incorrect string is passed", () { 121 | // Verify that it returns correct string. 122 | expect( 123 | "Email address is not valid", 124 | emailValidator("arteev"), 125 | ); 126 | }); 127 | 128 | test("emailValidator test when correct string is passed", () { 129 | // Verify that it returns correct string. 130 | expect( 131 | null, 132 | emailValidator("arteev@live.in"), 133 | ); 134 | }); 135 | 136 | ///[addressValidator] tests. 137 | test("addressValidator test when empty string is passed", () { 138 | // Verify that it returns correct string. 139 | expect( 140 | "Address is empty", 141 | addressValidator(""), 142 | ); 143 | }); 144 | 145 | test("addressValidator test when correct string is passed", () { 146 | // Verify that it returns null. 147 | expect( 148 | null, 149 | addressValidator("Durga Nagar, Jammu"), 150 | ); 151 | }); 152 | 153 | /// [unitsValidator] tests. 154 | test("unitsValidator when empty string is passed", () { 155 | // Verify that it returns correct string. 156 | expect( 157 | "Units are empty", 158 | unitsValidator(""), 159 | ); 160 | }); 161 | 162 | test("unitsValidator when correct string is passed", () { 163 | // Verify that it returns null. 164 | expect( 165 | "Units are empty", 166 | unitsValidator(""), 167 | ); 168 | }); 169 | }); 170 | } 171 | -------------------------------------------------------------------------------- /lib/views/register_page.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:flutter_svg/flutter_svg.dart'; 5 | import 'package:fluttertoast/fluttertoast.dart'; 6 | 7 | // Local imports. 8 | import 'package:knowyourdonor/provider/auth_provider.dart'; 9 | import 'package:knowyourdonor/components/textbox.dart'; 10 | import 'package:knowyourdonor/components/button.dart'; 11 | import 'package:knowyourdonor/components/loader.dart'; 12 | import 'package:knowyourdonor/constants/text_styles.dart'; 13 | import 'package:knowyourdonor/constants/validators.dart'; 14 | import 'package:knowyourdonor/constants/colors.dart'; 15 | import 'package:knowyourdonor/views/login_page.dart'; 16 | 17 | // Stateful Widget that handles Email Register Task. 18 | class RegisterPage extends StatefulWidget { 19 | @override 20 | _RegisterPageState createState() => _RegisterPageState(); 21 | } 22 | 23 | class _RegisterPageState extends State { 24 | // Unique key for the validation of the form. 25 | final _formKey = GlobalKey(); 26 | TextEditingController _emailController = TextEditingController(); 27 | TextEditingController _passwordController = TextEditingController(); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | AuthProvider authProvider = Provider.of(context); 32 | 33 | return Scaffold( 34 | body: SingleChildScrollView( 35 | child: Container( 36 | height: MediaQuery.of(context).size.height, 37 | padding: EdgeInsets.symmetric( 38 | horizontal: 20, 39 | ), 40 | color: backgroundColor, 41 | child: Column( 42 | mainAxisAlignment: MainAxisAlignment.center, 43 | children: [ 44 | SizedBox( 45 | height: 60, 46 | ), 47 | Expanded( 48 | flex: 2, 49 | child: Container( 50 | width: MediaQuery.of(context).size.width * 0.7, 51 | child: SvgPicture.asset( 52 | 'assets/doctor.svg', 53 | ), 54 | ), 55 | ), 56 | Center( 57 | child: Text( 58 | 'KNOW YOUR DONOR', 59 | style: largeTextStyle(), 60 | ), 61 | ), 62 | Expanded( 63 | flex: 2, 64 | child: Form( 65 | key: _formKey, 66 | child: Column( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | TextBox( 70 | context: context, 71 | hintText: "Enter your email", 72 | isPassword: false, 73 | inputController: _emailController, 74 | validator: emailValidator, 75 | keyboardtype: TextInputType.emailAddress, 76 | fieldIcon: Icon( 77 | Icons.email, 78 | color: buttonColor, 79 | ), 80 | ), 81 | SizedBox( 82 | height: 10, 83 | ), 84 | TextBox( 85 | context: context, 86 | hintText: "Enter your password", 87 | isPassword: true, 88 | inputController: _passwordController, 89 | validator: passwordValidator, 90 | keyboardtype: TextInputType.text, 91 | fieldIcon: Icon( 92 | Icons.security, 93 | color: buttonColor, 94 | ), 95 | ), 96 | SizedBox( 97 | height: 10, 98 | ), 99 | GestureDetector( 100 | onTap: () async { 101 | if (_formKey.currentState.validate()) { 102 | if (!await context.read().register( 103 | _emailController.text, 104 | _passwordController.text, 105 | )) { 106 | Fluttertoast.showToast( 107 | msg: "Could not Register", 108 | ); 109 | } else { 110 | Navigator.pushReplacement( 111 | context, 112 | MaterialPageRoute( 113 | builder: (context) => LoginPage(), 114 | ), 115 | ); 116 | } 117 | } 118 | }, 119 | child: (authProvider.authState == AuthState.Registering) 120 | ? Loader() 121 | : Button( 122 | context: context, 123 | buttonText: "Register", 124 | colorDifference: 60, 125 | ), 126 | ), 127 | SizedBox( 128 | height: 10, 129 | ), 130 | GestureDetector( 131 | onTap: () { 132 | Navigator.pushReplacement( 133 | context, 134 | MaterialPageRoute( 135 | builder: (context) => LoginPage(), 136 | ), 137 | ); 138 | }, 139 | child: Text( 140 | "Already registered ? Log In!", 141 | style: smallTextStyle(), 142 | ), 143 | ), 144 | ], 145 | ), 146 | ), 147 | ), 148 | ], 149 | ), 150 | ), 151 | ), 152 | ); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /test/widget_tests/login_page_tests.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:firebase_core/firebase_core.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/views/login_page.dart'; 10 | import 'package:knowyourdonor/provider/auth_provider.dart'; 11 | import 'package:knowyourdonor/provider/bottom_navigation_provider.dart'; 12 | import 'package:knowyourdonor/repository/donorRepository.dart'; 13 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 14 | import '../mock.dart'; 15 | 16 | Widget createLoginPageDemoScreen() => MultiProvider( 17 | providers: [ 18 | ChangeNotifierProvider( 19 | create: (_) => AuthProvider.instance(), 20 | ), 21 | ChangeNotifierProvider( 22 | create: (_) => SeekerRepository(FirebaseFirestore.instance), 23 | ), 24 | ChangeNotifierProvider( 25 | create: (_) => DonorRepository(FirebaseFirestore.instance), 26 | ), 27 | ChangeNotifierProvider( 28 | create: (_) => BottomNavigationBarProvider(), 29 | ) 30 | ], 31 | child: MaterialApp( 32 | home: LoginPage(), 33 | ), 34 | ); 35 | 36 | void main() { 37 | /// Initializing [FirebaseApp] just as in [main.dart]. 38 | setupFirebaseAuthMocks(); 39 | setUpAll(() async { 40 | await Firebase.initializeApp(); 41 | }); 42 | 43 | group("Login Page Tests", () { 44 | testWidgets("Check if Login Page shows up", (tester) async { 45 | await tester.pumpWidget(createLoginPageDemoScreen()); 46 | 47 | /// Check if [Login Page] shows up. 48 | expect( 49 | find.byType(LoginPage), 50 | findsOneWidget, 51 | ); 52 | }); 53 | 54 | testWidgets("Validations return false when empty form is submitted", 55 | (tester) async { 56 | await tester.pumpWidget(createLoginPageDemoScreen()); 57 | 58 | /// Get the hold of [Form] Widget. 59 | var form = tester.widget(find.byType(Form)); 60 | 61 | /// Get the hold of [Form Key]. 62 | var formKey = form.key as GlobalKey; 63 | 64 | // Get hold of Login button. 65 | var loginButton = find.text("Login"); 66 | 67 | // Tap on the Login button. 68 | await tester.tap(loginButton); 69 | await tester.pumpAndSettle(); 70 | 71 | // Verify that key's current state validates to false. 72 | expect( 73 | formKey.currentState.validate(), 74 | false, 75 | ); 76 | 77 | /// Verify that we are still on [Login Page]. 78 | expect( 79 | find.byType(LoginPage), 80 | findsOneWidget, 81 | ); 82 | }); 83 | 84 | testWidgets("Validations return false when incorrect email is submitted", 85 | (tester) async { 86 | await tester.pumpWidget(createLoginPageDemoScreen()); 87 | 88 | /// Get the hold of [Form] Widget. 89 | var form = tester.widget(find.byType(Form)); 90 | 91 | /// Get the hold of [Form Key]. 92 | var formKey = form.key as GlobalKey; 93 | 94 | // Get hold of Login button. 95 | var loginButton = find.text("Login"); 96 | 97 | // Fill in email field. 98 | await tester.enterText( 99 | find.byType(TextFormField).at(0), 100 | "arteev", 101 | ); 102 | 103 | // Fill in password field. 104 | await tester.enterText( 105 | find.byType(TextFormField).at(1), 106 | "abcd1234", 107 | ); 108 | 109 | // Tap on the Login button. 110 | await tester.tap(loginButton); 111 | await tester.pumpAndSettle(); 112 | 113 | // Verify that key's current state validates to false. 114 | expect( 115 | formKey.currentState.validate(), 116 | false, 117 | ); 118 | 119 | /// Verify that we are still on [Login Page]. 120 | expect( 121 | find.byType(LoginPage), 122 | findsOneWidget, 123 | ); 124 | }); 125 | 126 | testWidgets("Validations return false when incorrect password is submitted", 127 | (tester) async { 128 | await tester.pumpWidget(createLoginPageDemoScreen()); 129 | 130 | /// Get the hold of [Form] Widget. 131 | var form = tester.widget(find.byType(Form)); 132 | 133 | /// Get the hold of [Form Key]. 134 | var formKey = form.key as GlobalKey; 135 | 136 | // Get hold of Login button. 137 | var loginButton = find.text("Login"); 138 | 139 | // Fill in email field. 140 | await tester.enterText( 141 | find.byType(TextFormField).at(0), 142 | "arteev@live.in", 143 | ); 144 | 145 | // Fill in password field. 146 | await tester.enterText( 147 | find.byType(TextFormField).at(1), 148 | "abc", 149 | ); 150 | 151 | // Tap on the Register button. 152 | await tester.tap(loginButton); 153 | await tester.pumpAndSettle(); 154 | 155 | // Verify that key's current state validates to false. 156 | expect( 157 | formKey.currentState.validate(), 158 | false, 159 | ); 160 | 161 | /// Verify that we are still on [Login Page]. 162 | expect( 163 | find.byType(LoginPage), 164 | findsOneWidget, 165 | ); 166 | }); 167 | 168 | testWidgets( 169 | "Validations return false when correct email & password is submitted", 170 | (tester) async { 171 | await tester.pumpWidget(createLoginPageDemoScreen()); 172 | 173 | /// Get the hold of [Form] Widget. 174 | var form = tester.widget(find.byType(Form)); 175 | 176 | /// Get the hold of [Form Key]. 177 | var formKey = form.key as GlobalKey; 178 | 179 | // Fill in email field. 180 | await tester.enterText( 181 | find.byType(TextFormField).at(0), 182 | "arteev@live.in", 183 | ); 184 | 185 | // Fill in password field. 186 | await tester.enterText( 187 | find.byType(TextFormField).at(1), 188 | "abcd12234", 189 | ); 190 | 191 | // Verify that key's current state validates to true. 192 | expect( 193 | formKey.currentState.validate(), 194 | true, 195 | ); 196 | }); 197 | }); 198 | } 199 | -------------------------------------------------------------------------------- /lib/views/login_page.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:knowyourdonor/views/wrapper.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:flutter_svg/flutter_svg.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/provider/auth_provider.dart'; 10 | import 'package:knowyourdonor/components/textbox.dart'; 11 | import 'package:knowyourdonor/components/button.dart'; 12 | import 'package:knowyourdonor/components/loader.dart'; 13 | import 'package:knowyourdonor/constants/text_styles.dart'; 14 | import 'package:knowyourdonor/constants/validators.dart'; 15 | import 'package:knowyourdonor/constants/colors.dart'; 16 | import 'package:knowyourdonor/views/register_page.dart'; 17 | 18 | // Stateful Widget that handles Email Login Task. 19 | class LoginPage extends StatefulWidget { 20 | @override 21 | _LoginPageState createState() => _LoginPageState(); 22 | } 23 | 24 | class _LoginPageState extends State { 25 | // Unique key for the validation of the form. 26 | final _formKey = GlobalKey(); 27 | TextEditingController _emailController = TextEditingController(); 28 | TextEditingController _passwordController = TextEditingController(); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | AuthProvider authProvider = Provider.of(context); 33 | 34 | return Scaffold( 35 | body: SingleChildScrollView( 36 | child: Container( 37 | height: MediaQuery.of(context).size.height, 38 | padding: EdgeInsets.symmetric( 39 | horizontal: 20, 40 | ), 41 | color: backgroundColor, 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | children: [ 45 | SizedBox( 46 | height: 60, 47 | ), 48 | Expanded( 49 | flex: 2, 50 | child: Container( 51 | width: MediaQuery.of(context).size.width * 0.7, 52 | child: SvgPicture.asset( 53 | 'assets/doctor.svg', 54 | ), 55 | ), 56 | ), 57 | Center( 58 | child: Text( 59 | 'KNOW YOUR DONOR', 60 | style: largeTextStyle(), 61 | ), 62 | ), 63 | Expanded( 64 | flex: 2, 65 | child: Form( 66 | key: _formKey, 67 | child: Column( 68 | mainAxisAlignment: MainAxisAlignment.center, 69 | children: [ 70 | TextBox( 71 | context: context, 72 | hintText: "Enter your email", 73 | isPassword: false, 74 | inputController: _emailController, 75 | validator: emailValidator, 76 | keyboardtype: TextInputType.emailAddress, 77 | fieldIcon: Icon( 78 | Icons.email, 79 | color: buttonColor, 80 | ), 81 | ), 82 | SizedBox( 83 | height: 10, 84 | ), 85 | TextBox( 86 | context: context, 87 | hintText: "Enter your password", 88 | isPassword: true, 89 | inputController: _passwordController, 90 | validator: passwordValidator, 91 | keyboardtype: TextInputType.text, 92 | fieldIcon: Icon( 93 | Icons.security, 94 | color: buttonColor, 95 | ), 96 | ), 97 | SizedBox( 98 | height: 10, 99 | ), 100 | GestureDetector( 101 | onTap: () async { 102 | if (_formKey.currentState.validate()) { 103 | if (!await context.read().login( 104 | _emailController.text, 105 | _passwordController.text, 106 | )) { 107 | Fluttertoast.showToast( 108 | msg: "Could not Log in", 109 | ); 110 | } else { 111 | Navigator.pushReplacement( 112 | context, 113 | MaterialPageRoute( 114 | builder: (context) => Wrapper(), 115 | ), 116 | ); 117 | } 118 | } 119 | }, 120 | child: (authProvider.authState == AuthState.LoggingIn) 121 | ? Loader() 122 | : Button( 123 | context: context, 124 | buttonText: "Login", 125 | colorDifference: 60, 126 | ), 127 | ), 128 | SizedBox( 129 | height: 10, 130 | ), 131 | GestureDetector( 132 | onTap: () { 133 | Navigator.pushReplacement( 134 | context, 135 | MaterialPageRoute( 136 | builder: (context) => RegisterPage(), 137 | ), 138 | ); 139 | }, 140 | child: Text( 141 | "Not registered ? Register !", 142 | style: smallTextStyle(), 143 | ), 144 | ), 145 | ], 146 | ), 147 | ), 148 | ), 149 | ], 150 | ), 151 | ), 152 | ), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/views/wrapper.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:knowyourdonor/provider/bottom_navigation_provider.dart'; 5 | import 'package:knowyourdonor/provider/auth_provider.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/constants/colors.dart'; 10 | import 'package:knowyourdonor/constants/text_styles.dart'; 11 | import 'package:knowyourdonor/views/donors_list.dart'; 12 | import 'package:knowyourdonor/views/home_page.dart'; 13 | import 'package:knowyourdonor/views/seekers_list.dart'; 14 | import 'package:knowyourdonor/views/login_page.dart'; 15 | import 'package:knowyourdonor/views/about.dart'; 16 | 17 | class Wrapper extends StatefulWidget { 18 | @override 19 | _WrapperState createState() => _WrapperState(); 20 | } 21 | 22 | class _WrapperState extends State { 23 | /// Initialize [currentTab] variable. 24 | var currentTab = [ 25 | HomePage(), 26 | SeekersList(), 27 | DonorsList(), 28 | ]; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | /// Get the provider for [BNB]. 33 | var provider = Provider.of(context); 34 | 35 | /// Get the user email for [Drawer]. 36 | var email = Provider.of(context).user.email; 37 | 38 | return SafeArea( 39 | child: Scaffold( 40 | drawer: Drawer( 41 | child: ListView( 42 | padding: EdgeInsets.zero, 43 | children: [ 44 | DrawerHeader( 45 | child: Column( 46 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 47 | children: [ 48 | Align( 49 | alignment: Alignment.topRight, 50 | child: CircleAvatar( 51 | child: Text(email[0].toUpperCase()), 52 | ), 53 | ), 54 | Align( 55 | alignment: Alignment.bottomLeft, 56 | child: Text( 57 | "Welcome $email", 58 | style: drawerHeaderTextStyle(), 59 | ), 60 | ), 61 | ], 62 | ), 63 | decoration: BoxDecoration( 64 | color: Color(0xFFE94F37), 65 | ), 66 | ), 67 | ListTile( 68 | title: Row( 69 | children: [ 70 | Icon( 71 | Icons.logout, 72 | color: drawerIconColor, 73 | ), 74 | SizedBox( 75 | width: 8.0, 76 | ), 77 | Text( 78 | "Logout", 79 | style: drawerListTextStyle(), 80 | ), 81 | ], 82 | ), 83 | onTap: () async { 84 | if (await context.read().logout()) { 85 | Navigator.pushReplacement( 86 | context, 87 | MaterialPageRoute( 88 | builder: (context) => LoginPage(), 89 | ), 90 | ); 91 | } 92 | }, 93 | ), 94 | ListTile( 95 | title: Row( 96 | children: [ 97 | Icon( 98 | Icons.person, 99 | color: drawerIconColor, 100 | ), 101 | SizedBox( 102 | width: 8.0, 103 | ), 104 | Text( 105 | "Blood Requests", 106 | style: drawerListTextStyle(), 107 | ), 108 | ], 109 | ), 110 | onTap: () { 111 | Fluttertoast.showToast( 112 | msg: "Feature yet to added", 113 | ); 114 | }, 115 | ), 116 | ListTile( 117 | title: Row( 118 | children: [ 119 | Icon( 120 | Icons.email, 121 | color: drawerIconColor, 122 | ), 123 | SizedBox( 124 | width: 8.0, 125 | ), 126 | Text( 127 | "Developer Contact", 128 | style: drawerListTextStyle(), 129 | ), 130 | ], 131 | ), 132 | onTap: () { 133 | Navigator.push( 134 | context, 135 | MaterialPageRoute( 136 | builder: (context) => About(), 137 | ), 138 | ); 139 | }, 140 | ), 141 | ], 142 | ), 143 | ), 144 | appBar: AppBar( 145 | title: Text( 146 | "Know Your Donor", 147 | style: appBarTextStyle(), 148 | ), 149 | elevation: 0, 150 | backgroundColor: appBarColor, 151 | ), 152 | body: currentTab[provider.currentIndex], 153 | bottomNavigationBar: BottomNavigationBar( 154 | currentIndex: provider.currentIndex, 155 | onTap: (index) { 156 | provider.currentIndex = index; 157 | }, 158 | backgroundColor: backgroundColor, 159 | selectedItemColor: selectedItemColor, 160 | unselectedItemColor: unSelectedItemColor, 161 | items: const [ 162 | BottomNavigationBarItem( 163 | icon: Icon( 164 | Icons.home, 165 | ), 166 | label: "Home", 167 | ), 168 | BottomNavigationBarItem( 169 | icon: Icon( 170 | Icons.list, 171 | ), 172 | label: "Seekers", 173 | ), 174 | BottomNavigationBarItem( 175 | icon: Icon( 176 | Icons.list_alt_outlined, 177 | ), 178 | label: "Donors", 179 | ), 180 | ], 181 | ), 182 | ), 183 | ); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![forthebadge](https://forthebadge.com/images/badges/open-source.svg)](https://forthebadge.com) 2 | [![forthebadge](https://forthebadge.com/images/badges/built-by-developers.svg)](https://forthebadge.com) 3 | [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) 4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | 14 | 15 | # Know Your Donor 🩸 16 | 17 | Blood Donation app made for connecting donor with the patient. During this harsh times of pandemic, normal health care services are facing a set back and Covid-19 is on the top of the priority of health care departments. Also, people are now afraid to leave visit hospitals to arrange blood as they fear they might catch Covid-19. So, to make their life easier Know Your Donor has been developed. 18 | 19 | ## Tech Stack 💻 20 | 21 | - Flutter. 22 | - Dart. 23 | - Firebase. 24 | - Provider for StateManagement. 25 | 26 | ## Features 🚀 27 | 28 | - Email Authentication using Firebase. 29 | - Find donors and recipients in your near by area. 30 | - Volunteer as a Blood Donor or open a request for blood units. 31 | 32 | Feel free to open an issue if you have something in mind, it might make a large impact 💓 33 | 34 | ## Screenshots 👀 35 | 36 |

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ## Getting Started ⭐ 48 | 49 | * Get an API key at . 50 | 51 | * Enable Google Map SDK for each platform. 52 | * Go to [Google Developers Console](https://console.cloud.google.com/). 53 | * Choose the project that you want to enable Google Maps on. 54 | * Select the navigation menu and then select "Google Maps". 55 | * Select "APIs" under the Google Maps menu. 56 | * To enable Google Maps for Android, select "Maps SDK for Android" in the "Additional APIs" section, then select "ENABLE". 57 | * To enable Google Maps for iOS, select "Maps SDK for iOS" in the "Additional APIs" section, then select "ENABLE". 58 | * Make sure the APIs you enabled are under the "Enabled APIs" section. 59 | 60 | * You can also find detailed steps to get start with Google Maps Platform [here](https://developers.google.com/maps/gmp-get-started). 61 | 62 | ### Web 63 | 64 | ```html 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | ### Android 72 | 73 | Specify your API key in the application manifest `android/app/src/main/AndroidManifest.xml`: 74 | 75 | ```xml 76 | 80 | ``` 81 | 82 | ### iOS 83 | 84 | Specify your API key in the application delegate `ios/Runner/AppDelegate.m`: 85 | 86 | ```objectivec 87 | #include "AppDelegate.h" 88 | #include "GeneratedPluginRegistrant.h" 89 | #import "GoogleMaps/GoogleMaps.h" 90 | 91 | @implementation AppDelegate 92 | 93 | - (BOOL)application:(UIApplication *)application 94 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 95 | [GMSServices provideAPIKey:@"YOUR KEY HERE"]; 96 | [GeneratedPluginRegistrant registerWithRegistry:self]; 97 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 98 | } 99 | @end 100 | ``` 101 | 102 | Or in your swift code, specify your API key in the application delegate `ios/Runner/AppDelegate.swift`: 103 | 104 | ```swift 105 | import UIKit 106 | import Flutter 107 | import GoogleMaps 108 | 109 | @UIApplicationMain 110 | @objc class AppDelegate: FlutterAppDelegate { 111 | override func application( 112 | _ application: UIApplication, 113 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 114 | ) -> Bool { 115 | GMSServices.provideAPIKey("YOUR KEY HERE") 116 | GeneratedPluginRegistrant.register(with: self) 117 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 118 | } 119 | } 120 | ``` 121 | Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file 122 | with the key `io.flutter.embedded_views_preview` and the value `YES`. 123 | 124 | ## How to setup locally ? 🏁 125 | 126 | 1. Fork the repository. 127 | 128 | 2. Clone the repository using the following command. 129 | ``` 130 | git clone https://github.com//KnowYourDonor.git 131 | ``` 132 | 133 | 3. Open the code in your favorite code editor. 134 | 135 | 4. Install the dependencies using the following command: 136 | 137 | ``` 138 | $ flutter pub get 139 | ``` 140 | 141 | 5. Build the app using the following command: 142 | 143 | ``` 144 | $ flutter run 145 | ``` 146 | 147 | ## Repository Structure 🚧 148 | ``` 149 | * lib/ : all the code which is making the app run goes in this directory. 150 | * lib/components : contains code for reusable widgets in the app like buttons, appbars, textboxes etc. 151 | * lib/constants : contains the configuration for color pallets, validator functions and text styles. 152 | * lib/models : contains code for Seeker and Donor Model. 153 | * lib/provider : contains code for all the Provider classes for statemanagement. 154 | * lib/repository : contains code for Donor and Seeker Repository for communicating with Cloud Firestore database. 155 | * lib/views : contains code for the frontend screens. 156 | * test : contains the code for widget and unit test of the Application. 157 | ``` 158 | 159 | ## Interested in contributing ? 🌼 160 | 161 | See the [contributor's guide!](CONTRIBUTING.md) 162 | 163 | ## Questions or issues ? 164 | 165 | If you have general question about the project. Feel free to join [discord server](https://discord.gg/8CsHncucds). 166 | 167 | ## License 168 | 169 | [MIT](LICENSE) © 2021 170 | 171 | ### Learn Flutter ? 172 | 173 | Follow this [link](https://flutter.dev/) 174 | -------------------------------------------------------------------------------- /test/widget_tests/register_page_test.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:firebase_core/firebase_core.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/views/login_page.dart'; 10 | import 'package:knowyourdonor/views/register_page.dart'; 11 | import 'package:knowyourdonor/provider/auth_provider.dart'; 12 | import 'package:knowyourdonor/provider/bottom_navigation_provider.dart'; 13 | import 'package:knowyourdonor/repository/donorRepository.dart'; 14 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 15 | import '../mock.dart'; 16 | 17 | Widget createRegisterPageDemoScreen() => MultiProvider( 18 | providers: [ 19 | ChangeNotifierProvider( 20 | create: (_) => AuthProvider.instance(), 21 | ), 22 | ChangeNotifierProvider( 23 | create: (_) => SeekerRepository(FirebaseFirestore.instance), 24 | ), 25 | ChangeNotifierProvider( 26 | create: (_) => DonorRepository(FirebaseFirestore.instance), 27 | ), 28 | ChangeNotifierProvider( 29 | create: (_) => BottomNavigationBarProvider(), 30 | ) 31 | ], 32 | child: MaterialApp( 33 | home: RegisterPage(), 34 | ), 35 | ); 36 | 37 | void main() { 38 | /// Initializing [FirebaseApp] just as in [main.dart]. 39 | setupFirebaseAuthMocks(); 40 | setUpAll(() async { 41 | await Firebase.initializeApp(); 42 | }); 43 | group("Register Page Tests", () { 44 | testWidgets("Check if Register Page shows up", (tester) async { 45 | await tester.pumpWidget(createRegisterPageDemoScreen()); 46 | 47 | /// Check if [Register Page] shows up. 48 | expect( 49 | find.byType(RegisterPage), 50 | findsOneWidget, 51 | ); 52 | }); 53 | 54 | testWidgets("Validations return false when empty form is submitted", 55 | (tester) async { 56 | await tester.pumpWidget(createRegisterPageDemoScreen()); 57 | 58 | /// Get the hold of [Form] Widget. 59 | var form = tester.widget(find.byType(Form)); 60 | 61 | /// Get the hold of [Form Key]. 62 | var formKey = form.key as GlobalKey; 63 | 64 | // Get hold of Register button. 65 | var registerButton = find.text("Register"); 66 | 67 | // Tap on the Register button. 68 | await tester.tap(registerButton); 69 | await tester.pumpAndSettle(); 70 | 71 | // Verify that key's current state validates to false. 72 | expect( 73 | formKey.currentState.validate(), 74 | false, 75 | ); 76 | 77 | /// Verify that we are still on [Register Page]. 78 | expect( 79 | find.byType(RegisterPage), 80 | findsOneWidget, 81 | ); 82 | }); 83 | 84 | testWidgets("Validations return false when incorrect email is submitted", 85 | (tester) async { 86 | await tester.pumpWidget(createRegisterPageDemoScreen()); 87 | 88 | /// Get the hold of [Form] Widget. 89 | var form = tester.widget(find.byType(Form)); 90 | 91 | /// Get the hold of [Form Key]. 92 | var formKey = form.key as GlobalKey; 93 | 94 | // Get hold of Register button. 95 | var registerButton = find.text("Register"); 96 | 97 | // Fill in email field. 98 | await tester.enterText( 99 | find.byType(TextFormField).at(0), 100 | "arteev", 101 | ); 102 | 103 | // Fill in password field. 104 | await tester.enterText( 105 | find.byType(TextFormField).at(1), 106 | "abcd1234", 107 | ); 108 | 109 | // Tap on the Register button. 110 | await tester.tap(registerButton); 111 | await tester.pumpAndSettle(); 112 | 113 | // Verify that key's current state validates to false. 114 | expect( 115 | formKey.currentState.validate(), 116 | false, 117 | ); 118 | 119 | /// Verify that we are still on [Register Page]. 120 | expect( 121 | find.byType(RegisterPage), 122 | findsOneWidget, 123 | ); 124 | }); 125 | 126 | testWidgets("Validations return false when incorrect password is submitted", 127 | (tester) async { 128 | await tester.pumpWidget(createRegisterPageDemoScreen()); 129 | 130 | /// Get the hold of [Form] Widget. 131 | var form = tester.widget(find.byType(Form)); 132 | 133 | /// Get the hold of [Form Key]. 134 | var formKey = form.key as GlobalKey; 135 | 136 | // Get hold of Register button. 137 | var registerButton = find.text("Register"); 138 | 139 | // Fill in email field. 140 | await tester.enterText( 141 | find.byType(TextFormField).at(0), 142 | "arteev@live.in", 143 | ); 144 | 145 | // Fill in password field. 146 | await tester.enterText( 147 | find.byType(TextFormField).at(1), 148 | "abc", 149 | ); 150 | 151 | // Tap on the Register button. 152 | await tester.tap(registerButton); 153 | await tester.pumpAndSettle(); 154 | 155 | // Verify that key's current state validates to false. 156 | expect( 157 | formKey.currentState.validate(), 158 | false, 159 | ); 160 | 161 | /// Verify that we are still on [Register Page]. 162 | expect( 163 | find.byType(RegisterPage), 164 | findsOneWidget, 165 | ); 166 | }); 167 | 168 | testWidgets( 169 | "Validations return false when correct email & password is submitted", 170 | (tester) async { 171 | await tester.pumpWidget(createRegisterPageDemoScreen()); 172 | 173 | /// Get the hold of [Form] Widget. 174 | var form = tester.widget(find.byType(Form)); 175 | 176 | /// Get the hold of [Form Key]. 177 | var formKey = form.key as GlobalKey; 178 | 179 | // Fill in email field. 180 | await tester.enterText( 181 | find.byType(TextFormField).at(0), 182 | "arteev@live.in", 183 | ); 184 | 185 | // Fill in password field. 186 | await tester.enterText( 187 | find.byType(TextFormField).at(1), 188 | "abcd12234", 189 | ); 190 | 191 | // Verify that key's current state validates to true. 192 | expect( 193 | formKey.currentState.validate(), 194 | true, 195 | ); 196 | }); 197 | 198 | testWidgets("On Log In tap navigates to Log In Page", (tester) async { 199 | await tester.pumpWidget(createRegisterPageDemoScreen()); 200 | 201 | // Find Log In! text. 202 | var login = find.text("Already registered ? Log In!"); 203 | 204 | // Tap on the Log In! text. 205 | await tester.tap(login); 206 | await tester.pumpAndSettle(); 207 | 208 | // Check if Login Page shows up. 209 | expect( 210 | find.byType(LoginPage), 211 | findsOneWidget, 212 | ); 213 | }); 214 | }); 215 | } 216 | -------------------------------------------------------------------------------- /test/widget_tests/donote_blood_form_test.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'dart:math'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_core/firebase_core.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | // Local imports. 10 | import 'package:knowyourdonor/views/donate_blood_form.dart'; 11 | import 'package:knowyourdonor/provider/auth_provider.dart'; 12 | import 'package:knowyourdonor/provider/bottom_navigation_provider.dart'; 13 | import 'package:knowyourdonor/repository/donorRepository.dart'; 14 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 15 | import '../mock.dart'; 16 | 17 | Widget createDonateBloodFormDemoScreen() => MultiProvider( 18 | providers: [ 19 | ChangeNotifierProvider( 20 | create: (_) => AuthProvider.instance(), 21 | ), 22 | ChangeNotifierProvider( 23 | create: (_) => SeekerRepository(FirebaseFirestore.instance), 24 | ), 25 | ChangeNotifierProvider( 26 | create: (_) => DonorRepository(FirebaseFirestore.instance), 27 | ), 28 | ChangeNotifierProvider( 29 | create: (_) => BottomNavigationBarProvider(), 30 | ) 31 | ], 32 | child: MaterialApp( 33 | home: DonateBlood(), 34 | ), 35 | ); 36 | 37 | void main() { 38 | /// Initializing [FirebaseApp] just as in [main.dart]. 39 | setupFirebaseAuthMocks(); 40 | setUpAll(() async { 41 | await Firebase.initializeApp(); 42 | }); 43 | 44 | group("Blood Form Tests", () { 45 | testWidgets("Check if Blood Form shows up", (tester) async { 46 | await tester.pumpWidget(createDonateBloodFormDemoScreen()); 47 | 48 | /// Check if [Blood Form] shows up. 49 | expect( 50 | find.byType(DonateBlood), 51 | findsOneWidget, 52 | ); 53 | }); 54 | 55 | testWidgets("Form does not submit when fields are kept empty", 56 | (tester) async { 57 | await tester.pumpWidget(createDonateBloodFormDemoScreen()); 58 | 59 | /// Get the hold of [Form] Widget. 60 | var form = tester.widget(find.byType(Form)); 61 | 62 | /// Get the hold of [Form Key]. 63 | var formKey = form.key as GlobalKey; 64 | 65 | // Get the hold of Post button. 66 | var post = find.text("Post"); 67 | 68 | // Tap on the Post button. 69 | await tester.tap(post); 70 | await tester.pumpAndSettle(); 71 | 72 | // Verify that key's current state validates to false. 73 | expect( 74 | formKey.currentState.validate(), 75 | false, 76 | ); 77 | }); 78 | 79 | testWidgets("Form does not submit when any of field is kept empty", 80 | (tester) async { 81 | await tester.pumpWidget(createDonateBloodFormDemoScreen()); 82 | 83 | /// Get the hold of [Form] Widget. 84 | var form = tester.widget(find.byType(Form)); 85 | 86 | /// Get the hold of [Form Key]. 87 | var formKey = form.key as GlobalKey; 88 | 89 | /// Get the total number of [TextFormField]'s. 90 | var length = tester.widgetList(find.byType(TextFormField)).length; 91 | 92 | /// Get the number between [0, 4]. 93 | var index = Random().nextInt(length); 94 | 95 | if (index == 0) { 96 | // Fill all the other TextFormFields except at index 0. 97 | // Fill in Address Line. 98 | await tester.enterText( 99 | find.byType(TextFormField).at(1), 100 | "Durga Nagar, Jammu", 101 | ); 102 | 103 | // Fill in bloodgroup field. 104 | await tester.enterText( 105 | find.byType(TextFormField).at(2), 106 | "A+", 107 | ); 108 | 109 | // Fill in phonenumber. 110 | await tester.enterText( 111 | find.byType(TextFormField).at(3), 112 | "9419191919", 113 | ); 114 | } else if (index == 1) { 115 | // Fill all the other TextFormFields except at index 1. 116 | // Fill in Donors Name. 117 | await tester.enterText( 118 | find.byType(TextFormField).at(0), 119 | "Arteev Raina", 120 | ); 121 | 122 | // Fill in bloodgroup field. 123 | await tester.enterText( 124 | find.byType(TextFormField).at(2), 125 | "A+", 126 | ); 127 | 128 | // Fill in phonenumber. 129 | await tester.enterText( 130 | find.byType(TextFormField).at(3), 131 | "9419191919", 132 | ); 133 | } else if (index == 2) { 134 | // Fill all the other TextFormFields except at index 2. 135 | // Fill in Donors Name. 136 | await tester.enterText( 137 | find.byType(TextFormField).at(0), 138 | "Arteev Raina", 139 | ); 140 | 141 | // Fill in Address Line. 142 | await tester.enterText( 143 | find.byType(TextFormField).at(1), 144 | "Durga Nagar, Jammu", 145 | ); 146 | 147 | // Fill in phonenumber. 148 | await tester.enterText( 149 | find.byType(TextFormField).at(3), 150 | "9419191919", 151 | ); 152 | } else if (index == 3) { 153 | // Fill all the other TextFormFields except at index 3. 154 | // Fill in Donors Name. 155 | await tester.enterText( 156 | find.byType(TextFormField).at(0), 157 | "Arteev Raina", 158 | ); 159 | 160 | // Fill in Address Line. 161 | await tester.enterText( 162 | find.byType(TextFormField).at(1), 163 | "Durga Nagar, Jammu", 164 | ); 165 | 166 | // Fill in bloodgroup field. 167 | await tester.enterText( 168 | find.byType(TextFormField).at(2), 169 | "A+", 170 | ); 171 | } 172 | 173 | // Verify that key's current state validates to false. 174 | expect( 175 | formKey.currentState.validate(), 176 | false, 177 | ); 178 | }); 179 | 180 | testWidgets("Form validates to true when all fields are correctly filled", 181 | (tester) async { 182 | await tester.pumpWidget(createDonateBloodFormDemoScreen()); 183 | 184 | /// Get the hold of [Form] Widget. 185 | var form = tester.widget(find.byType(Form)); 186 | 187 | /// Get the hold of [Form Key]. 188 | var formKey = form.key as GlobalKey; 189 | 190 | // Fill in Donors Name. 191 | await tester.enterText( 192 | find.byType(TextFormField).at(0), 193 | "Arteev Raina", 194 | ); 195 | 196 | // Fill in Address Line. 197 | await tester.enterText( 198 | find.byType(TextFormField).at(1), 199 | "Durga Nagar, Jammu", 200 | ); 201 | 202 | // Fill in bloodgroup field. 203 | await tester.enterText( 204 | find.byType(TextFormField).at(2), 205 | "A+", 206 | ); 207 | 208 | // Fill in phonenumber. 209 | await tester.enterText( 210 | find.byType(TextFormField).at(3), 211 | "9419191919", 212 | ); 213 | 214 | // Verify that key's current state validates to true. 215 | expect( 216 | formKey.currentState.validate(), 217 | true, 218 | ); 219 | }); 220 | }); 221 | } 222 | -------------------------------------------------------------------------------- /lib/views/home_page.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:carousel_slider/carousel_slider.dart'; 5 | 6 | // Local imports. 7 | import 'package:knowyourdonor/constants/colors.dart'; 8 | import 'package:knowyourdonor/constants/text_styles.dart'; 9 | import 'package:knowyourdonor/views/donate_blood_form.dart'; 10 | import 'package:knowyourdonor/views/request_blood_form.dart'; 11 | 12 | // Screen for Home Page of the App. 13 | class HomePage extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | child: Stack( 18 | children: [ 19 | Column( 20 | children: [ 21 | Expanded( 22 | flex: 1, 23 | child: Container( 24 | decoration: BoxDecoration( 25 | color: upperHalfColor, 26 | ), 27 | child: SvgPicture.asset( 28 | 'assets/doctor.svg', 29 | ), 30 | ), 31 | ), 32 | Expanded( 33 | flex: 1, 34 | child: Container( 35 | decoration: BoxDecoration( 36 | color: lowerHalfColor, 37 | ), 38 | child: CarouselSlider( 39 | items: [ 40 | //1st Image of Slider 41 | Container( 42 | margin: EdgeInsets.all(6.0), 43 | decoration: BoxDecoration( 44 | borderRadius: BorderRadius.circular(8.0), 45 | image: DecorationImage( 46 | image: AssetImage("assets/Quote 1.jpg"), 47 | fit: BoxFit.cover, 48 | ), 49 | ), 50 | ), 51 | 52 | //2nd Image of Slider 53 | Container( 54 | margin: EdgeInsets.all(6.0), 55 | decoration: BoxDecoration( 56 | borderRadius: BorderRadius.circular(8.0), 57 | image: DecorationImage( 58 | image: AssetImage("assets/Quote 2.jpeg"), 59 | fit: BoxFit.cover, 60 | ), 61 | ), 62 | ), 63 | 64 | //3rd Image of Slider 65 | Container( 66 | margin: EdgeInsets.all(6.0), 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.circular(8.0), 69 | image: DecorationImage( 70 | image: AssetImage("assets/Quote 3.jpeg"), 71 | fit: BoxFit.cover, 72 | ), 73 | ), 74 | ), 75 | 76 | //4th Image of Slider 77 | Container( 78 | margin: EdgeInsets.all(6.0), 79 | decoration: BoxDecoration( 80 | borderRadius: BorderRadius.circular(8.0), 81 | image: DecorationImage( 82 | image: AssetImage("assets/Quote 4.jpeg"), 83 | fit: BoxFit.cover, 84 | ), 85 | ), 86 | ), 87 | 88 | //5th Image of Slider 89 | Container( 90 | margin: EdgeInsets.all(6.0), 91 | decoration: BoxDecoration( 92 | borderRadius: BorderRadius.circular(8.0), 93 | image: DecorationImage( 94 | image: AssetImage("assets/Quote 5.jpeg"), 95 | fit: BoxFit.cover, 96 | ), 97 | ), 98 | ), 99 | ], 100 | //Slider Container properties 101 | options: CarouselOptions( 102 | height: 180.0, 103 | enlargeCenterPage: true, 104 | autoPlay: true, 105 | aspectRatio: 16 / 9, 106 | autoPlayCurve: Curves.fastOutSlowIn, 107 | enableInfiniteScroll: true, 108 | autoPlayAnimationDuration: Duration(milliseconds: 800), 109 | viewportFraction: 0.8, 110 | ), 111 | ), 112 | ), 113 | ), 114 | ], 115 | ), 116 | Center( 117 | child: Container( 118 | height: MediaQuery.of(context).size.height / 10, 119 | child: Row( 120 | mainAxisAlignment: MainAxisAlignment.center, 121 | children: [ 122 | GestureDetector( 123 | onTap: () { 124 | Navigator.push( 125 | context, 126 | MaterialPageRoute( 127 | builder: (context) => RequestBlood(), 128 | ), 129 | ); 130 | }, 131 | child: Container( 132 | width: MediaQuery.of(context).size.width / 2.5, 133 | decoration: BoxDecoration( 134 | color: lowerHalfColor, 135 | border: Border.all( 136 | width: 3, 137 | color: borderColor, 138 | style: BorderStyle.solid, 139 | ), 140 | borderRadius: BorderRadius.only( 141 | bottomLeft: Radius.circular(50), 142 | topLeft: Radius.circular(50), 143 | topRight: Radius.circular(0), 144 | bottomRight: Radius.circular(0), 145 | ), 146 | ), 147 | child: Center( 148 | child: Text( 149 | "Request Blood", 150 | style: homePageButtonTextStyle(), 151 | ), 152 | ), 153 | ), 154 | ), 155 | GestureDetector( 156 | onTap: () { 157 | Navigator.push( 158 | context, 159 | MaterialPageRoute( 160 | builder: (context) => DonateBlood(), 161 | ), 162 | ); 163 | }, 164 | child: Container( 165 | width: MediaQuery.of(context).size.width / 2.5, 166 | decoration: BoxDecoration( 167 | color: lowerHalfColor, 168 | border: Border.all( 169 | width: 3, 170 | color: borderColor, 171 | style: BorderStyle.solid, 172 | ), 173 | borderRadius: BorderRadius.only( 174 | bottomRight: Radius.circular(50), 175 | topRight: Radius.circular(50), 176 | topLeft: Radius.circular(0), 177 | bottomLeft: Radius.circular(0), 178 | ), 179 | ), 180 | child: Center( 181 | child: Text( 182 | "Donate Blood", 183 | style: homePageButtonTextStyle(), 184 | ), 185 | ), 186 | ), 187 | ) 188 | ], 189 | ), 190 | ), 191 | ), 192 | ], 193 | ), 194 | ); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/views/donate_blood_form.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:geocoding/geocoding.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/models/Donor.dart'; 10 | import 'package:knowyourdonor/provider/auth_provider.dart'; 11 | import 'package:knowyourdonor/components/loader.dart'; 12 | import 'package:knowyourdonor/components/formbutton.dart'; 13 | import 'package:knowyourdonor/constants/colors.dart'; 14 | import 'package:knowyourdonor/constants/text_styles.dart'; 15 | import 'package:knowyourdonor/constants/validators.dart'; 16 | import 'package:knowyourdonor/repository/donorRepository.dart'; 17 | 18 | class DonateBlood extends StatefulWidget { 19 | @override 20 | _DonateBloodState createState() => _DonateBloodState(); 21 | } 22 | 23 | class _DonateBloodState extends State { 24 | // Unique key for the validation of the form. 25 | final _formKey = GlobalKey(); 26 | 27 | // Text Controllers for Forms. 28 | TextEditingController _donorNameController = TextEditingController(); 29 | TextEditingController _addressController = TextEditingController(); 30 | TextEditingController _bloodgroupController = TextEditingController(); 31 | TextEditingController _phoneNumberController = TextEditingController(); 32 | @override 33 | Widget build(BuildContext context) { 34 | // Provider for DonorRepository. 35 | DonorRepository donorProvider = Provider.of(context); 36 | 37 | return Scaffold( 38 | appBar: AppBar( 39 | title: Text( 40 | "Know Your Donor", 41 | style: appBarTextStyle(), 42 | ), 43 | elevation: 0, 44 | backgroundColor: appBarColor, 45 | ), 46 | body: Stack( 47 | children: [ 48 | Positioned.fill( 49 | child: Opacity( 50 | opacity: 0.5, 51 | child: SvgPicture.asset( 52 | 'assets/stethoscope.svg', 53 | fit: BoxFit.fitWidth, 54 | alignment: Alignment.center, 55 | ), 56 | ), 57 | ), 58 | SingleChildScrollView( 59 | child: Container( 60 | height: MediaQuery.of(context).size.height - 100, 61 | padding: EdgeInsets.symmetric( 62 | horizontal: 20, 63 | ), 64 | child: Form( 65 | key: _formKey, 66 | child: Column( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | Text( 70 | "Enter details to donate blood", 71 | style: mediumTextStyle(), 72 | ), 73 | SizedBox( 74 | height: 20.0, 75 | ), 76 | TextFormField( 77 | controller: _donorNameController, 78 | validator: userNameValidator, 79 | style: smallTextStyle(), 80 | decoration: InputDecoration( 81 | border: OutlineInputBorder(), 82 | labelText: "Donor's Name", 83 | hintText: "Enter your Name", 84 | prefixIcon: Icon( 85 | Icons.person, 86 | ), 87 | ), 88 | ), 89 | SizedBox( 90 | height: 8.0, 91 | ), 92 | TextFormField( 93 | controller: _addressController, 94 | validator: addressValidator, 95 | style: smallTextStyle(), 96 | decoration: InputDecoration( 97 | border: OutlineInputBorder(), 98 | labelText: "Address Line", 99 | hintText: "Street Name, City Name", 100 | prefixIcon: Icon( 101 | Icons.home, 102 | ), 103 | ), 104 | ), 105 | SizedBox( 106 | height: 8.0, 107 | ), 108 | TextFormField( 109 | controller: _bloodgroupController, 110 | validator: bloodGroupValidator, 111 | style: smallTextStyle(), 112 | decoration: InputDecoration( 113 | border: OutlineInputBorder(), 114 | labelText: "Blood Group", 115 | hintText: "Enter your Blood Group", 116 | prefixIcon: Icon( 117 | Icons.category, 118 | ), 119 | ), 120 | ), 121 | SizedBox( 122 | height: 8.0, 123 | ), 124 | TextFormField( 125 | controller: _phoneNumberController, 126 | validator: phoneNumberValidator, 127 | keyboardType: TextInputType.number, 128 | style: smallTextStyle(), 129 | decoration: InputDecoration( 130 | border: OutlineInputBorder(), 131 | labelText: "Phone Number", 132 | hintText: "Enter your Phone Number", 133 | prefixIcon: Icon( 134 | Icons.phone, 135 | ), 136 | ), 137 | ), 138 | SizedBox( 139 | height: 16.0, 140 | ), 141 | (donorProvider.state == SubmitState.Submitting) 142 | ? Loader() 143 | : GestureDetector( 144 | onTap: () async { 145 | if (_formKey.currentState.validate()) { 146 | /// First get the [lat] and [long] of the 147 | /// address and then create [instance]. 148 | List locations = 149 | await locationFromAddress( 150 | _addressController.text, 151 | ); 152 | 153 | // Get the first item in the list. 154 | Location coordinates = locations.first; 155 | 156 | /// Create [instance] of [Donor Model] 157 | /// and then post it using [DonorRepository] 158 | /// function. 159 | // Get the user email ID. 160 | var email = Provider.of(context) 161 | .user 162 | .email; 163 | Donor donor = Donor( 164 | _donorNameController.text, 165 | email, 166 | _addressController.text, 167 | _bloodgroupController.text, 168 | int.parse(_phoneNumberController.text), 169 | coordinates.latitude, 170 | coordinates.longitude, 171 | ); 172 | 173 | if (await context 174 | .read() 175 | .postDonor(donor)) { 176 | /// If everything goes fine 177 | /// then show toast. 178 | Fluttertoast.showToast( 179 | msg: "Post Created", 180 | ); 181 | 182 | // Clear all the text fields. 183 | _donorNameController.clear(); 184 | _addressController.clear(); 185 | _bloodgroupController.clear(); 186 | _phoneNumberController.clear(); 187 | } else { 188 | /// Else toast that Request is 189 | /// rejected. 190 | Fluttertoast.showToast( 191 | msg: "Request Rejected", 192 | ); 193 | } 194 | } 195 | }, 196 | child: FormButton( 197 | buttonText: "Post", 198 | colorDifference: 60, 199 | ), 200 | ), 201 | ], 202 | ), 203 | ), 204 | ), 205 | ), 206 | ], 207 | ), 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /lib/views/request_blood_form.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:geocoding/geocoding.dart'; 7 | 8 | // Local imports. 9 | import 'package:knowyourdonor/models/Seeker.dart'; 10 | import 'package:knowyourdonor/provider/auth_provider.dart'; 11 | import 'package:knowyourdonor/components/loader.dart'; 12 | import 'package:knowyourdonor/components/formbutton.dart'; 13 | import 'package:knowyourdonor/constants/colors.dart'; 14 | import 'package:knowyourdonor/constants/text_styles.dart'; 15 | import 'package:knowyourdonor/constants/validators.dart'; 16 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 17 | 18 | class RequestBlood extends StatefulWidget { 19 | @override 20 | _RequestBloodState createState() => _RequestBloodState(); 21 | } 22 | 23 | class _RequestBloodState extends State { 24 | // Unique key for the validation of the form. 25 | final _formKey = GlobalKey(); 26 | 27 | // Text Controllers for Forms. 28 | TextEditingController _seekerNameController = TextEditingController(); 29 | TextEditingController _addressController = TextEditingController(); 30 | TextEditingController _bloodgroupController = TextEditingController(); 31 | TextEditingController _unitsController = TextEditingController(); 32 | TextEditingController _phoneNumberController = TextEditingController(); 33 | 34 | // Value for checkbox. 35 | bool isPlatelet = false; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | // Provider for SeekerRepository. 40 | SeekerRepository seekerProvider = Provider.of(context); 41 | 42 | // Get the user email id. 43 | var email = Provider.of(context).user.email; 44 | 45 | return Scaffold( 46 | appBar: AppBar( 47 | title: Text( 48 | "Know Your Donor", 49 | style: appBarTextStyle(), 50 | ), 51 | elevation: 0, 52 | backgroundColor: appBarColor, 53 | ), 54 | body: Stack( 55 | children: [ 56 | Positioned.fill( 57 | child: Opacity( 58 | opacity: 0.5, 59 | child: SvgPicture.asset( 60 | 'assets/stethoscope.svg', 61 | fit: BoxFit.fitWidth, 62 | alignment: Alignment.center, 63 | ), 64 | ), 65 | ), 66 | SingleChildScrollView( 67 | child: Container( 68 | height: MediaQuery.of(context).size.height - 100, 69 | padding: EdgeInsets.symmetric( 70 | horizontal: 20, 71 | ), 72 | child: Form( 73 | key: _formKey, 74 | child: Column( 75 | mainAxisAlignment: MainAxisAlignment.center, 76 | children: [ 77 | Text( 78 | "Enter details to request blood", 79 | style: mediumTextStyle(), 80 | ), 81 | SizedBox( 82 | height: 20.0, 83 | ), 84 | TextFormField( 85 | controller: _seekerNameController, 86 | validator: userNameValidator, 87 | style: smallTextStyle(), 88 | decoration: InputDecoration( 89 | border: OutlineInputBorder(), 90 | labelText: "Seeker's Name", 91 | hintText: "Enter name", 92 | prefixIcon: Icon( 93 | Icons.person, 94 | ), 95 | ), 96 | ), 97 | SizedBox( 98 | height: 8.0, 99 | ), 100 | TextFormField( 101 | controller: _addressController, 102 | validator: addressValidator, 103 | style: smallTextStyle(), 104 | decoration: InputDecoration( 105 | border: OutlineInputBorder(), 106 | labelText: "Address Line", 107 | hintText: "Street Name, City Name", 108 | prefixIcon: Icon( 109 | Icons.home, 110 | ), 111 | ), 112 | ), 113 | SizedBox( 114 | height: 8.0, 115 | ), 116 | Row( 117 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 118 | children: [ 119 | Container( 120 | width: MediaQuery.of(context).size.width * 0.5, 121 | child: TextFormField( 122 | controller: _bloodgroupController, 123 | validator: bloodGroupValidator, 124 | style: smallTextStyle(), 125 | decoration: InputDecoration( 126 | border: OutlineInputBorder(), 127 | labelText: "Blood Group", 128 | hintText: "Blood Group", 129 | prefixIcon: Icon( 130 | Icons.category, 131 | ), 132 | ), 133 | ), 134 | ), 135 | Text( 136 | "Platelets \nRequired ?", 137 | style: smallTextStyle(), 138 | ), 139 | Checkbox( 140 | value: isPlatelet, 141 | onChanged: (bool newValue) { 142 | setState(() { 143 | isPlatelet = newValue; 144 | }); 145 | }, 146 | activeColor: errorTextColor, 147 | ), 148 | ], 149 | ), 150 | SizedBox( 151 | height: 8.0, 152 | ), 153 | TextFormField( 154 | controller: _unitsController, 155 | validator: unitsValidator, 156 | keyboardType: TextInputType.number, 157 | style: smallTextStyle(), 158 | decoration: InputDecoration( 159 | border: OutlineInputBorder(), 160 | labelText: "Units required", 161 | hintText: "Enter units of blood required", 162 | prefixIcon: Icon( 163 | Icons.stacked_bar_chart, 164 | ), 165 | ), 166 | ), 167 | SizedBox( 168 | height: 8.0, 169 | ), 170 | TextFormField( 171 | controller: _phoneNumberController, 172 | validator: phoneNumberValidator, 173 | keyboardType: TextInputType.number, 174 | style: smallTextStyle(), 175 | decoration: InputDecoration( 176 | border: OutlineInputBorder(), 177 | labelText: "Phone Number", 178 | hintText: "Enter your Phone Number", 179 | prefixIcon: Icon( 180 | Icons.phone, 181 | ), 182 | ), 183 | ), 184 | SizedBox( 185 | height: 16.0, 186 | ), 187 | (seekerProvider.state == SubmitState.Submitting) 188 | ? Loader() 189 | : GestureDetector( 190 | onTap: () async { 191 | if (_formKey.currentState.validate()) { 192 | /// First get the [lat] and [long] of the 193 | /// address and then create [instance]. 194 | List locations = 195 | await locationFromAddress( 196 | _addressController.text, 197 | ); 198 | 199 | // Get the first item in the list. 200 | Location coordinates = locations.first; 201 | 202 | /// Create [instance] of [Seeker Model] 203 | /// and then post it using [SeekerRepository] 204 | /// function. 205 | Seeker seeker = Seeker( 206 | _seekerNameController.text, 207 | email, 208 | _addressController.text, 209 | _bloodgroupController.text, 210 | int.parse(_unitsController.text), 211 | int.parse(_phoneNumberController.text), 212 | coordinates.latitude, 213 | coordinates.longitude, 214 | isPlatelet, 215 | ); 216 | 217 | if (await context 218 | .read() 219 | .postSeeker(seeker)) { 220 | /// If everything goes fine 221 | /// then show toast. 222 | Fluttertoast.showToast( 223 | msg: "Request Created", 224 | ); 225 | 226 | // Clear all text fields. 227 | _seekerNameController.clear(); 228 | _addressController.clear(); 229 | _bloodgroupController.clear(); 230 | _unitsController.clear(); 231 | _phoneNumberController.clear(); 232 | } else { 233 | /// Else toast that Request is 234 | /// rejected. 235 | Fluttertoast.showToast( 236 | msg: "Request Rejected", 237 | ); 238 | } 239 | } 240 | }, 241 | child: FormButton( 242 | buttonText: "Post", 243 | colorDifference: 60, 244 | ), 245 | ), 246 | ], 247 | ), 248 | ), 249 | ), 250 | ), 251 | ], 252 | ), 253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /assets/girl.svg: -------------------------------------------------------------------------------- 1 | Meditation -------------------------------------------------------------------------------- /lib/views/donors_list.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'dart:async'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:geolocator/geolocator.dart'; 7 | import 'package:flutter_svg/flutter_svg.dart'; 8 | import 'package:cloud_firestore/cloud_firestore.dart'; 9 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 10 | import 'package:url_launcher/url_launcher.dart'; 11 | 12 | // Local imports. 13 | import 'package:knowyourdonor/provider/auth_provider.dart'; 14 | import 'package:knowyourdonor/components/loader.dart'; 15 | import 'package:knowyourdonor/components/alertButton.dart'; 16 | import 'package:knowyourdonor/constants/text_styles.dart'; 17 | import 'package:knowyourdonor/constants/colors.dart'; 18 | import 'package:knowyourdonor/repository/donorRepository.dart'; 19 | import 'package:knowyourdonor/repository/location_repository.dart'; 20 | 21 | class DonorsList extends StatefulWidget { 22 | @override 23 | _DonorsListState createState() => _DonorsListState(); 24 | } 25 | 26 | class _DonorsListState extends State { 27 | Completer _controller = Completer(); 28 | Map markers = {}; 29 | 30 | // Donor fields. 31 | String address; 32 | String bloodGroup; 33 | String name; 34 | double latitude; 35 | double longitude; 36 | int phoneNumber; 37 | 38 | final LocationRepository _donorLocationRepository = LocationRepository(); 39 | 40 | Stream donors; 41 | 42 | double currentLat, currentLong; 43 | 44 | bool isTapped = false; 45 | 46 | @override 47 | void initState() { 48 | getCurrentLocation(); 49 | fetchDonors(); 50 | super.initState(); 51 | } 52 | 53 | /// Function for getting [Current Location]. 54 | Future getCurrentLocation() async { 55 | Position res = await _donorLocationRepository.getCurrentLocation(); 56 | 57 | // Set the state and show map. 58 | setState(() { 59 | currentLat = res.latitude; 60 | currentLong = res.longitude; 61 | }); 62 | } 63 | 64 | /// Function for getting [Donors]. 65 | void fetchDonors() async { 66 | donors = context.read().getDonors(); 67 | 68 | // Get the email of the user. 69 | var email = context.read().user.email; 70 | 71 | /// Iterating over the list and calling [initMarker] 72 | /// for each document. 73 | donors.forEach( 74 | (element) { 75 | element.docs.forEach( 76 | (element) { 77 | // If user email and fetched don't match then 78 | // initialise a marker. 79 | if (email != element.data()['email']) { 80 | initMarker(element.data(), element.id); 81 | } 82 | }, 83 | ); 84 | }, 85 | ); 86 | } 87 | 88 | /// After getting [Donor] document populate the 89 | /// field to show in [Dialog Box] 90 | Future populateDonor(String id) async { 91 | DocumentSnapshot doc = 92 | await context.read().getDonorById(id); 93 | name = doc["name"]; 94 | address = doc["address"]; 95 | bloodGroup = doc["bloodGroup"]; 96 | latitude = doc["latitude"]; 97 | longitude = doc["longitude"]; 98 | phoneNumber = doc["phoneNumber"]; 99 | } 100 | 101 | /// Function for initialising location [Markers]. 102 | void initMarker(Map info, String id) { 103 | // Set the markerId. 104 | var markerIdVal = id; 105 | final MarkerId markerId = MarkerId(markerIdVal); 106 | 107 | // Create a new Marker. 108 | final Marker marker = Marker( 109 | markerId: markerId, 110 | position: LatLng( 111 | info['latitude'], 112 | info['longitude'], 113 | ), 114 | onTap: () async { 115 | await populateDonor(id); 116 | setState(() { 117 | isTapped = true; 118 | }); 119 | }, 120 | zIndex: 2, 121 | ); 122 | 123 | // Set the state and show markes. 124 | setState(() { 125 | markers[markerId] = marker; 126 | }); 127 | } 128 | 129 | @override 130 | Widget build(BuildContext context) { 131 | return Scaffold( 132 | backgroundColor: backgroundColor, 133 | // If currentLat or currentLong is null then wait. 134 | // else show Google Maps. 135 | body: (currentLat == null || currentLong == null) 136 | ? Center( 137 | child: Loader(), 138 | ) 139 | : Stack( 140 | children: [ 141 | StreamBuilder( 142 | stream: context.read().getDonors(), 143 | builder: (context, snapshot) { 144 | if (snapshot.hasError) { 145 | return Center( 146 | child: Text( 147 | "Error: ${snapshot.error}", 148 | style: mediumTextStyle(), 149 | ), 150 | ); 151 | } 152 | 153 | if (!snapshot.hasData) { 154 | return Center( 155 | child: Loader(), 156 | ); 157 | } 158 | 159 | return Stack( 160 | children: [ 161 | GoogleMap( 162 | mapType: MapType.normal, 163 | initialCameraPosition: CameraPosition( 164 | target: LatLng(currentLat, currentLong), 165 | zoom: 15, 166 | ), 167 | markers: Set.of(markers.values), 168 | onMapCreated: (GoogleMapController controller) { 169 | _controller.complete(controller); 170 | }, 171 | ), 172 | isTapped 173 | ? Dialog( 174 | shape: RoundedRectangleBorder( 175 | borderRadius: BorderRadius.circular(20), 176 | ), 177 | elevation: 2, 178 | backgroundColor: backgroundColor, 179 | child: Stack( 180 | children: [ 181 | Container( 182 | width: MediaQuery.of(context).size.width * 183 | 0.8, 184 | height: 185 | MediaQuery.of(context).size.height * 186 | 0.23, 187 | child: Positioned.fill( 188 | child: Opacity( 189 | opacity: 0.5, 190 | child: SvgPicture.asset( 191 | 'assets/stethoscope.svg', 192 | fit: BoxFit.fitWidth, 193 | alignment: Alignment.center, 194 | ), 195 | ), 196 | ), 197 | ), 198 | Container( 199 | width: MediaQuery.of(context).size.width * 200 | 0.8, 201 | height: 202 | MediaQuery.of(context).size.height * 203 | 0.2, 204 | padding: EdgeInsets.all(10.0), 205 | margin: EdgeInsets.only( 206 | top: 10.0, 207 | // bottom: 10.0, 208 | ), 209 | child: Column( 210 | mainAxisAlignment: 211 | MainAxisAlignment.spaceBetween, 212 | children: [ 213 | Row( 214 | mainAxisAlignment: 215 | MainAxisAlignment.spaceBetween, 216 | children: [ 217 | Container( 218 | decoration: BoxDecoration( 219 | borderRadius: 220 | BorderRadius.all( 221 | Radius.circular(30), 222 | ), 223 | border: Border.all( 224 | color: Colors.red, 225 | ), 226 | ), 227 | child: Padding( 228 | padding: 229 | const EdgeInsets.only( 230 | left: 8.0, 231 | right: 8.0, 232 | top: 4.0, 233 | bottom: 4.0, 234 | ), 235 | child: Text( 236 | name, 237 | style: mediumTextStyle(), 238 | ), 239 | ), 240 | ), 241 | Container( 242 | width: 50.0, 243 | height: 50.0, 244 | decoration: BoxDecoration( 245 | color: circleColor, 246 | borderRadius: 247 | BorderRadius.all( 248 | Radius.circular(50.0), 249 | ), 250 | ), 251 | child: Center( 252 | child: Text( 253 | bloodGroup.toUpperCase(), 254 | style: 255 | bloodGroupTextStyle(), 256 | ), 257 | ), 258 | ), 259 | ], 260 | ), 261 | Row( 262 | mainAxisAlignment: 263 | MainAxisAlignment.spaceBetween, 264 | children: [ 265 | GestureDetector( 266 | onTap: () async { 267 | final Uri _phoneNumberURI = 268 | Uri( 269 | scheme: 'tel', 270 | path: 271 | phoneNumber.toString(), 272 | ); 273 | await canLaunch( 274 | _phoneNumberURI 275 | .toString()) 276 | ? await launch( 277 | _phoneNumberURI 278 | .toString()) 279 | : Fluttertoast.showToast( 280 | msg: 281 | "Could not make a call", 282 | ); 283 | }, 284 | child: AlertButton( 285 | "Call", 286 | Icons.call, 287 | ), 288 | ), 289 | GestureDetector( 290 | onTap: () { 291 | setState(() { 292 | isTapped = false; 293 | }); 294 | }, 295 | child: AlertButton( 296 | "Exit", 297 | Icons.exit_to_app, 298 | ), 299 | ), 300 | ], 301 | ) 302 | ], 303 | ), 304 | ), 305 | ], 306 | ), 307 | ) 308 | : SizedBox(), 309 | ], 310 | ); 311 | }, 312 | ), 313 | Container( 314 | child: Padding( 315 | padding: const EdgeInsets.all(10.0), 316 | child: Align( 317 | alignment: Alignment.topCenter, 318 | child: Text( 319 | "Donors in your Area", 320 | style: mediumTextStyle(), 321 | ), 322 | ), 323 | ), 324 | ), 325 | ], 326 | ), 327 | ); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /lib/views/seekers_list.dart: -------------------------------------------------------------------------------- 1 | // Library imports. 2 | import 'dart:async'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:geolocator/geolocator.dart'; 6 | import 'package:flutter_svg/flutter_svg.dart'; 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 9 | import 'package:fluttertoast/fluttertoast.dart'; 10 | import 'package:url_launcher/url_launcher.dart'; 11 | 12 | // Local imports 13 | import 'package:knowyourdonor/provider/auth_provider.dart'; 14 | import 'package:knowyourdonor/components/loader.dart'; 15 | import 'package:knowyourdonor/components/alertButton.dart'; 16 | import 'package:knowyourdonor/constants/text_styles.dart'; 17 | import 'package:knowyourdonor/constants/colors.dart'; 18 | import 'package:knowyourdonor/repository/seekerRepository.dart'; 19 | import 'package:knowyourdonor/repository/location_repository.dart'; 20 | 21 | class SeekersList extends StatefulWidget { 22 | @override 23 | _SeekersListState createState() => _SeekersListState(); 24 | } 25 | 26 | class _SeekersListState extends State { 27 | Completer _controller = Completer(); 28 | Map markers = {}; 29 | 30 | // Seeker fields. 31 | String address; 32 | String bloodGroup; 33 | String name; 34 | double latitude; 35 | double longitude; 36 | int phoneNumber; 37 | int units; 38 | bool isPlatelets; 39 | 40 | final LocationRepository _seekerLocationRepository = LocationRepository(); 41 | 42 | Stream seekers; 43 | 44 | double currentLat, currentLong; 45 | 46 | bool isTapped = false; 47 | 48 | @override 49 | void initState() { 50 | getCurrentLocation(); 51 | fetchSeekers(); 52 | super.initState(); 53 | } 54 | 55 | /// Function for getting [Current Location]. 56 | Future getCurrentLocation() async { 57 | Position res = await _seekerLocationRepository.getCurrentLocation(); 58 | 59 | // Set the state and show map. 60 | setState(() { 61 | currentLat = res.latitude; 62 | currentLong = res.longitude; 63 | }); 64 | } 65 | 66 | /// Function for getting [Donors]. 67 | void fetchSeekers() async { 68 | seekers = context.read().getSeekers(); 69 | 70 | // Get the email of the user. 71 | var email = context.read().user.email; 72 | 73 | /// Iterating over the list and calling [initMarker] 74 | /// for each document. 75 | seekers.forEach( 76 | (element) { 77 | element.docs.forEach( 78 | (element) { 79 | if (email != element.data()['email']) { 80 | initMarker(element.data(), element.id); 81 | } 82 | }, 83 | ); 84 | }, 85 | ); 86 | } 87 | 88 | /// After getting [Seeker] document populate the 89 | /// field to show in [Dialog Box] 90 | Future populateSeeker(String id) async { 91 | DocumentSnapshot doc = 92 | await context.read().getSeekerById(id); 93 | name = doc["name"]; 94 | address = doc["address"]; 95 | bloodGroup = doc["bloodGroup"]; 96 | latitude = doc["latitude"]; 97 | longitude = doc["longitude"]; 98 | phoneNumber = doc["phoneNumber"]; 99 | units = doc["units"]; 100 | isPlatelets = doc["isPlatelet"]; 101 | } 102 | 103 | /// Function for initialising location [Markers]. 104 | void initMarker(Map info, String id) { 105 | // Set the markerId. 106 | var markerIdVal = id; 107 | final MarkerId markerId = MarkerId(markerIdVal); 108 | 109 | // Create a new Marker. 110 | final Marker marker = Marker( 111 | markerId: markerId, 112 | position: LatLng( 113 | info['latitude'], 114 | info['longitude'], 115 | ), 116 | onTap: () async { 117 | await populateSeeker(id); 118 | setState(() { 119 | isTapped = true; 120 | }); 121 | }, 122 | zIndex: 2, 123 | ); 124 | 125 | // Set the state and show markes. 126 | setState(() { 127 | markers[markerId] = marker; 128 | }); 129 | } 130 | 131 | @override 132 | Widget build(BuildContext context) { 133 | return Scaffold( 134 | backgroundColor: backgroundColor, 135 | // If currentLat or currentLong is null then wait. 136 | // else show Google Maps. 137 | body: (currentLat == null || currentLong == null) 138 | ? Center( 139 | child: Loader(), 140 | ) 141 | : Stack( 142 | children: [ 143 | StreamBuilder( 144 | stream: context.read().getSeekers(), 145 | builder: (context, snapshot) { 146 | if (snapshot.hasError) { 147 | return Center( 148 | child: Text( 149 | "Error: ${snapshot.error}", 150 | style: mediumTextStyle(), 151 | ), 152 | ); 153 | } 154 | 155 | if (!snapshot.hasData) { 156 | return Center( 157 | child: Loader(), 158 | ); 159 | } 160 | 161 | return Stack( 162 | children: [ 163 | GoogleMap( 164 | mapType: MapType.normal, 165 | initialCameraPosition: CameraPosition( 166 | target: LatLng(currentLat, currentLong), 167 | zoom: 15, 168 | ), 169 | markers: Set.of(markers.values), 170 | onMapCreated: (GoogleMapController controller) { 171 | _controller.complete(controller); 172 | }, 173 | ), 174 | isTapped 175 | ? Dialog( 176 | shape: RoundedRectangleBorder( 177 | borderRadius: BorderRadius.circular(20), 178 | ), 179 | elevation: 2, 180 | backgroundColor: backgroundColor, 181 | child: Stack( 182 | children: [ 183 | Container( 184 | width: MediaQuery.of(context).size.width * 185 | 0.8, 186 | height: 187 | MediaQuery.of(context).size.height * 188 | 0.23, 189 | child: Opacity( 190 | opacity: 0.5, 191 | child: SvgPicture.asset( 192 | 'assets/stethoscope.svg', 193 | fit: BoxFit.fitWidth, 194 | alignment: Alignment.center, 195 | ), 196 | ), 197 | ), 198 | Container( 199 | width: MediaQuery.of(context).size.width * 200 | 0.8, 201 | height: 202 | MediaQuery.of(context).size.height * 203 | 0.25, 204 | padding: EdgeInsets.all(10.0), 205 | margin: EdgeInsets.only( 206 | top: 10.0, 207 | // bottom: 10.0, 208 | ), 209 | child: Column( 210 | mainAxisAlignment: 211 | MainAxisAlignment.spaceEvenly, 212 | children: [ 213 | Row( 214 | mainAxisAlignment: 215 | MainAxisAlignment.spaceBetween, 216 | children: [ 217 | Container( 218 | decoration: BoxDecoration( 219 | borderRadius: 220 | BorderRadius.all( 221 | Radius.circular(30), 222 | ), 223 | border: Border.all( 224 | color: Colors.red, 225 | ), 226 | ), 227 | child: Padding( 228 | padding: 229 | const EdgeInsets.only( 230 | left: 8.0, 231 | right: 8.0, 232 | top: 4.0, 233 | bottom: 4.0, 234 | ), 235 | child: Text( 236 | name, 237 | style: mediumTextStyle(), 238 | ), 239 | ), 240 | ), 241 | Container( 242 | width: 50.0, 243 | height: 50.0, 244 | decoration: BoxDecoration( 245 | color: circleColor, 246 | borderRadius: 247 | BorderRadius.all( 248 | Radius.circular(50.0), 249 | ), 250 | ), 251 | child: Center( 252 | child: Text( 253 | bloodGroup.toUpperCase(), 254 | style: 255 | bloodGroupTextStyle(), 256 | ), 257 | ), 258 | ), 259 | ], 260 | ), 261 | Row( 262 | mainAxisAlignment: 263 | MainAxisAlignment.spaceBetween, 264 | children: [ 265 | Container( 266 | decoration: BoxDecoration( 267 | borderRadius: 268 | BorderRadius.all( 269 | Radius.circular(30), 270 | ), 271 | border: Border.all( 272 | color: Colors.red, 273 | ), 274 | ), 275 | child: Padding( 276 | padding: 277 | const EdgeInsets.only( 278 | left: 8.0, 279 | right: 8.0, 280 | top: 4.0, 281 | bottom: 4.0, 282 | ), 283 | child: Text( 284 | "$units units", 285 | style: mediumTextStyle(), 286 | ), 287 | ), 288 | ), 289 | isPlatelets 290 | ? Text( 291 | "* Platelets Required!", 292 | style: cardTextStyle(), 293 | ) 294 | : SizedBox( 295 | width: 0.0, 296 | ), 297 | ], 298 | ), 299 | Row( 300 | mainAxisAlignment: 301 | MainAxisAlignment.spaceBetween, 302 | children: [ 303 | GestureDetector( 304 | onTap: () async { 305 | final Uri _phoneNumberURI = 306 | Uri( 307 | scheme: 'tel', 308 | path: 309 | phoneNumber.toString(), 310 | ); 311 | await canLaunch( 312 | _phoneNumberURI 313 | .toString()) 314 | ? await launch( 315 | _phoneNumberURI 316 | .toString()) 317 | : Fluttertoast.showToast( 318 | msg: 319 | "Could not make a call", 320 | ); 321 | }, 322 | child: AlertButton( 323 | "Call", 324 | Icons.call, 325 | ), 326 | ), 327 | GestureDetector( 328 | onTap: () { 329 | setState(() { 330 | isTapped = false; 331 | }); 332 | }, 333 | child: AlertButton( 334 | "Exit", 335 | Icons.exit_to_app, 336 | ), 337 | ), 338 | ], 339 | ) 340 | ], 341 | ), 342 | ), 343 | ], 344 | ), 345 | ) 346 | : SizedBox(), 347 | ], 348 | ); 349 | }, 350 | ), 351 | Container( 352 | child: Padding( 353 | padding: const EdgeInsets.all(10.0), 354 | child: Align( 355 | alignment: Alignment.topCenter, 356 | child: Text( 357 | "Seekers in your Area", 358 | style: mediumTextStyle(), 359 | ), 360 | ), 361 | ), 362 | ), 363 | ], 364 | ), 365 | ); 366 | } 367 | } 368 | --------------------------------------------------------------------------------