├── 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
├── fonts
│ ├── sf.ttf
│ └── courgette.ttf
├── icons
│ ├── 045-blood drop empty.svg
│ ├── 044-blood drop.svg
│ ├── 009-map location.svg
│ ├── 013-blood drop.svg
│ ├── 041-blood drop.svg
│ ├── 008-blood drop.svg
│ ├── 027-blood drop.svg
│ ├── 023-blood drop.svg
│ ├── 001-blood drop.svg
│ ├── 030-syringe needle.svg
│ ├── 020-blood drop.svg
│ ├── 015-blood drop.svg
│ ├── 026-map pointer.svg
│ ├── 037-blood drop.svg
│ ├── 033-blood drop.svg
│ ├── 043-shirt.svg
│ ├── 003-medical kit.svg
│ ├── 018-blood tube.svg
│ ├── 002-screen.svg
│ ├── 025-donor.svg
│ ├── 039-ribbon.svg
│ ├── 012-mobile phone.svg
│ ├── 005-blood bag.svg
│ ├── 022-blood donation.svg
│ ├── 031-blood bank.svg
│ ├── 004-clipboard.svg
│ ├── 038-stethoscope.svg
│ ├── 024-nurse.svg
│ ├── 036-dentist chair.svg
│ ├── 029-rubber gloves.svg
│ ├── 021-red blood cells.svg
│ ├── 042-dropper.svg
│ ├── 028-blood donor card.svg
│ ├── 007-microscope.svg
│ ├── 019-hospital.svg
│ ├── 006-ambulance.svg
│ ├── 010-test tube.svg
│ ├── 016-syringe.svg
│ ├── 017-blood drop.svg
│ ├── 014-doctor.svg
│ ├── 040-blood bag.svg
│ ├── logo.svg
│ ├── 035-band aid.svg
│ ├── 032-blood bag.svg
│ ├── 034-virus.svg
│ └── 011-dna.svg
└── data
│ ├── blood_banks.json
│ └── medical_centers.json
├── screenshots
├── 06 - home.jpg
├── 12 - news.jpg
├── 01 - splash.jpg
├── 08 - drawer.jpg
├── 09 - profile.jpg
├── 02 - tutorial 1.jpg
├── 03 - Login empty.jpg
├── 10 - edit profile.jpg
├── 05 - register empty.jpg
├── 07 - request detail.jpg
├── 13 - who can donate.jpg
├── 15 - admin add news.jpg
├── 04 - login wrong password.jpg
├── 11 - submit medical centers.jpg
└── 14 - drawer admin activated.jpg
├── lib
├── common
│ ├── styles.dart
│ ├── app_config.dart
│ ├── hive_boxes.dart
│ ├── colors.dart
│ └── assets.dart
├── utils
│ ├── extensions.dart
│ ├── tools.dart
│ ├── validators.dart
│ └── blood_types.dart
├── data
│ ├── medical_center.dart
│ ├── blood_request.dart
│ ├── lists
│ │ ├── blood_banks.dart
│ │ └── medical_centers.dart
│ └── info_group.dart
├── widgets
│ ├── news_tile.dart
│ ├── action_button.dart
│ ├── all_blood_requests.dart
│ ├── submitted_blood_requests.dart
│ └── blood_request_tile.dart
├── screens
│ ├── news_screen.dart
│ ├── who_can_donate_screen.dart
│ ├── home_screen.dart
│ ├── splash_screen.dart
│ ├── add_news_item.dart
│ └── tutorial_screen.dart
└── main.dart
├── analysis_options.yaml
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── ic_launcher-playstore.png
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── values
│ │ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ └── drawable
│ │ │ │ │ ├── launch_background.xml
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── blood_donation
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── .metadata
├── .gitignore
├── LICENSE
├── README.md
└── pubspec.yaml
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/assets/fonts/sf.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/assets/fonts/sf.ttf
--------------------------------------------------------------------------------
/assets/fonts/courgette.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/assets/fonts/courgette.ttf
--------------------------------------------------------------------------------
/screenshots/06 - home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/06 - home.jpg
--------------------------------------------------------------------------------
/screenshots/12 - news.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/12 - news.jpg
--------------------------------------------------------------------------------
/screenshots/01 - splash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/01 - splash.jpg
--------------------------------------------------------------------------------
/screenshots/08 - drawer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/08 - drawer.jpg
--------------------------------------------------------------------------------
/screenshots/09 - profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/09 - profile.jpg
--------------------------------------------------------------------------------
/lib/common/styles.dart:
--------------------------------------------------------------------------------
1 | class Fonts {
2 | static const logo = 'Courgette';
3 | static const text = 'SF';
4 | }
5 |
--------------------------------------------------------------------------------
/screenshots/02 - tutorial 1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/02 - tutorial 1.jpg
--------------------------------------------------------------------------------
/screenshots/03 - Login empty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/03 - Login empty.jpg
--------------------------------------------------------------------------------
/screenshots/10 - edit profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/10 - edit profile.jpg
--------------------------------------------------------------------------------
/screenshots/05 - register empty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/05 - register empty.jpg
--------------------------------------------------------------------------------
/screenshots/07 - request detail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/07 - request detail.jpg
--------------------------------------------------------------------------------
/screenshots/13 - who can donate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/13 - who can donate.jpg
--------------------------------------------------------------------------------
/screenshots/15 - admin add news.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/15 - admin add news.jpg
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:lint/analysis_options.yaml
2 |
3 | linter:
4 | rules:
5 | prefer_relative_imports: true
--------------------------------------------------------------------------------
/screenshots/04 - login wrong password.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/04 - login wrong password.jpg
--------------------------------------------------------------------------------
/screenshots/11 - submit medical centers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/11 - submit medical centers.jpg
--------------------------------------------------------------------------------
/screenshots/14 - drawer admin activated.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/screenshots/14 - drawer admin activated.jpg
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/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/bassel/BloodDonation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/blood_donation/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.blood_donation
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/common/app_config.dart:
--------------------------------------------------------------------------------
1 | class AppConfig {
2 | static const email = 'blood.donations.lb@gmail.com';
3 | static const bloodDonationInfoLink =
4 | 'https://www.moph.gov.lb/en/Pages/4/3262/blood-transfusion-';
5 | }
6 |
--------------------------------------------------------------------------------
/lib/common/hive_boxes.dart:
--------------------------------------------------------------------------------
1 | class ConfigBox {
2 | static const key = 'configBox';
3 | static const isFirstLaunch = 'isFirstLaunch';
4 | static const bloodType = 'bloodType';
5 | static const isAdmin = 'isAdmin';
6 | }
7 |
--------------------------------------------------------------------------------
/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.7-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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: bbfbf1770cca2da7c82e887e4e4af910034800b6
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/assets/icons/045-blood drop empty.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/044-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/utils/extensions.dart:
--------------------------------------------------------------------------------
1 | extension DurationFormatter on Duration {
2 | /// Returns the minutes:seconds representation from a duration
3 | String simpleFormat() {
4 | String twoDigits(int n) {
5 | if (n >= 10) return "$n";
6 | return "0$n";
7 | }
8 |
9 | final twoDigitMinutes = twoDigits(inMinutes.remainder(60) as int);
10 | final twoDigitSeconds = twoDigits(inSeconds.remainder(60) as int);
11 | return "$twoDigitMinutes:$twoDigitSeconds";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/assets/icons/009-map location.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/utils/tools.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' show sqrt;
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class Tools {
6 | static bool isTablet(Size size) {
7 | final diagonal = sqrt(
8 | (size.width * size.width) + (size.height * size.height),
9 | );
10 | return diagonal > 1100.0;
11 | }
12 |
13 | static String formatDate(DateTime date) {
14 | if (date == null) return null;
15 | return '${date.day}/${date.month}/${date.year}';
16 | }
17 |
18 | static bool isNullOrEmpty(String s) => s == null || s.isEmpty;
19 | }
20 |
--------------------------------------------------------------------------------
/assets/icons/013-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/041-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/008-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/027-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/common/colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MainColors {
4 | static const primary = Color(0xffD7212D);
5 | static const primaryDark = Color(0xffB9182A);
6 | static const accent = Color(0xffeb9096);
7 | static const bg = Color(0xFFFAFAFA);
8 | static const swatch = MaterialColor(0xffD7212D, {
9 | 50: Color.fromRGBO(215,33,45, .1),
10 | 100: Color.fromRGBO(215,33,45, .2),
11 | 200: Color.fromRGBO(215,33,45, .3),
12 | 300: Color.fromRGBO(215,33,45, .4),
13 | 400: Color.fromRGBO(215,33,45, .5),
14 | 500: Color.fromRGBO(215,33,45, .6),
15 | 600: Color.fromRGBO(215,33,45, .7),
16 | 700: Color.fromRGBO(215,33,45, .8),
17 | 800: Color.fromRGBO(215,33,45, .9),
18 | 900: Color.fromRGBO(215,33,45, 1),
19 | });
20 | }
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.0'
10 | classpath 'com.google.gms:google-services:4.3.8'
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 |
--------------------------------------------------------------------------------
/assets/icons/023-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/001-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/030-syringe needle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/data/medical_center.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | @immutable
4 | class MedicalCenter {
5 | final String name;
6 | final List phoneNumbers;
7 | final String location;
8 | final String latitude;
9 | final String longitude;
10 |
11 | const MedicalCenter({
12 | this.name,
13 | this.phoneNumbers,
14 | this.location,
15 | this.latitude,
16 | this.longitude,
17 | });
18 |
19 | MedicalCenter.fromJson(Map json)
20 | : name = json['name'] as String,
21 | phoneNumbers = List.from(json['phoneNumbers'] as List),
22 | location = json['location'] as String,
23 | latitude = json['latitude'] as String,
24 | longitude = json['longitude'] as String;
25 |
26 | Map toJson() => {
27 | 'name': name,
28 | 'phoneNumbers': phoneNumbers,
29 | 'location': location,
30 | 'latitude': latitude,
31 | 'longitude': longitude,
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/assets/icons/020-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.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 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Exceptions to above rules.
44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
45 |
46 | ## google-services
47 | android/app/google-services.json
48 | ios/Runner/GoogleService-Info.plist
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/assets/icons/015-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Bassel Cheaib
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 |
--------------------------------------------------------------------------------
/assets/icons/026-map pointer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/037-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/033-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/043-shirt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/widgets/news_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../common/colors.dart';
4 |
5 | class NewsTile extends StatelessWidget {
6 | final String title;
7 | final String body;
8 | final String date;
9 |
10 | const NewsTile({
11 | Key key,
12 | @required this.title,
13 | this.body,
14 | this.date,
15 | }) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | final textTheme = Theme.of(context).textTheme;
20 | return Column(
21 | crossAxisAlignment: CrossAxisAlignment.start,
22 | children: [
23 | ListTile(
24 | leading: const Icon(
25 | Icons.notifications_active,
26 | color: MainColors.primary,
27 | ),
28 | title: Text(
29 | title,
30 | style: textTheme.headline6.copyWith(color: MainColors.primary),
31 | ),
32 | trailing: Text(
33 | date,
34 | style: textTheme.caption.copyWith(fontSize: 14),
35 | ),
36 | ),
37 | Padding(
38 | padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
39 | child: Text(body, style: textTheme.caption.copyWith(fontSize: 16)),
40 | ),
41 | const Divider(color: MainColors.primary, indent: 16, endIndent: 16),
42 | ],
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/assets/icons/003-medical kit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/018-blood tube.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/002-screen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/025-donor.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/widgets/action_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../common/colors.dart';
4 |
5 | class ActionButton extends StatelessWidget {
6 | final VoidCallback callback;
7 | final String text;
8 | final Color backgroundColor;
9 | final bool isLoading;
10 | final double radius;
11 |
12 | const ActionButton({
13 | Key key,
14 | @required this.callback,
15 | @required this.text,
16 | this.backgroundColor = MainColors.primary,
17 | this.isLoading = false,
18 | this.radius = 5.0,
19 | }) : super(key: key);
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return SizedBox(
24 | width: double.infinity,
25 | height: 50,
26 | child: isLoading
27 | ? const Center(
28 | child: CircularProgressIndicator(
29 | valueColor: AlwaysStoppedAnimation(MainColors.primary),
30 | ),
31 | )
32 | : ElevatedButton(
33 | style: ButtonStyle(
34 | backgroundColor: MaterialStateProperty.all(backgroundColor),
35 | foregroundColor: MaterialStateProperty.all(Colors.white),
36 | shape: MaterialStateProperty.all(RoundedRectangleBorder(
37 | borderRadius: BorderRadius.circular(radius),
38 | )),
39 | ),
40 | onPressed: callback,
41 | child: Text(text, style: const TextStyle(fontSize: 18)),
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/data/blood_request.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/foundation.dart';
3 |
4 | import '../utils/blood_types.dart';
5 | import 'medical_center.dart';
6 |
7 | class BloodRequest {
8 | final String id;
9 | final String uid, submittedBy, patientName, contactNumber, note;
10 | final BloodType bloodType;
11 | final DateTime submittedAt, requestDate;
12 | final MedicalCenter medicalCenter;
13 | bool isFulfilled;
14 |
15 | BloodRequest({
16 | @required this.id,
17 | @required this.uid,
18 | @required this.submittedBy,
19 | @required this.patientName,
20 | @required this.contactNumber,
21 | @required this.bloodType,
22 | @required this.medicalCenter,
23 | @required this.submittedAt,
24 | @required this.requestDate,
25 | this.note,
26 | this.isFulfilled = false,
27 | });
28 |
29 | factory BloodRequest.fromJson(Map json, {String id}) =>
30 | BloodRequest(
31 | id: id,
32 | uid: json['uid'] as String,
33 | submittedBy: json['submittedBy'] as String,
34 | patientName: json['patientName'] as String,
35 | contactNumber: json['contactNumber'] as String,
36 | bloodType: BloodTypeUtils.fromName(json['bloodType'] as String),
37 | medicalCenter: MedicalCenter.fromJson(
38 | json['medicalCenter'] as Map,
39 | ),
40 | submittedAt: (json['submittedAt'] as Timestamp).toDate(),
41 | requestDate: (json['requestDate'] as Timestamp).toDate(),
42 | note: json['note'] as String,
43 | isFulfilled: json['isFulfilled'] as bool,
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/039-ribbon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/012-mobile phone.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/screens/news_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import '../common/colors.dart';
5 | import '../utils/tools.dart';
6 | import '../widgets/news_tile.dart';
7 |
8 | class NewsScreen extends StatelessWidget {
9 | static const route = 'news';
10 | const NewsScreen({Key key}) : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | final news = FirebaseFirestore.instance.collection('news');
15 |
16 | return Scaffold(
17 | appBar: AppBar(title: const Text('News and Tips')),
18 | body: SafeArea(
19 | child: StreamBuilder>>(
20 | stream: news.orderBy('date', descending: true).limit(20).snapshots(),
21 | builder: (context, snapshot) {
22 | if (snapshot.hasError) {
23 | return const Center(child: Text('Something went wrong'));
24 | }
25 |
26 | if (snapshot.connectionState == ConnectionState.waiting) {
27 | return const Center(
28 | child: CircularProgressIndicator(
29 | valueColor: AlwaysStoppedAnimation(MainColors.primary),
30 | ),
31 | );
32 | }
33 |
34 | return ListView(
35 | children: snapshot.data.docs.map((doc) {
36 | return NewsTile(
37 | title: doc.data()['title'] as String,
38 | body: doc.data()['body'] as String,
39 | date: Tools.formatDate(
40 | (doc.data()['date'] as Timestamp).toDate(),
41 | ),
42 | );
43 | }).toList(),
44 | );
45 | },
46 | ),
47 | ),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/assets/icons/005-blood bag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/022-blood donation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/utils/validators.dart:
--------------------------------------------------------------------------------
1 | /// To chain validators, call them one after the other with the ?? operator.
2 | /// Reasoning: If the first validator returns null then it has no errors, so
3 | /// we check the second one, etc.
4 | /// Once a validator returns a non-null value, this means it caught an error
5 | /// and no need to check any further
6 | class Validators {
7 | static String required(String val, String name) {
8 | if (val == null || val.isEmpty) {
9 | return '* $name is required';
10 | }
11 | return null;
12 | }
13 |
14 | static String email(String val) {
15 | if (val == null || !val.contains('@')) {
16 | return '* Please enter a valid email';
17 | }
18 | return null;
19 | }
20 |
21 | static String phone(String val) {
22 | final regExp = RegExp(r'(^[0-9]{7,8}$)');
23 | if (!regExp.hasMatch(val)) {
24 | return '* Please enter a valid phone number';
25 | }
26 | return null;
27 | }
28 |
29 | /// Allows years only between 1900 and 2099
30 | /// Allows only birth years of people between 18 and 100 years old
31 | static String birthYear(String val) {
32 | final regExp = RegExp(r'^(19|20)\d{2}$');
33 | if (!regExp.hasMatch(val)) {
34 | return '* Please enter a valid year';
35 | }
36 | final year = int.tryParse(val) ?? 0;
37 | final age = DateTime.now().year - year;
38 | if (age < 18) {
39 | return '* You must be at least 18 years old';
40 | } else if (age > 100) {
41 | return '* You must be less than 100 years old';
42 | }
43 | return null;
44 | }
45 |
46 | /// Allows only alphabetical non numeric characters,
47 | /// and only the dash and apostrophe in special chars
48 | static String name(String val) {
49 | final regExp =
50 | RegExp(r"^[a-zA-Z]+(([' -][a-zA-Z ])?[a-zA-Z]*)*$");
51 | if (!regExp.hasMatch(val)) {
52 | return '* Please enter a valid name';
53 | }
54 | return null;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 | blood_donation
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 | NSPhotoLibraryUsageDescription
30 | We use this permission to allow you to change your profile image
31 | NSCameraUsageDescription
32 | We use this permission to allow you to change your profile image
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/assets/icons/031-blood bank.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blood Donation
2 |
3 | Demo of a blood donation app that connects donors and recipients.
4 |
5 | ## Setup
6 | This app uses firebase as a backend. In order to run it, you need to add your own google services files
7 | in the following locations:
8 | - `android/app/google-services.json`
9 | - `ios/Runner/GoogleService-Info.plist`
10 |
11 | ## Screenshots
12 |
13 | 
14 | 
15 | 
16 | 
17 | 
18 | 
19 | 
20 | 
21 | 
22 | 
23 | 
24 | 
25 | 
26 |
27 | ## Bonus section
28 | When a user is set as an admin from the backend, an extra section appears is the drawer allowing them
29 | to add news directly from within the app:
30 |
31 | 
32 | 
33 |
34 |
35 | ## Getting Started
36 |
37 | This project is a starting point for a Flutter application.
38 |
39 | A few resources to get you started if this is your first Flutter project:
40 |
41 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
42 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
43 |
44 | For help getting started with Flutter, view our
45 | [online documentation](https://flutter.dev/docs), which offers tutorials,
46 | samples, guidance on mobile development, and a full API reference.
47 |
--------------------------------------------------------------------------------
/assets/icons/004-clipboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:hive/hive.dart';
3 | import 'package:hive_flutter/hive_flutter.dart';
4 |
5 | import 'common/colors.dart';
6 | import 'common/hive_boxes.dart';
7 | import 'common/styles.dart';
8 | import 'screens/add_blood_request_screen.dart';
9 | import 'screens/add_news_item.dart';
10 | import 'screens/edit_profile_screen.dart';
11 | import 'screens/home_screen.dart';
12 | import 'screens/login_screen.dart';
13 | import 'screens/news_screen.dart';
14 | import 'screens/profile_screen.dart';
15 | import 'screens/registration_screen.dart';
16 | import 'screens/splash_screen.dart';
17 | import 'screens/tutorial_screen.dart';
18 | import 'screens/who_can_donate_screen.dart';
19 |
20 | Future main() async {
21 | await Hive.initFlutter();
22 | await Hive.openBox(ConfigBox.key);
23 | runApp(MyApp());
24 | }
25 |
26 | class MyApp extends StatelessWidget {
27 | @override
28 | Widget build(BuildContext context) {
29 | return MaterialApp(
30 | debugShowCheckedModeBanner: false,
31 | title: 'Blood Donation',
32 | theme: ThemeData(
33 | primarySwatch: MainColors.swatch,
34 | visualDensity: VisualDensity.adaptivePlatformDensity,
35 | fontFamily: Fonts.text,
36 | ),
37 | initialRoute: SplashScreen.route,
38 | routes: {
39 | HomeScreen.route: (_) => const HomeScreen(),
40 | TutorialScreen.route: (_) => const TutorialScreen(),
41 | LoginScreen.route: (_) => const LoginScreen(),
42 | RegistrationScreen.route: (_) => const RegistrationScreen(),
43 | SplashScreen.route: (_) => const SplashScreen(),
44 | ProfileScreen.route: (_) => const ProfileScreen(),
45 | WhoCanDonateScreen.route: (_) => const WhoCanDonateScreen(),
46 | AddBloodRequestScreen.route: (_) => const AddBloodRequestScreen(),
47 | NewsScreen.route: (_) => const NewsScreen(),
48 | AddNewsItem.route: (_) => const AddNewsItem(),
49 | EditProfileScreen.route: (_) => const EditProfileScreen(),
50 | },
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/assets/icons/038-stethoscope.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/024-nurse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | android {
30 | compileSdkVersion 30
31 |
32 | sourceSets {
33 | main.java.srcDirs += 'src/main/kotlin'
34 | }
35 |
36 | lintOptions {
37 | disable 'InvalidPackage'
38 | }
39 |
40 | defaultConfig {
41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
42 | applicationId "com.example.blood_donation"
43 | minSdkVersion 21
44 | targetSdkVersion 30
45 | multiDexEnabled true
46 | versionCode flutterVersionCode.toInteger()
47 | versionName flutterVersionName
48 | }
49 |
50 | buildTypes {
51 | release {
52 | // TODO: Add your own signing config for the release build.
53 | // Signing with the debug keys for now, so `flutter run --release` works.
54 | signingConfig signingConfigs.debug
55 | }
56 | }
57 | }
58 |
59 | flutter {
60 | source '../..'
61 | }
62 |
63 | dependencies {
64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
65 | implementation 'com.android.support:multidex:1.0.3'
66 | }
67 |
--------------------------------------------------------------------------------
/lib/screens/who_can_donate_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 | import 'package:fontisto_flutter/fontisto_flutter.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 |
6 | import '../common/app_config.dart';
7 | import '../common/colors.dart';
8 | import '../data/info_group.dart';
9 | import '../widgets/action_button.dart';
10 |
11 | class WhoCanDonateScreen extends StatelessWidget {
12 | static const route = 'who-can-donate';
13 | const WhoCanDonateScreen({Key key}) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | final titleStyle = Theme.of(context)
18 | .textTheme
19 | .headline6
20 | .copyWith(color: MainColors.primary);
21 | return Scaffold(
22 | appBar: AppBar(title: const Text('Who Can Donate Blood ?')),
23 | body: SafeArea(
24 | child: SingleChildScrollView(
25 | child: Column(
26 | children: [
27 | ...InfoGroup.whoCanDonate
28 | .map(
29 | (g) => ExpansionTile(
30 | title: Text(g.title, style: titleStyle),
31 | initiallyExpanded: g.id == 0,
32 | children: g.info
33 | .map(
34 | (c) => ListTile(
35 | leading: const Icon(Istos.bookmark),
36 | title: Text(c),
37 | ),
38 | )
39 | .toList(),
40 | ),
41 | )
42 | .toList(),
43 | Padding(
44 | padding: const EdgeInsets.all(16),
45 | child: ActionButton(
46 | callback: () async {
47 | if (await canLaunch(AppConfig.bloodDonationInfoLink)) {
48 | launch(AppConfig.bloodDonationInfoLink);
49 | } else {
50 | Fluttertoast.showToast(msg: 'Could not launch the link');
51 | }
52 | },
53 | text: 'Learn More',
54 | ),
55 | ),
56 | ],
57 | ),
58 | ),
59 | ),
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/assets/icons/036-dentist chair.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/data/blood_banks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Spears Blood Bank",
4 | "phone_numbers": ["01372802", "01372803", "01372804", "01372805"],
5 | "location": "Beirut",
6 | "latitude": "33.888630",
7 | "longitude": "35.495480"
8 | },
9 | {
10 | "name": "Gemayze Blood Bank",
11 | "phone_numbers": ["01444102"],
12 | "location": "Beirut",
13 | "latitude": "33.888630",
14 | "longitude": "35.495480"
15 | },
16 | {
17 | "name": "Jounieh Blood Bank",
18 | "phone_numbers": ["09931750"],
19 | "location": "Beirut",
20 | "latitude": "33.888630",
21 | "longitude": "35.495480"
22 | },
23 | {
24 | "name": "Antelias Blood Bank",
25 | "phone_numbers": ["04524164"],
26 | "location": "Beirut",
27 | "latitude": "33.888630",
28 | "longitude": "35.495480"
29 | },
30 | {
31 | "name": "Jbeil Blood Bank",
32 | "phone_numbers": ["09945220"],
33 | "location": "Beirut",
34 | "latitude": "33.888630",
35 | "longitude": "35.495480"
36 | },
37 | {
38 | "name": "Beit El Dine Blood Bank",
39 | "phone_numbers": ["03468728"],
40 | "location": "Beirut",
41 | "latitude": "33.888630",
42 | "longitude": "35.495480"
43 | },
44 | {
45 | "name": "Tripoli Blood Bank",
46 | "phone_numbers": ["06601429"],
47 | "location": "Beirut",
48 | "latitude": "33.888630",
49 | "longitude": "35.495480"
50 | },
51 | {
52 | "name": "Halba Blood Bank",
53 | "phone_numbers": ["06695370"],
54 | "location": "Beirut",
55 | "latitude": "33.888630",
56 | "longitude": "35.495480"
57 | },
58 | {
59 | "name": "Zahle Blood Bank",
60 | "phone_numbers": ["08804930"],
61 | "location": "Beirut",
62 | "latitude": "33.888630",
63 | "longitude": "35.495480"
64 | },
65 | {
66 | "name": "Saida Blood Bank",
67 | "phone_numbers": ["07752141"],
68 | "location": "Beirut",
69 | "latitude": "33.888630",
70 | "longitude": "35.495480"
71 | },
72 | {
73 | "name": "Tyr Blood Bank",
74 | "phone_numbers": ["07740070"],
75 | "location": "Beirut",
76 | "latitude": "33.888630",
77 | "longitude": "35.495480"
78 | },
79 | {
80 | "name": "Nabatiye Blood Bank",
81 | "phone_numbers": ["07768687"],
82 | "location": "Beirut",
83 | "latitude": "33.888630",
84 | "longitude": "35.495480"
85 | }
86 | ]
87 |
--------------------------------------------------------------------------------
/assets/icons/029-rubber gloves.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/data/lists/blood_banks.dart:
--------------------------------------------------------------------------------
1 | import '../medical_center.dart';
2 |
3 | const bloodBanks = [
4 | MedicalCenter(
5 | name: "Spears Blood Bank",
6 | phoneNumbers: ["01372802", "01372803", "01372804", "01372805"],
7 | location: "Beirut",
8 | longitude: "35.495480",
9 | latitude: "33.888630",
10 | ),
11 | MedicalCenter(
12 | name: "Gemayze Blood Bank",
13 | phoneNumbers: ["01444102"],
14 | location: "Beirut",
15 | longitude: "35.495480",
16 | latitude: "33.888630",
17 | ),
18 | MedicalCenter(
19 | name: "Jounieh Blood Bank",
20 | phoneNumbers: ["09931750"],
21 | location: "Beirut",
22 | longitude: "35.495480",
23 | latitude: "33.888630",
24 | ),
25 | MedicalCenter(
26 | name: "Antelias Blood Bank",
27 | phoneNumbers: ["04524164"],
28 | location: "Beirut",
29 | longitude: "35.495480",
30 | latitude: "33.888630",
31 | ),
32 | MedicalCenter(
33 | name: "Jbeil Blood Bank",
34 | phoneNumbers: ["09945220"],
35 | location: "Beirut",
36 | longitude: "35.495480",
37 | latitude: "33.888630",
38 | ),
39 | MedicalCenter(
40 | name: "Beit El Dine Blood Bank",
41 | phoneNumbers: ["03468728"],
42 | location: "Beirut",
43 | longitude: "35.495480",
44 | latitude: "33.888630",
45 | ),
46 | MedicalCenter(
47 | name: "Tripoli Blood Bank",
48 | phoneNumbers: ["06601429"],
49 | location: "Beirut",
50 | longitude: "35.495480",
51 | latitude: "33.888630",
52 | ),
53 | MedicalCenter(
54 | name: "Halba Blood Bank",
55 | phoneNumbers: ["06695370"],
56 | location: "Beirut",
57 | longitude: "35.495480",
58 | latitude: "33.888630",
59 | ),
60 | MedicalCenter(
61 | name: "Zahle Blood Bank",
62 | phoneNumbers: ["08804930"],
63 | location: "Beirut",
64 | longitude: "35.495480",
65 | latitude: "33.888630",
66 | ),
67 | MedicalCenter(
68 | name: "Saida Blood Bank",
69 | phoneNumbers: ["07752141"],
70 | location: "Beirut",
71 | longitude: "35.495480",
72 | latitude: "33.888630",
73 | ),
74 | MedicalCenter(
75 | name: "Tyr Blood Bank",
76 | phoneNumbers: ["07740070"],
77 | location: "Beirut",
78 | longitude: "35.495480",
79 | latitude: "33.888630",
80 | ),
81 | MedicalCenter(
82 | name: "Nabatiye Blood Bank",
83 | phoneNumbers: ["07768687"],
84 | location: "Beirut",
85 | longitude: "35.495480",
86 | latitude: "33.888630",
87 | )
88 | ];
89 |
--------------------------------------------------------------------------------
/assets/icons/021-red blood cells.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/data/info_group.dart:
--------------------------------------------------------------------------------
1 | class InfoGroup {
2 | final int id;
3 | final String title;
4 | final List info;
5 |
6 | const InfoGroup({this.id, this.title, this.info});
7 |
8 | static const whoCanDonate = [
9 | InfoGroup(id: 0, title: 'Blood Donors:', info: _conditions),
10 | InfoGroup(
11 | id: 1,
12 | title: 'You should not donate blood if:',
13 | info: _doNotDonateIf,
14 | ),
15 | InfoGroup(
16 | id: 2,
17 | title: 'Wait 6 months before donation if:',
18 | info: _wait6MonthsIf,
19 | ),
20 | InfoGroup(
21 | id: 3,
22 | title: 'Wait 12 months before donation:',
23 | info: _wait12MonthsIf,
24 | ),
25 | ];
26 | }
27 |
28 | const _conditions = [
29 | 'Must be in good general health',
30 | 'Must be at least 18 years old and no more than 65. After the age of 60, donors require the approval of a transfusion medicine physician',
31 | 'Must weight at least 50 kg',
32 | 'Must not be at risk of transmitting blood-borne diseases',
33 | '''
34 | Must have a hemoglobin or hematocrit level of:
35 | o 13.5-18 g/dl (0.40%) for a man
36 | o 12.5-16 g/dl (0.38%) for a woman
37 | ''',
38 | 'Must have a systolic blood pressure of 100-140 mmHg and a diastolic blood pressure of 60-90 mmHg',
39 | 'Must have a pulse rate of 60-100 bpm (beats per minute)',
40 | 'Must have a temperature below 37.6°C',
41 | 'Must have a platelet count above 150x109/L',
42 | ];
43 |
44 | const _doNotDonateIf = [
45 | 'You have ever taken drugs',
46 | 'Your partner takes drugs',
47 | 'You are HIV positive',
48 | 'You are a male who had sexual contacts with another male',
49 | 'Your partner is HIV positive',
50 | 'You have more than one sexual partner',
51 | 'You think your partner has risky sex',
52 | ];
53 |
54 | const _wait6MonthsIf = [
55 | 'You have casual partners',
56 | 'You have changed sexual partners',
57 | ];
58 |
59 | const _wait12MonthsIf = [
60 | 'After a tattoo or ear/body piercing',
61 | 'After a scarification (except if therapeutic)',
62 | 'If you have undergone an acupuncture treatment and did not have needles for strictly personal use or single-use needles',
63 | 'If you have been cut with potentially contaminated objects (through sharing razor blades, for example)',
64 | 'If you have had prolonged contact with a damaged skin contaminated with secretions or blood',
65 | 'If you have been injured with a dirty needle',
66 | 'In case of a human bite',
67 | 'After surgery or endoscopic evaluation',
68 | ];
69 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/042-dropper.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/028-blood donor card.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/007-microscope.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/019-hospital.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/006-ambulance.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/common/assets.dart:
--------------------------------------------------------------------------------
1 | const iconsPath = 'assets/icons';
2 |
3 | class IconAssets {
4 | static const logo = '$iconsPath/logo.svg';
5 | static const bloodDrop = '$iconsPath/001-blood drop.svg';
6 | static const bloodAPos = '$iconsPath/008-blood drop.svg';
7 | static const bloodANeg = '$iconsPath/013-blood drop.svg';
8 | static const bloodBPos = '$iconsPath/015-blood drop.svg';
9 | static const bloodBNeg = '$iconsPath/020-blood drop.svg';
10 | static const bloodOPos = '$iconsPath/023-blood drop.svg';
11 | static const bloodONeg = '$iconsPath/027-blood drop.svg';
12 | static const bloodABPos = '$iconsPath/033-blood drop.svg';
13 | static const bloodABNeg = '$iconsPath/037-blood drop.svg';
14 | static const screen = '$iconsPath/002-screen.svg';
15 | static const medicalKit = '$iconsPath/003-medical kit.svg';
16 | static const clipboard = '$iconsPath/004-clipboard.svg';
17 | static const bloodBag = '$iconsPath/005-blood bag.svg';
18 | static const ambulance = '$iconsPath/006-ambulance.svg';
19 | static const microscope = '$iconsPath/007-microscope.svg';
20 | static const mapLocation = '$iconsPath/009-map location.svg';
21 | static const testTube = '$iconsPath/010-test tube.svg';
22 | static const dna = '$iconsPath/011-dna.svg';
23 | static const mobilePhone = '$iconsPath/012-mobile phone.svg';
24 | static const doctor = '$iconsPath/014-doctor.svg';
25 | static const syringe = '$iconsPath/016-syringe.svg';
26 | static const bloodHand = '$iconsPath/017-blood drop.svg';
27 | static const bloodTube = '$iconsPath/018-blood tube.svg';
28 | static const hospital = '$iconsPath/019-hospital.svg';
29 | static const redBloodCells = '$iconsPath/021-red blood cells.svg';
30 | static const bloodDonation = '$iconsPath/022-blood donation.svg';
31 | static const nurse = '$iconsPath/024-nurse.svg';
32 | static const donor = '$iconsPath/025-donor.svg';
33 | static const mapPointer = '$iconsPath/026-map pointer.svg';
34 | static const bloodDonorCard = '$iconsPath/028-blood donor card.svg';
35 | static const rubberGloves = '$iconsPath/029-rubber gloves.svg';
36 | static const syringeNeedle = '$iconsPath/030-syringe needle.svg';
37 | static const bloodBank = '$iconsPath/031-blood bank.svg';
38 | static const bloodBagHand = '$iconsPath/032-blood bag.svg';
39 | static const virus = '$iconsPath/034-virus.svg';
40 | static const bandAid = '$iconsPath/035-band aid.svg';
41 | static const dentistChair = '$iconsPath/036-dentist chair.svg';
42 | static const stethoscope = '$iconsPath/038-stethoscope.svg';
43 | static const ribbon = '$iconsPath/039-ribbon.svg';
44 | static const bloodBagStand = '$iconsPath/040-blood bag.svg';
45 | static const bloodTick = '$iconsPath/041-blood drop.svg';
46 | static const dropper = '$iconsPath/042-dropper.svg';
47 | static const shirt = '$iconsPath/043-shirt.svg';
48 | static const bloodPlus = '$iconsPath/044-blood drop.svg';
49 | static const bloodDropEmpty = '$iconsPath/045-blood drop empty.svg';
50 | }
51 |
--------------------------------------------------------------------------------
/assets/icons/010-test tube.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/screens/home_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 |
4 | import '../common/assets.dart';
5 | import '../common/colors.dart';
6 | import '../widgets/all_blood_requests.dart';
7 | import '../widgets/custom_drawer.dart';
8 |
9 | class HomeScreen extends StatelessWidget {
10 | static const route = 'home';
11 | const HomeScreen({Key key}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | drawer: const CustomDrawer(),
17 | appBar: AppBar(title: const Text('Blood Requests')),
18 | body: SafeArea(
19 | child: CustomScrollView(
20 | physics: const BouncingScrollPhysics(),
21 | slivers: [
22 | SliverToBoxAdapter(
23 | child: Padding(
24 | padding: const EdgeInsets.symmetric(
25 | horizontal: 24,
26 | vertical: 16,
27 | ),
28 | child: Card(
29 | margin: EdgeInsets.zero,
30 | elevation: 3,
31 | shape: RoundedRectangleBorder(
32 | borderRadius: BorderRadius.circular(16),
33 | ),
34 | child: Padding(
35 | padding: const EdgeInsets.all(16),
36 | child: Row(
37 | children: [
38 | SvgPicture.asset(
39 | IconAssets.bloodBagHand,
40 | height: 80,
41 | width: 80,
42 | ),
43 | const SizedBox(width: 12),
44 | Expanded(
45 | child: Center(
46 | child: Text(
47 | 'Donate Blood,\nSave Lives',
48 | textAlign: TextAlign.center,
49 | style: Theme.of(context)
50 | .textTheme
51 | .headline5
52 | .copyWith(color: MainColors.primary),
53 | ),
54 | ),
55 | ),
56 | ],
57 | ),
58 | ),
59 | ),
60 | ),
61 | ),
62 | SliverAppBar(
63 | title: Text(
64 | 'Current Requests',
65 | style: Theme.of(context)
66 | .textTheme
67 | .headline5
68 | .copyWith(color: MainColors.primary),
69 | ),
70 | primary: false,
71 | pinned: true,
72 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
73 | automaticallyImplyLeading: false,
74 | ),
75 | const AllBloodRequests(),
76 | ],
77 | ),
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/screens/splash_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:firebase_core/firebase_core.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_svg/svg.dart';
6 | import 'package:hive/hive.dart';
7 |
8 | import '../common/assets.dart';
9 | import '../common/hive_boxes.dart';
10 | import '../common/styles.dart';
11 | import 'home_screen.dart';
12 | import 'login_screen.dart';
13 | import 'tutorial_screen.dart';
14 |
15 | class SplashScreen extends StatefulWidget {
16 | static const route = '/';
17 | const SplashScreen({Key key}) : super(key: key);
18 |
19 | @override
20 | _SplashScreenState createState() => _SplashScreenState();
21 | }
22 |
23 | class _SplashScreenState extends State {
24 | String _destination = '';
25 |
26 | @override
27 | void initState() {
28 | super.initState();
29 | Firebase.initializeApp().whenComplete(_resolveDestination);
30 | }
31 |
32 | Future _resolveDestination() async {
33 | debugPrint('Firebase init complete');
34 |
35 | // Allows the splash screen to remain for a bit longer
36 | await Future.delayed(const Duration(seconds: 2));
37 |
38 | final isFirstLaunch = Hive.box(ConfigBox.key)
39 | .get(ConfigBox.isFirstLaunch, defaultValue: true) as bool;
40 |
41 | if (isFirstLaunch) {
42 | _destination = TutorialScreen.route;
43 | } else if (FirebaseAuth.instance.currentUser != null) {
44 | _destination = HomeScreen.route;
45 | _updateCachedData();
46 | } else {
47 | _destination = LoginScreen.route;
48 | }
49 |
50 | Navigator.of(context).pushReplacementNamed(_destination);
51 | }
52 |
53 | Future _updateCachedData() async {
54 | FirebaseFirestore.instance
55 | .collection('users')
56 | .doc(FirebaseAuth.instance.currentUser.uid)
57 | .get()
58 | .then((value) {
59 | final configBox = Hive.box(ConfigBox.key);
60 | configBox.put(
61 | ConfigBox.bloodType,
62 | value.data()['bloodType'] as String,
63 | );
64 | configBox.put(
65 | ConfigBox.isAdmin,
66 | value.data()['isAdmin'] as bool ?? false,
67 | );
68 | });
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | return Scaffold(
74 | body: SafeArea(
75 | child: Container(
76 | alignment: Alignment.center,
77 | child: Column(
78 | mainAxisSize: MainAxisSize.min,
79 | children: [
80 | SvgPicture.asset(IconAssets.logo),
81 | const SizedBox(height: 28),
82 | Flexible(
83 | child: Text(
84 | 'Blood Donation',
85 | textAlign: TextAlign.center,
86 | style: Theme.of(context).textTheme.headline4.copyWith(
87 | fontFamily: Fonts.logo,
88 | ),
89 | ),
90 | ),
91 | ],
92 | ),
93 | ),
94 | ),
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/assets/icons/016-syringe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/017-blood drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/widgets/all_blood_requests.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/flutter_svg.dart';
5 |
6 | import '../common/assets.dart';
7 | import '../common/styles.dart';
8 | import '../data/blood_request.dart';
9 | import '../widgets/blood_request_tile.dart';
10 |
11 | class AllBloodRequests extends StatefulWidget {
12 | const AllBloodRequests({Key key}) : super(key: key);
13 |
14 | @override
15 | _AllBloodRequestsState createState() => _AllBloodRequestsState();
16 | }
17 |
18 | class _AllBloodRequestsState extends State {
19 | Stream>> _query;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _query = FirebaseFirestore.instance
25 | .collection('blood_requests')
26 | .where('isFulfilled', isEqualTo: false)
27 | .orderBy('requestDate')
28 | .limit(30)
29 | .snapshots();
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return StreamBuilder>>(
35 | stream: _query,
36 | builder: (context, snapshot) {
37 | if (snapshot.hasError) {
38 | return SliverFillRemaining(
39 | hasScrollBody: false,
40 | child: Center(
41 | child: Text(
42 | 'Could not fetch blood requests',
43 | textAlign: TextAlign.center,
44 | style: Theme.of(context).textTheme.subtitle1,
45 | ),
46 | ),
47 | );
48 | }
49 |
50 | if (snapshot.connectionState == ConnectionState.active) {
51 | if (snapshot.data.docs.isEmpty) {
52 | return SliverFillRemaining(
53 | hasScrollBody: false,
54 | child: Center(
55 | child: Column(
56 | mainAxisAlignment: MainAxisAlignment.center,
57 | children: [
58 | SvgPicture.asset(IconAssets.bloodBag, height: 140),
59 | const SizedBox(height: 16),
60 | const Text(
61 | 'No requests yet!',
62 | style: TextStyle(fontFamily: Fonts.logo, fontSize: 20),
63 | ),
64 | ],
65 | ),
66 | ),
67 | );
68 | } else {
69 | return SliverList(
70 | delegate: SliverChildBuilderDelegate(
71 | (context, i) {
72 | return BloodRequestTile(
73 | request: BloodRequest.fromJson(
74 | snapshot.data.docs[i].data(),
75 | id: snapshot.data.docs[i].id,
76 | ),
77 | );
78 | },
79 | childCount: snapshot.data.size,
80 | ),
81 | );
82 | }
83 | }
84 |
85 | return const SliverFillRemaining(
86 | hasScrollBody: false,
87 | child: Center(child: CircularProgressIndicator()),
88 | );
89 | },
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
17 |
24 |
28 |
32 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/assets/icons/014-doctor.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/040-blood bag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/widgets/submitted_blood_requests.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/flutter_svg.dart';
5 |
6 | import '../common/assets.dart';
7 | import '../common/styles.dart';
8 | import '../data/blood_request.dart';
9 | import 'blood_request_tile.dart';
10 |
11 | class SubmittedBloodRequests extends StatefulWidget {
12 | final bool activeOnly;
13 |
14 | const SubmittedBloodRequests({
15 | Key key,
16 | this.activeOnly = true,
17 | }) : super(key: key);
18 |
19 | @override
20 | _SubmittedBloodRequestsState createState() => _SubmittedBloodRequestsState();
21 | }
22 |
23 | class _SubmittedBloodRequestsState extends State {
24 | Future>> _submittedRequests;
25 |
26 | @override
27 | void initState() {
28 | super.initState();
29 | if (widget.activeOnly) {
30 | _submittedRequests = FirebaseFirestore.instance
31 | .collection('blood_requests')
32 | .where('uid', isEqualTo: FirebaseAuth.instance.currentUser.uid)
33 | .where('isFulfilled', isEqualTo: false)
34 | .orderBy('submittedAt', descending: true)
35 | .get();
36 | } else {
37 | _submittedRequests = FirebaseFirestore.instance
38 | .collection('blood_requests')
39 | .where('uid', isEqualTo: FirebaseAuth.instance.currentUser.uid)
40 | .orderBy('submittedAt', descending: true)
41 | .limit(20)
42 | .get();
43 | }
44 | }
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return FutureBuilder>>(
49 | future: _submittedRequests,
50 | builder: (context, snapshot) {
51 | if (snapshot.hasError) {
52 | return Center(
53 | child: Text(
54 | 'Could not fetch submitted requests',
55 | textAlign: TextAlign.center,
56 | style: Theme.of(context).textTheme.subtitle1,
57 | ),
58 | );
59 | }
60 | if (snapshot.connectionState == ConnectionState.done) {
61 | if (snapshot.data.docs.isEmpty) {
62 | return Center(
63 | child: Column(
64 | mainAxisAlignment: MainAxisAlignment.center,
65 | children: [
66 | SvgPicture.asset(IconAssets.bloodBag, height: 140),
67 | const SizedBox(height: 16),
68 | const Text(
69 | 'No requests yet!',
70 | style: TextStyle(fontFamily: Fonts.logo, fontSize: 20),
71 | ),
72 | ],
73 | ),
74 | );
75 | } else {
76 | return ListView.builder(
77 | itemCount: snapshot.data.size,
78 | physics: const BouncingScrollPhysics(),
79 | itemBuilder: (context, i) {
80 | return BloodRequestTile(
81 | request: BloodRequest.fromJson(
82 | snapshot.data.docs[i].data(),
83 | id: snapshot.data.docs[i].id,
84 | ),
85 | );
86 | },
87 | );
88 | }
89 | }
90 |
91 | return const Center(child: CircularProgressIndicator());
92 | },
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/widgets/blood_request_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../common/colors.dart';
4 | import '../data/blood_request.dart';
5 | import '../screens/single_request_screen.dart';
6 | import '../utils/blood_types.dart';
7 | import '../utils/tools.dart';
8 |
9 | const kBorderRadius = 12.0;
10 |
11 | class BloodRequestTile extends StatelessWidget {
12 | final BloodRequest request;
13 |
14 | const BloodRequestTile({Key key, this.request}) : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final textTheme = Theme.of(context).textTheme;
19 | return Card(
20 | margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
21 | elevation: 2,
22 | shape: RoundedRectangleBorder(
23 | borderRadius: BorderRadius.circular(kBorderRadius),
24 | ),
25 | child: Column(
26 | children: [
27 | Padding(
28 | padding: const EdgeInsets.all(16),
29 | child: Row(
30 | children: [
31 | Expanded(
32 | child: Column(
33 | crossAxisAlignment: CrossAxisAlignment.start,
34 | children: [
35 | Text('Patient Name', style: textTheme.caption),
36 | Text(request.patientName ?? ''),
37 | const SizedBox(height: 12),
38 | Text('Location', style: textTheme.caption),
39 | Text(
40 | '${request.medicalCenter.name} - ${request.medicalCenter.location}',
41 | ),
42 | ],
43 | ),
44 | ),
45 | const SizedBox(width: 12),
46 | Column(
47 | crossAxisAlignment: CrossAxisAlignment.start,
48 | children: [
49 | Text('Needed By', style: textTheme.caption),
50 | Text(Tools.formatDate(request.requestDate) ?? ''),
51 | const SizedBox(height: 12),
52 | Text('Blood Type', style: textTheme.caption),
53 | Text(request.bloodType.name ?? ''),
54 | ],
55 | ),
56 | ],
57 | ),
58 | ),
59 | const SizedBox(height: 8),
60 | InkWell(
61 | onTap: () {
62 | Navigator.of(context).push(MaterialPageRoute(
63 | builder: (_) => SingleRequestScreen(request: request),
64 | ));
65 | },
66 | borderRadius: const BorderRadius.only(
67 | bottomRight: Radius.circular(kBorderRadius),
68 | bottomLeft: Radius.circular(kBorderRadius),
69 | ),
70 | child: Ink(
71 | padding: const EdgeInsets.all(12),
72 | width: double.infinity,
73 | decoration: const BoxDecoration(
74 | color: MainColors.primary,
75 | borderRadius: BorderRadius.only(
76 | bottomRight: Radius.circular(kBorderRadius),
77 | bottomLeft: Radius.circular(kBorderRadius),
78 | ),
79 | ),
80 | child: Center(
81 | child: Text(
82 | 'Details',
83 | style: textTheme.button.copyWith(color: Colors.white),
84 | ),
85 | ),
86 | ),
87 | ),
88 | ],
89 | ),
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/screens/add_news_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 |
5 | import '../utils/validators.dart';
6 | import '../widgets/action_button.dart';
7 |
8 | class AddNewsItem extends StatefulWidget {
9 | static const route = 'add-news';
10 | const AddNewsItem({Key key}) : super(key: key);
11 |
12 | @override
13 | _AddNewsItemState createState() => _AddNewsItemState();
14 | }
15 |
16 | class _AddNewsItemState extends State {
17 | final _formKey = GlobalKey();
18 | final _titleController = TextEditingController();
19 | final _bodyController = TextEditingController();
20 | bool _isLoading = false;
21 |
22 | @override
23 | void dispose() {
24 | _titleController?.dispose();
25 | _bodyController?.dispose();
26 | super.dispose();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return Scaffold(
32 | appBar: AppBar(title: const Text('Add News Item')),
33 | body: SafeArea(
34 | child: Form(
35 | key: _formKey,
36 | child: SingleChildScrollView(
37 | child: Padding(
38 | padding: const EdgeInsets.all(24),
39 | child: Column(
40 | mainAxisAlignment: MainAxisAlignment.center,
41 | children: [
42 | TextFormField(
43 | controller: _titleController,
44 | textCapitalization: TextCapitalization.sentences,
45 | validator: (v) => Validators.required(v, 'Title'),
46 | decoration: const InputDecoration(
47 | border: OutlineInputBorder(),
48 | labelText: 'Title',
49 | ),
50 | ),
51 | const SizedBox(height: 24),
52 | TextFormField(
53 | controller: _bodyController,
54 | textCapitalization: TextCapitalization.sentences,
55 | minLines: 3,
56 | maxLines: 5,
57 | validator: (v) => Validators.required(v, 'Body'),
58 | decoration: const InputDecoration(
59 | border: OutlineInputBorder(),
60 | labelText: 'Body',
61 | ),
62 | ),
63 | const SizedBox(height: 36),
64 | ActionButton(
65 | callback: _submit,
66 | text: 'Submit',
67 | isLoading: _isLoading,
68 | ),
69 | ],
70 | ),
71 | ),
72 | ),
73 | ),
74 | ),
75 | );
76 | }
77 |
78 | Future _submit() async {
79 | if (_formKey.currentState.validate()) {
80 | setState(() => _isLoading = true);
81 | try {
82 | final news = FirebaseFirestore.instance.collection('news');
83 | await news.add({
84 | 'title': _titleController.text,
85 | 'body': _bodyController.text,
86 | 'date': DateTime.now(),
87 | });
88 | _titleController.clear();
89 | _bodyController.clear();
90 | Fluttertoast.showToast(msg: 'News item successfully added');
91 | } catch (e) {
92 | Fluttertoast.showToast(
93 | msg: 'Something went wrong. Please try again',
94 | );
95 | }
96 | setState(() => _isLoading = false);
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: blood_donation
2 | description: A Flutter application for blood donations.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.7.0 <3.0.0"
22 |
23 | dependencies:
24 | awesome_dialog: ^2.1.0
25 | cached_network_image: ^3.1.0
26 | cloud_firestore: ^2.4.0
27 | dots_indicator: ^2.0.0
28 | firebase_auth: ^3.0.1
29 | firebase_core: ^1.4.0
30 | firebase_storage: ^10.0.1
31 | flutter:
32 | sdk: flutter
33 | flutter_svg: ^0.22.0
34 | fluttertoast: ^8.0.7
35 | fontisto_flutter: ^3.1.2
36 | hive: ^2.0.4
37 | hive_flutter: ^1.1.0
38 | image_picker: ^0.8.2
39 | share: ^2.0.4
40 | url_launcher: ^6.0.9
41 |
42 | dev_dependencies:
43 | flutter_test:
44 | sdk: flutter
45 | lint: ^1.5.3
46 |
47 | # For information on the generic Dart part of this file, see the
48 | # following page: https://dart.dev/tools/pub/pubspec
49 |
50 | # The following section is specific to Flutter.
51 | flutter:
52 |
53 | # The following line ensures that the Material Icons font is
54 | # included with your application, so that you can use the icons in
55 | # the material Icons class.
56 | uses-material-design: true
57 |
58 | # To add assets to your application, add an assets section, like this:
59 | # assets:
60 | # - images/a_dot_burr.jpeg
61 | # - images/a_dot_ham.jpeg
62 |
63 | # An image asset can refer to one or more resolution-specific "variants", see
64 | # https://flutter.dev/assets-and-images/#resolution-aware.
65 |
66 | # For details regarding adding assets from package dependencies, see
67 | # https://flutter.dev/assets-and-images/#from-packages
68 |
69 | assets:
70 | - assets/icons/
71 |
72 | # To add custom fonts to your application, add a fonts section here,
73 | # in this "flutter" section. Each entry in this list should have a
74 | # "family" key with the font family name, and a "fonts" key with a
75 | # list giving the asset and other descriptors for the font. For
76 | # example:
77 | # fonts:
78 | # - family: Schyler
79 | # fonts:
80 | # - asset: fonts/Schyler-Regular.ttf
81 | # - asset: fonts/Schyler-Italic.ttf
82 | # style: italic
83 | # - family: Trajan Pro
84 | # fonts:
85 | # - asset: fonts/TrajanPro.ttf
86 | # - asset: fonts/TrajanPro_Bold.ttf
87 | # weight: 700
88 | #
89 | # For details regarding fonts from package dependencies,
90 | # see https://flutter.dev/custom-fonts/#from-packages
91 |
92 | fonts:
93 | - family: SF
94 | fonts:
95 | - asset: assets/fonts/sf.ttf
96 | - family: Courgette
97 | fonts:
98 | - asset: assets/fonts/courgette.ttf
--------------------------------------------------------------------------------
/assets/icons/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/data/medical_centers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Auxilia",
4 | "location": "Lebanon",
5 | "latitude": "33.888630",
6 | "longitude": "35.495480",
7 | "phone_numbers": ["01282883", "01290407", "09218922"]
8 | },
9 | {
10 | "name": "Green Peace",
11 | "location": "Lebanon",
12 | "latitude": "33.888630",
13 | "longitude": "35.495480",
14 | "phone_numbers": ["01785665"]
15 | },
16 | {
17 | "name": "Caritas",
18 | "location": "Lebanon",
19 | "latitude": "33.888630",
20 | "longitude": "35.495480",
21 | "phone_numbers": ["01384737", "01384738", "01384739", "01502552"]
22 | },
23 | {
24 | "name": "Centre Sedim",
25 | "location": "Furn el Chebbak",
26 | "latitude": "33.888630",
27 | "longitude": "35.495480",
28 | "phone_numbers": ["01293100", "01293101"]
29 | },
30 | {
31 | "name": "Arc en Ciel",
32 | "location": "Jisr el Wateh",
33 | "latitude": "33.888630",
34 | "longitude": "35.495480",
35 | "phone_numbers": ["01564630", "01564631", "01564632", "01564633"]
36 | },
37 | {
38 | "name": "Yasa",
39 | "location": "Hazmieh",
40 | "latitude": "33.888630",
41 | "longitude": "35.495480",
42 | "phone_numbers": ["05452587", "05952587"]
43 | },
44 | {
45 | "name": "Air Liquide O2",
46 | "location": "Dekwaneh",
47 | "latitude": "33.888630",
48 | "longitude": "35.495480",
49 | "phone_numbers": ["01692380", "01692381", "01692382", "01692383", "01692384", "01692385"]
50 | },
51 | {
52 | "name": "CRC لوازم معاقين",
53 | "location": "Lebanon",
54 | "latitude": "33.888630",
55 | "longitude": "35.495480",
56 | "phone_numbers": ["01510261", "03601793"]
57 | },
58 | {
59 | "name": "Jad للمخدرات",
60 | "location": "Lebanon",
61 | "latitude": "33.888630",
62 | "longitude": "35.495480",
63 | "phone_numbers": ["09546357", "03749484"]
64 | },
65 | {
66 | "name": "Hindi Laboratory مختبرات هندي",
67 | "location": "Jal el Dib",
68 | "latitude": "33.888630",
69 | "longitude": "35.495480",
70 | "phone_numbers": ["04713131"]
71 | },
72 | {
73 | "name": "CICR اللجنة الدولية للصليب الأحمر",
74 | "location": "Hamra",
75 | "latitude": "33.888630",
76 | "longitude": "35.495480",
77 | "phone_numbers": ["01739297", "01739298", "01739299"]
78 | },
79 | {
80 | "name": "LRC Garage كاراج الصليب الأحمر",
81 | "location": "Sed el Baouchrieh",
82 | "latitude": "33.888630",
83 | "longitude": "35.495480",
84 | "phone_numbers": ["03407522", "03337542"]
85 | },
86 | {
87 | "name": "مركز الرعاية الدائمة",
88 | "location": "Fiyadieh",
89 | "latitude": "33.888630",
90 | "longitude": "35.495480",
91 | "phone_numbers": ["05456859"]
92 | },
93 | {
94 | "name": "مركز الحريري للتصوير",
95 | "location": "Unesco",
96 | "latitude": "33.888630",
97 | "longitude": "35.495480",
98 | "phone_numbers": ["01705376"]
99 | },
100 | {
101 | "name": "مركز السياحي الطبي",
102 | "location": "Ain el Remmaneh",
103 | "latitude": "33.888630",
104 | "longitude": "35.495480",
105 | "phone_numbers": ["01665330"]
106 | },
107 | {
108 | "name": "مركز الشياح الطبي",
109 | "location": "Chiyah",
110 | "latitude": "33.888630",
111 | "longitude": "35.495480",
112 | "phone_numbers": ["01290837"]
113 | },
114 | {
115 | "name": "إتحاد جمعيات الصليب الأحمر والهلال الأحمر",
116 | "location": "Fiyadieh",
117 | "latitude": "33.888630",
118 | "longitude": "35.495480",
119 | "phone_numbers": ["01424851"]
120 | },
121 | {
122 | "name": "صليب إعانة الأرمن",
123 | "location": "Burj Hammoud",
124 | "latitude": "33.888630",
125 | "longitude": "35.495480",
126 | "phone_numbers": ["01253793", "01253794", "01253795", "01253796"]
127 | },
128 | {
129 | "name": "الهيئة الصحية الإسلامية",
130 | "location": "Lebanon",
131 | "latitude": "33.888630",
132 | "longitude": "35.495480",
133 | "phone_numbers": ["01552637"]
134 | },
135 | {
136 | "name": "هيئة الإسعاف الشعبي",
137 | "location": "Lebanon",
138 | "latitude": "33.888630",
139 | "longitude": "35.495480",
140 | "phone_numbers": ["01735417"]
141 | }
142 | ]
143 |
--------------------------------------------------------------------------------
/assets/icons/035-band aid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/utils/blood_types.dart:
--------------------------------------------------------------------------------
1 | import '../common/assets.dart';
2 |
3 | enum BloodType { aPos, aNeg, bPos, bNeg, abPos, abNeg, oPos, oNeg }
4 |
5 | extension BloodTypeUtils on BloodType {
6 | static const bloodTypes = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-'];
7 |
8 | static BloodType fromName(String s) {
9 | switch (s) {
10 | case 'A+':
11 | return BloodType.aPos;
12 | case 'A-':
13 | return BloodType.aNeg;
14 | case 'B+':
15 | return BloodType.bPos;
16 | case 'B-':
17 | return BloodType.bNeg;
18 | case 'AB+':
19 | return BloodType.abPos;
20 | case 'AB-':
21 | return BloodType.abNeg;
22 | case 'O+':
23 | return BloodType.oPos;
24 | case 'O-':
25 | return BloodType.oNeg;
26 | default:
27 | throw AssertionError('Blood type does not exist');
28 | }
29 | }
30 |
31 | String get name {
32 | switch (this) {
33 | case BloodType.aPos:
34 | return 'A+';
35 | case BloodType.aNeg:
36 | return 'A-';
37 | case BloodType.bPos:
38 | return 'B+';
39 | case BloodType.bNeg:
40 | return 'B-';
41 | case BloodType.abPos:
42 | return 'AB+';
43 | case BloodType.abNeg:
44 | return 'AB-';
45 | case BloodType.oPos:
46 | return 'O+';
47 | case BloodType.oNeg:
48 | return 'O-';
49 | default:
50 | throw AssertionError('Blood type does not exist');
51 | }
52 | }
53 |
54 | String get icon {
55 | switch (this) {
56 | case BloodType.aPos:
57 | return IconAssets.bloodAPos;
58 | case BloodType.aNeg:
59 | return IconAssets.bloodANeg;
60 | case BloodType.bPos:
61 | return IconAssets.bloodBPos;
62 | case BloodType.bNeg:
63 | return IconAssets.bloodBNeg;
64 | case BloodType.abPos:
65 | return IconAssets.bloodABPos;
66 | case BloodType.abNeg:
67 | return IconAssets.bloodABNeg;
68 | case BloodType.oPos:
69 | return IconAssets.bloodOPos;
70 | case BloodType.oNeg:
71 | return IconAssets.bloodONeg;
72 | default:
73 | throw AssertionError('Blood type does not exist');
74 | }
75 | }
76 |
77 | List get possibleDonors {
78 | switch (this) {
79 | case BloodType.aPos:
80 | return [BloodType.aPos, BloodType.aNeg, BloodType.oPos, BloodType.oNeg];
81 | case BloodType.aNeg:
82 | return [BloodType.aNeg, BloodType.oNeg];
83 | case BloodType.bPos:
84 | return [BloodType.bPos, BloodType.bNeg, BloodType.oPos, BloodType.oNeg];
85 | case BloodType.bNeg:
86 | return [BloodType.bNeg, BloodType.oNeg];
87 | case BloodType.abPos:
88 | // can get from all
89 | return BloodType.values;
90 | case BloodType.abNeg:
91 | return [
92 | BloodType.abNeg,
93 | BloodType.aNeg,
94 | BloodType.bNeg,
95 | BloodType.oNeg
96 | ];
97 | case BloodType.oPos:
98 | return [BloodType.oPos, BloodType.oNeg];
99 | case BloodType.oNeg:
100 | return [BloodType.oNeg];
101 | default:
102 | return [];
103 | }
104 | }
105 |
106 | List get possibleRecipients {
107 | switch (this) {
108 | case BloodType.aPos:
109 | return [BloodType.aPos, BloodType.abPos];
110 | case BloodType.aNeg:
111 | return [
112 | BloodType.aNeg,
113 | BloodType.aPos,
114 | BloodType.abNeg,
115 | BloodType.abPos
116 | ];
117 | case BloodType.bPos:
118 | return [BloodType.bPos, BloodType.abPos];
119 | case BloodType.bNeg:
120 | return [
121 | BloodType.bNeg,
122 | BloodType.bPos,
123 | BloodType.abNeg,
124 | BloodType.abPos
125 | ];
126 | case BloodType.abPos:
127 | return [BloodType.abPos];
128 | case BloodType.abNeg:
129 | return [BloodType.abNeg, BloodType.abPos];
130 | case BloodType.oPos:
131 | return [
132 | BloodType.oPos,
133 | BloodType.aPos,
134 | BloodType.bPos,
135 | BloodType.abPos
136 | ];
137 | case BloodType.oNeg:
138 | // Can donate to all
139 | return BloodType.values;
140 | default:
141 | return [];
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/assets/icons/032-blood bag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/data/lists/medical_centers.dart:
--------------------------------------------------------------------------------
1 | import '../medical_center.dart';
2 |
3 | const medicalCenters = [
4 | MedicalCenter(
5 | name: "Auxilia",
6 | phoneNumbers: ["01282883", "01290407", "09218922"],
7 | location: "Lebanon",
8 | longitude: "35.495480",
9 | latitude: "33.888630",
10 | ),
11 | MedicalCenter(
12 | name: "Green Peace",
13 | phoneNumbers: ["01785665"],
14 | location: "Lebanon",
15 | longitude: "35.495480",
16 | latitude: "33.888630",
17 | ),
18 | MedicalCenter(
19 | name: "Caritas",
20 | phoneNumbers: ["01384737", "01384738", "01384739", "01502552"],
21 | location: "Lebanon",
22 | longitude: "35.495480",
23 | latitude: "33.888630",
24 | ),
25 | MedicalCenter(
26 | name: "Centre Sedim",
27 | phoneNumbers: ["01293100", "01293101"],
28 | location: "Furn el Chebbak",
29 | longitude: "35.495480",
30 | latitude: "33.888630",
31 | ),
32 | MedicalCenter(
33 | name: "Arc en Ciel",
34 | phoneNumbers: ["01564630", "01564631", "01564632", "01564633"],
35 | location: "Jisr el Wateh",
36 | longitude: "35.495480",
37 | latitude: "33.888630",
38 | ),
39 | MedicalCenter(
40 | name: "Yasa",
41 | phoneNumbers: ["05452587", "05952587"],
42 | location: "Hazmieh",
43 | longitude: "35.495480",
44 | latitude: "33.888630",
45 | ),
46 | MedicalCenter(
47 | name: "Air Liquide O2",
48 | phoneNumbers: [
49 | "01692380",
50 | "01692381",
51 | "01692382",
52 | "01692383",
53 | "01692384",
54 | "01692385"
55 | ],
56 | location: "Dekwaneh",
57 | longitude: "35.495480",
58 | latitude: "33.888630",
59 | ),
60 | MedicalCenter(
61 | name: "CRC لوازم معاقين",
62 | phoneNumbers: ["01510261", "03601793"],
63 | location: "Lebanon",
64 | longitude: "35.495480",
65 | latitude: "33.888630",
66 | ),
67 | MedicalCenter(
68 | name: "Jad للمخدرات",
69 | phoneNumbers: ["09546357", "03749484"],
70 | location: "Lebanon",
71 | longitude: "35.495480",
72 | latitude: "33.888630",
73 | ),
74 | MedicalCenter(
75 | name: "Hindi Laboratory مختبرات هندي",
76 | phoneNumbers: ["04713131"],
77 | location: "Jal el Dib",
78 | longitude: "35.495480",
79 | latitude: "33.888630",
80 | ),
81 | MedicalCenter(
82 | name: "CICR اللجنة الدولية للصليب الأحمر",
83 | phoneNumbers: ["01739297", "01739298", "01739299"],
84 | location: "Hamra",
85 | longitude: "35.495480",
86 | latitude: "33.888630",
87 | ),
88 | MedicalCenter(
89 | name: "LRC Garage كاراج الصليب الأحمر",
90 | phoneNumbers: ["03407522", "03337542"],
91 | location: "Sed el Baouchrieh",
92 | longitude: "35.495480",
93 | latitude: "33.888630",
94 | ),
95 | MedicalCenter(
96 | name: "مركز الرعاية الدائمة",
97 | phoneNumbers: ["05456859"],
98 | location: "Fiyadieh",
99 | longitude: "35.495480",
100 | latitude: "33.888630",
101 | ),
102 | MedicalCenter(
103 | name: "مركز الحريري للتصوير",
104 | phoneNumbers: ["01705376"],
105 | location: "Unesco",
106 | longitude: "35.495480",
107 | latitude: "33.888630",
108 | ),
109 | MedicalCenter(
110 | name: "مركز السياحي الطبي",
111 | phoneNumbers: ["01665330"],
112 | location: "Ain el Remmaneh",
113 | longitude: "35.495480",
114 | latitude: "33.888630",
115 | ),
116 | MedicalCenter(
117 | name: "مركز الشياح الطبي",
118 | phoneNumbers: ["01290837"],
119 | location: "Chiyah",
120 | longitude: "35.495480",
121 | latitude: "33.888630",
122 | ),
123 | MedicalCenter(
124 | name: "إتحاد جمعيات الصليب الأحمر والهلال الأحمر",
125 | phoneNumbers: ["01424851"],
126 | location: "Fiyadieh",
127 | longitude: "35.495480",
128 | latitude: "33.888630",
129 | ),
130 | MedicalCenter(
131 | name: "صليب إعانة الأرمن",
132 | phoneNumbers: ["01253793", "01253794", "01253795", "01253796"],
133 | location: "Burj Hammoud",
134 | longitude: "35.495480",
135 | latitude: "33.888630",
136 | ),
137 | MedicalCenter(
138 | name: "الهيئة الصحية الإسلامية",
139 | phoneNumbers: ["01552637"],
140 | location: "Lebanon",
141 | longitude: "35.495480",
142 | latitude: "33.888630",
143 | ),
144 | MedicalCenter(
145 | name: "هيئة الإسعاف الشعبي",
146 | phoneNumbers: ["01735417"],
147 | location: "Lebanon",
148 | longitude: "35.495480",
149 | latitude: "33.888630",
150 | )
151 | ];
152 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
31 |
34 |
37 |
40 |
43 |
46 |
49 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/assets/icons/034-virus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/screens/tutorial_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:dots_indicator/dots_indicator.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_svg/flutter_svg.dart';
4 | import 'package:hive/hive.dart';
5 |
6 | import '../common/assets.dart';
7 | import '../common/colors.dart';
8 | import '../common/hive_boxes.dart';
9 | import 'login_screen.dart';
10 |
11 | class TutorialScreen extends StatefulWidget {
12 | static const route = 'tutorial';
13 | const TutorialScreen({Key key}) : super(key: key);
14 |
15 | @override
16 | _TutorialScreenState createState() => _TutorialScreenState();
17 | }
18 |
19 | class _TutorialScreenState extends State
20 | with SingleTickerProviderStateMixin {
21 | final _controller = PageController();
22 | int _currentIndex = 0;
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _controller.addListener(() {
28 | if (_controller.page.round() != _currentIndex) {
29 | setState(() => _currentIndex = _controller.page.round());
30 | }
31 | });
32 | }
33 |
34 | @override
35 | void dispose() {
36 | _controller?.dispose();
37 | super.dispose();
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return Scaffold(
43 | body: SafeArea(
44 | child: Column(
45 | children: [
46 | Expanded(
47 | child: PageView(
48 | controller: _controller,
49 | physics: const BouncingScrollPhysics(),
50 | children: const [
51 | _TutorialPanel(
52 | asset: IconAssets.bloodHand,
53 | title: 'Request Blood',
54 | body: 'Submit a blood request and let the donors know!',
55 | ),
56 | _TutorialPanel(
57 | asset: IconAssets.bloodBagHand,
58 | title: 'Donate Blood',
59 | body: 'Browse the requests and check if you can help by '
60 | 'donating blood to those who need it',
61 | ),
62 | _TutorialPanel(
63 | asset: IconAssets.clipboard,
64 | title: 'Health Information',
65 | body: 'Stay updated with the latest health tips and '
66 | 'information',
67 | ),
68 | ],
69 | ),
70 | ),
71 | Padding(
72 | padding: const EdgeInsets.symmetric(vertical: 32),
73 | child: DotsIndicator(
74 | dotsCount: 3,
75 | decorator: const DotsDecorator(
76 | activeColor: MainColors.primary,
77 | size: Size.square(12),
78 | activeSize: Size.square(12),
79 | ),
80 | position: _currentIndex * 1.0,
81 | ),
82 | ),
83 | InkWell(
84 | onTap: () {
85 | if (_currentIndex == 2) {
86 | Hive.box(ConfigBox.key).put(ConfigBox.isFirstLaunch, false);
87 | Navigator.of(context).pushReplacementNamed(LoginScreen.route);
88 | } else {
89 | _controller.animateToPage(
90 | _currentIndex + 1,
91 | duration: const Duration(milliseconds: 300),
92 | curve: Curves.decelerate,
93 | );
94 | }
95 | },
96 | child: Ink(
97 | color: MainColors.primary,
98 | padding: const EdgeInsets.all(16),
99 | width: double.infinity,
100 | child: Text(
101 | _currentIndex == 2 ? "Let's go!" : 'Next',
102 | textAlign: TextAlign.center,
103 | style: const TextStyle(color: Colors.white, fontSize: 16),
104 | ),
105 | ),
106 | ),
107 | ],
108 | ),
109 | ),
110 | );
111 | }
112 | }
113 |
114 | class _TutorialPanel extends StatelessWidget {
115 | final String asset, title, body;
116 |
117 | const _TutorialPanel({
118 | Key key,
119 | @required this.asset,
120 | @required this.title,
121 | @required this.body,
122 | }) : super(key: key);
123 |
124 | @override
125 | Widget build(BuildContext context) {
126 | final textTheme = Theme.of(context).textTheme;
127 | return Padding(
128 | padding: const EdgeInsets.all(24),
129 | child: Column(
130 | mainAxisAlignment: MainAxisAlignment.center,
131 | children: [
132 | Container(
133 | padding: const EdgeInsets.symmetric(vertical: 42),
134 | child: SvgPicture.asset(
135 | asset,
136 | fit: BoxFit.fitWidth,
137 | width: MediaQuery.of(context).size.width * 0.5,
138 | ),
139 | ),
140 | Text(
141 | title,
142 | style: textTheme.headline4.copyWith(
143 | color: MainColors.primary,
144 | ),
145 | ),
146 | const SizedBox(height: 18),
147 | Text(
148 | body,
149 | textAlign: TextAlign.center,
150 | style: textTheme.headline3.copyWith(fontSize: 18, height: 1.2),
151 | ),
152 | ],
153 | ),
154 | );
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/assets/icons/011-dna.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------