├── 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 | ![01 - Splash](screenshots/01%20-%20splash.jpg) 14 | ![02 - Tutorial](screenshots/02%20-%20tutorial%201.jpg) 15 | ![03 - Login](screenshots/03%20-%20Login%20empty.jpg) 16 | ![04 - Login error](screenshots/04%20-%20login%20wrong%20password.jpg) 17 | ![05 - Register](screenshots/05%20-%20register%20empty.jpg) 18 | ![06 - Home](screenshots/06%20-%20home.jpg) 19 | ![07 - Request details](screenshots/07%20-%20request%20detail.jpg) 20 | ![08 - Drawer](screenshots/08%20-%20drawer.jpg) 21 | ![09 - Profile](screenshots/09%20-%20profile.jpg) 22 | ![10 - Edit profile](screenshots/10%20-%20edit%20profile.jpg) 23 | ![11 - Medical Centers](screenshots/11%20-%20submit%20medical%20centers.jpg) 24 | ![12 - News](screenshots/12%20-%20news.jpg) 25 | ![13 - Who can donate](screenshots/13%20-%20who%20can%20donate.jpg) 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 | ![14 - drawer admin activated](screenshots/14%20-%20drawer%20admin%20activated.jpg) 32 | ![15 - admin add news](screenshots/15%20-%20admin%20add%20news.jpg) 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 | 7172737475767778797107117127137147157167177187197207217227237247 -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------