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