Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
M
Mapping-Software-Efficient-Routing-Algorithm
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
h703249754
Mapping-Software-Efficient-Routing-Algorithm
Commits
a2d2df27
Commit
a2d2df27
authored
Oct 07, 2025
by
aleenaasghar
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Web Page for Admin and Driver Users
parent
c2c69eec
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
1357 additions
and
513 deletions
+1357
-513
main.dart
lib/main.dart
+6
-1
delivery_address.dart
lib/models/delivery_address.dart
+12
-0
user_model.dart
lib/models/user_model.dart
+25
-0
admin_dashboard_screen.dart
lib/screens/admin_dashboard_screen.dart
+154
-225
assigned_addresses_screen.dart
lib/screens/assigned_addresses_screen.dart
+89
-0
driver_assignments_screen.dart
lib/screens/driver_assignments_screen.dart
+88
-0
login.dart
lib/screens/login.dart
+168
-107
map_screen.dart
lib/screens/map_screen.dart
+79
-39
signup.dart
lib/screens/signup.dart
+75
-108
firestore_service.dart
lib/services/firestore_service.dart
+138
-5
address_list.dart
lib/widgets/address_list.dart
+108
-28
assign_address_dialog.dart
lib/widgets/assign_address_dialog.dart
+82
-0
assign_drivers_dialog.dart
lib/widgets/assign_drivers_dialog.dart
+101
-0
drivers_list.dart
lib/widgets/drivers_list.dart
+66
-0
select_address_dialog.dart
lib/widgets/select_address_dialog.dart
+81
-0
users_list.dart
lib/widgets/users_list.dart
+64
-0
index.html
web/index.html
+21
-0
No files found.
lib/main.dart
View file @
a2d2df27
...
@@ -20,6 +20,8 @@ import 'screens/login.dart';
...
@@ -20,6 +20,8 @@ import 'screens/login.dart';
import
'screens/signup.dart'
;
import
'screens/signup.dart'
;
import
'screens/forgot_password.dart'
;
import
'screens/forgot_password.dart'
;
import
'screens/admin_dashboard_screen.dart'
;
import
'screens/admin_dashboard_screen.dart'
;
import
'screens/assigned_addresses_screen.dart'
;
import
'screens/driver_assignments_screen.dart'
;
Future
<
void
>
main
()
async
{
Future
<
void
>
main
()
async
{
WidgetsFlutterBinding
.
ensureInitialized
();
WidgetsFlutterBinding
.
ensureInitialized
();
...
@@ -79,7 +81,7 @@ class MyApp extends StatelessWidget {
...
@@ -79,7 +81,7 @@ class MyApp extends StatelessWidget {
),
),
useMaterial3:
true
,
useMaterial3:
true
,
),
),
home:
kIsWeb
?
const
AdminDashboardScreen
()
:
const
HomeScreen
(),
home:
kIsWeb
?
const
LoginPage
()
:
const
HomeScreen
(),
routes:
{
routes:
{
"/login"
:
(
context
)
=>
const
LoginPage
(),
"/login"
:
(
context
)
=>
const
LoginPage
(),
"/signup"
:
(
context
)
=>
const
SignupPage
(),
"/signup"
:
(
context
)
=>
const
SignupPage
(),
...
@@ -87,6 +89,9 @@ class MyApp extends StatelessWidget {
...
@@ -87,6 +89,9 @@ class MyApp extends StatelessWidget {
"/map"
:
(
context
)
=>
const
MapScreen
(),
"/map"
:
(
context
)
=>
const
MapScreen
(),
"/settings"
:
(
context
)
=>
const
SettingsScreen
(),
"/settings"
:
(
context
)
=>
const
SettingsScreen
(),
"/profile"
:
(
context
)
=>
const
ProfileScreen
(),
"/profile"
:
(
context
)
=>
const
ProfileScreen
(),
"/admin-dashboard"
:
(
context
)
=>
const
AdminDashboardScreen
(),
"/assigned-addresses"
:
(
context
)
=>
AssignedAddressesScreen
(),
"/driver-assignments"
:
(
context
)
=>
const
DriverAssignmentsScreen
(),
},
},
);
);
},
},
...
...
lib/models/delivery_address.dart
View file @
a2d2df27
...
@@ -11,6 +11,8 @@ class DeliveryAddress {
...
@@ -11,6 +11,8 @@ class DeliveryAddress {
final
double
?
longitude
;
final
double
?
longitude
;
final
String
?
notes
;
final
String
?
notes
;
final
DateTime
createdAt
;
final
DateTime
createdAt
;
final
String
?
driverId
;
final
String
status
;
DeliveryAddress
({
DeliveryAddress
({
String
?
id
,
String
?
id
,
...
@@ -23,6 +25,8 @@ class DeliveryAddress {
...
@@ -23,6 +25,8 @@ class DeliveryAddress {
this
.
longitude
,
this
.
longitude
,
this
.
notes
,
this
.
notes
,
DateTime
?
createdAt
,
DateTime
?
createdAt
,
this
.
driverId
,
this
.
status
=
'pending'
,
})
:
id
=
id
??
const
Uuid
().
v4
(),
})
:
id
=
id
??
const
Uuid
().
v4
(),
createdAt
=
createdAt
??
DateTime
.
now
();
createdAt
=
createdAt
??
DateTime
.
now
();
...
@@ -41,6 +45,8 @@ class DeliveryAddress {
...
@@ -41,6 +45,8 @@ class DeliveryAddress {
'longitude'
:
longitude
,
'longitude'
:
longitude
,
'notes'
:
notes
,
'notes'
:
notes
,
'createdAt'
:
createdAt
.
toIso8601String
(),
'createdAt'
:
createdAt
.
toIso8601String
(),
'driverId'
:
driverId
,
'status'
:
status
,
};
};
factory
DeliveryAddress
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
DeliveryAddress
(
factory
DeliveryAddress
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
DeliveryAddress
(
...
@@ -54,6 +60,8 @@ class DeliveryAddress {
...
@@ -54,6 +60,8 @@ class DeliveryAddress {
longitude:
json
[
'longitude'
]?.
toDouble
(),
longitude:
json
[
'longitude'
]?.
toDouble
(),
notes:
json
[
'notes'
],
notes:
json
[
'notes'
],
createdAt:
DateTime
.
parse
(
json
[
'createdAt'
]),
createdAt:
DateTime
.
parse
(
json
[
'createdAt'
]),
driverId:
json
[
'driverId'
],
status:
json
[
'status'
]
??
'pending'
,
);
);
DeliveryAddress
copyWith
({
DeliveryAddress
copyWith
({
...
@@ -65,6 +73,8 @@ class DeliveryAddress {
...
@@ -65,6 +73,8 @@ class DeliveryAddress {
double
?
latitude
,
double
?
latitude
,
double
?
longitude
,
double
?
longitude
,
String
?
notes
,
String
?
notes
,
String
?
driverId
,
String
?
status
,
})
=>
DeliveryAddress
(
})
=>
DeliveryAddress
(
id:
id
,
id:
id
,
userId:
userId
??
this
.
userId
,
userId:
userId
??
this
.
userId
,
...
@@ -76,5 +86,7 @@ class DeliveryAddress {
...
@@ -76,5 +86,7 @@ class DeliveryAddress {
longitude:
longitude
??
this
.
longitude
,
longitude:
longitude
??
this
.
longitude
,
notes:
notes
??
this
.
notes
,
notes:
notes
??
this
.
notes
,
createdAt:
createdAt
,
createdAt:
createdAt
,
driverId:
driverId
??
this
.
driverId
,
status:
status
??
this
.
status
,
);
);
}
}
lib/models/user_model.dart
0 → 100644
View file @
a2d2df27
import
'package:cloud_firestore/cloud_firestore.dart'
;
class
UserModel
{
final
String
uid
;
final
String
?
email
;
final
String
?
displayName
;
final
String
?
role
;
UserModel
({
required
this
.
uid
,
this
.
email
,
this
.
displayName
,
this
.
role
,
});
factory
UserModel
.
fromFirestore
(
DocumentSnapshot
doc
)
{
Map
<
String
,
dynamic
>
data
=
doc
.
data
()
as
Map
<
String
,
dynamic
>;
return
UserModel
(
uid:
doc
.
id
,
email:
data
[
'email'
],
displayName:
data
[
'displayName'
],
role:
data
[
'role'
],
);
}
}
lib/screens/admin_dashboard_screen.dart
View file @
a2d2df27
...
@@ -3,13 +3,15 @@ import 'package:csv/csv.dart';
...
@@ -3,13 +3,15 @@ import 'package:csv/csv.dart';
import
'package:file_picker/file_picker.dart'
;
import
'package:file_picker/file_picker.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/flutter_svg.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/delivery_address.dart'
;
import
'../
services/google_auth_service
.dart'
;
import
'../
models/user_model
.dart'
;
import
'../services/firestore_service.dart'
;
import
'../services/firestore_service.dart'
;
import
'../widgets/address_list.dart'
;
import
'../widgets/address_list.dart'
;
import
'../widgets/add_edit_address_dialog.dart'
;
import
'../widgets/add_edit_address_dialog.dart'
;
import
'../widgets/assign_drivers_dialog.dart'
;
import
'../widgets/drivers_list.dart'
;
import
'../widgets/users_list.dart'
;
class
AdminDashboardScreen
extends
StatefulWidget
{
class
AdminDashboardScreen
extends
StatefulWidget
{
const
AdminDashboardScreen
({
super
.
key
});
const
AdminDashboardScreen
({
super
.
key
});
...
@@ -20,10 +22,7 @@ class AdminDashboardScreen extends StatefulWidget {
...
@@ -20,10 +22,7 @@ class AdminDashboardScreen extends StatefulWidget {
class
_AdminDashboardScreenState
extends
State
<
AdminDashboardScreen
>
{
class
_AdminDashboardScreenState
extends
State
<
AdminDashboardScreen
>
{
final
FirestoreService
_firestoreService
=
FirestoreService
();
final
FirestoreService
_firestoreService
=
FirestoreService
();
final
TextEditingController
_emailController
=
TextEditingController
();
Set
<
String
>
_selectedAddressIds
=
{};
final
TextEditingController
_passwordController
=
TextEditingController
();
final
_formKey
=
GlobalKey
<
FormState
>();
bool
_isLoading
=
false
;
User
?
_user
;
User
?
_user
;
@override
@override
...
@@ -35,10 +34,20 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -35,10 +34,20 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
setState
(()
{
setState
(()
{
_user
=
user
;
_user
=
user
;
});
});
if
(
user
==
null
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/login'
);
}
}
}
});
});
}
}
void
_onSelectionChanged
(
Set
<
String
>
selectedIds
)
{
setState
(()
{
_selectedAddressIds
=
selectedIds
;
});
}
void
_showAddEditAddressDialog
({
DeliveryAddress
?
address
})
{
void
_showAddEditAddressDialog
({
DeliveryAddress
?
address
})
{
if
(
_user
==
null
)
return
;
if
(
_user
==
null
)
return
;
showDialog
(
showDialog
(
...
@@ -72,10 +81,11 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -72,10 +81,11 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
final
list
=
const
CsvToListConverter
().
convert
(
content
);
final
list
=
const
CsvToListConverter
().
convert
(
content
);
if
(
list
.
isNotEmpty
)
{
if
(
list
.
isNotEmpty
)
{
list
.
removeAt
(
0
);
// remove header
list
.
removeAt
(
0
);
}
}
final
addresses
=
list
.
map
((
row
)
{
final
addresses
=
list
.
map
((
row
)
{
try
{
try
{
return
DeliveryAddress
(
return
DeliveryAddress
(
userId:
_user
!.
uid
,
userId:
_user
!.
uid
,
...
@@ -89,9 +99,12 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -89,9 +99,12 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
print
(
'Error parsing row:
$row
, error:
$e
'
);
print
(
'Error parsing row:
$row
, error:
$e
'
);
return
null
;
return
null
;
}
}
}).
where
((
address
)
=>
address
!=
null
).
cast
<
DeliveryAddress
>().
toList
();
})
.
where
((
address
)
=>
address
!=
null
)
.
cast
<
DeliveryAddress
>()
.
toList
();
if
(!
mounted
)
return
;
// Check if the widget is still in the tree
if
(!
mounted
)
return
;
if
(
addresses
.
isNotEmpty
)
{
if
(
addresses
.
isNotEmpty
)
{
try
{
try
{
...
@@ -126,49 +139,48 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -126,49 +139,48 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
void
_deleteAddress
(
String
addressId
)
{
void
_deleteAddress
(
String
addressId
)
{
_firestoreService
.
deleteAddress
(
addressId
);
_firestoreService
.
deleteAddress
(
addressId
);
setState
(()
{
_selectedAddressIds
.
remove
(
addressId
);
});
}
}
Future
<
void
>
_loginWithEmail
()
async
{
void
_reassignAddress
(
String
addressId
)
{
if
(!
_formKey
.
currentState
!.
validate
())
return
;
_firestoreService
.
reassignAddress
(
addressId
);
setState
(()
=>
_isLoading
=
true
);
try
{
await
FirebaseAuth
.
instance
.
signInWithEmailAndPassword
(
email:
_emailController
.
text
.
trim
(),
password:
_passwordController
.
text
.
trim
(),
);
}
on
FirebaseAuthException
catch
(
e
)
{
if
(!
mounted
)
return
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Login Failed:
${e.message}
"
)),
);
}
finally
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
}
}
Future
<
void
>
_loginWithGoogle
()
async
{
void
_logout
()
async
{
setState
(()
=>
_isLoading
=
true
);
await
FirebaseAuth
.
instance
.
signOut
();
try
{
await
GoogleAuthService
.
signInWithGoogle
();
}
catch
(
e
)
{
if
(
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Google Login Failed:
${e.toString()}
"
)),
);
}
}
finally
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
void
_assignDriverRole
(
String
uid
)
{
_firestoreService
.
assignDriverRole
(
uid
);
}
}
void
_removeDriverRole
(
String
uid
)
{
_firestoreService
.
removeDriverRole
(
uid
);
}
}
void
_logout
()
async
{
void
_showAssignDriversDialog
()
async
{
await
FirebaseAuth
.
instance
.
signOut
();
if
(
_user
==
null
||
_selectedAddressIds
.
isEmpty
)
return
;
final
drivers
=
await
_firestoreService
.
getDrivers
().
first
;
if
(!
mounted
)
return
;
showDialog
(
context:
context
,
builder:
(
context
)
=>
AssignDriversDialog
(
drivers:
drivers
,
onAssign:
(
selectedDriverIds
)
{
_firestoreService
.
assignAddressesToDrivers
(
_selectedAddressIds
.
toList
(),
selectedDriverIds
,
);
setState
(()
{
_selectedAddressIds
.
clear
();
});
},
),
);
}
}
@override
@override
...
@@ -188,15 +200,13 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -188,15 +200,13 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
child:
Center
(
child:
Text
(
_user
!.
email
??
''
,
style:
const
TextStyle
(
color:
Colors
.
white
))),
child:
Center
(
child:
Text
(
_user
!.
email
??
''
,
style:
const
TextStyle
(
color:
Colors
.
white
))),
),
),
if
(
_user
!=
null
)
if
(
_user
!=
null
)
TextButton
.
ic
on
(
IconButt
on
(
onPressed:
_logout
,
onPressed:
_logout
,
icon:
const
Icon
(
Icons
.
logout
,
color:
Colors
.
white
,
size:
18
),
icon:
const
Icon
(
Icons
.
logout
,
color:
Colors
.
white
,
size:
18
),
label:
const
Text
(
'Logout'
,
style:
TextStyle
(
color:
Colors
.
white
)),
style:
TextButton
.
styleFrom
(
foregroundColor:
Colors
.
white
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
)),
),
),
],
],
),
),
body:
_user
==
null
?
_buildLoginForm
(
context
)
:
_buildLoggedInView
(
context
),
body:
_user
==
null
?
const
Center
(
child:
CircularProgressIndicator
()
)
:
_buildLoggedInView
(
context
),
);
);
}
}
...
@@ -206,26 +216,52 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -206,26 +216,52 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
}
}
return
Padding
(
return
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Row
(
children:
[
Expanded
(
flex:
3
,
// 75% of the space
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
Row
(
Wrap
(
spacing:
16.0
,
runSpacing:
8.0
,
children:
[
children:
[
ElevatedButton
.
icon
(
ElevatedButton
.
icon
(
onPressed:
()
=>
_showAddEditAddressDialog
(),
onPressed:
()
=>
_showAddEditAddressDialog
(),
icon:
const
Icon
(
Icons
.
add
),
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'Add Address'
),
label:
const
Text
(
'Add Address'
),
style:
ElevatedButton
.
styleFrom
(
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
)
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
),
),
),
),
),
const
SizedBox
(
width:
16
),
OutlinedButton
.
icon
(
OutlinedButton
.
icon
(
onPressed:
_showUploadCsvDialog
,
onPressed:
_showUploadCsvDialog
,
icon:
const
Icon
(
Icons
.
upload_file
),
icon:
const
Icon
(
Icons
.
upload_file
),
label:
const
Text
(
'Upload CSV'
),
label:
const
Text
(
'Upload CSV'
),
style:
OutlinedButton
.
styleFrom
(
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
)
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
),
),
),
ElevatedButton
.
icon
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pushNamed
(
'/assigned-addresses'
),
icon:
const
Icon
(
Icons
.
assignment_turned_in
),
label:
const
Text
(
'View Assigned Addresses'
),
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
),
backgroundColor:
Colors
.
indigo
,
foregroundColor:
Colors
.
white
,
),
),
if
(
_selectedAddressIds
.
isNotEmpty
)
ElevatedButton
.
icon
(
onPressed:
_showAssignDriversDialog
,
icon:
const
Icon
(
Icons
.
assignment_ind
),
label:
Text
(
'Assign Selected (
${_selectedAddressIds.length}
)'
),
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
green
,
foregroundColor:
Colors
.
white
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
),
),
),
),
),
],
],
...
@@ -239,147 +275,40 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
...
@@ -239,147 +275,40 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
child:
AddressList
(
child:
AddressList
(
onEdit:
(
address
)
=>
_showAddEditAddressDialog
(
address:
address
),
onEdit:
(
address
)
=>
_showAddEditAddressDialog
(
address:
address
),
onDelete:
_deleteAddress
,
onDelete:
_deleteAddress
,
onReassign:
_reassignAddress
,
addressesStream:
_firestoreService
.
getAddresses
(
_user
!.
uid
),
addressesStream:
_firestoreService
.
getAddresses
(
_user
!.
uid
),
),
onSelectionChanged:
_onSelectionChanged
,
),
const
SizedBox
(
height:
16
),
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
()
{},
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
20
),
backgroundColor:
Colors
.
green
,
foregroundColor:
Colors
.
white
),
child:
const
Text
(
'Release to Drivers'
),
),
),
),
),
],
],
),
),
);
}
Widget
_buildLoginForm
(
BuildContext
context
)
{
final
ThemeData
currentTheme
=
Theme
.
of
(
context
);
final
bool
darkMode
=
currentTheme
.
brightness
==
Brightness
.
dark
;
final
Color
welcomeTextColor
=
darkMode
?
Colors
.
white
:
Colors
.
black87
;
final
Color
sloganTextColor
=
darkMode
?
Colors
.
grey
[
300
]!
:
Colors
.
black54
;
final
Color
iconColor
=
darkMode
?
Colors
.
white
:
const
Color
(
0xFF0D2B0D
);
return
Center
(
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
32.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
Icon
(
Icons
.
account_tree
,
size:
100
,
color:
iconColor
,
),
const
SizedBox
(
height:
20
),
Text
(
'Welcome to GraphGo'
,
style:
currentTheme
.
textTheme
.
headlineMedium
?.
copyWith
(
fontSize:
(
currentTheme
.
textTheme
.
headlineMedium
?.
fontSize
??
28
)
*
1.15
,
fontWeight:
FontWeight
.
bold
,
color:
welcomeTextColor
,
),
),
const
SizedBox
(
height:
10
),
Text
(
'Admin Panel Access'
,
style:
currentTheme
.
textTheme
.
bodyLarge
?.
copyWith
(
fontSize:
(
currentTheme
.
textTheme
.
bodyLarge
?.
fontSize
??
16
)
*
1.1
,
color:
sloganTextColor
,
),
textAlign:
TextAlign
.
center
,
),
),
const
SizedBox
(
height:
50
),
const
VerticalDivider
(
width:
32
),
ConstrainedBox
(
Expanded
(
constraints:
const
BoxConstraints
(
maxWidth:
400
),
flex:
1
,
// 25% of the space
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
SizedBox
(
Text
(
'Users'
,
style:
Theme
.
of
(
context
).
textTheme
.
headlineSmall
),
width:
double
.
infinity
,
Expanded
(
child:
OutlinedButton
.
icon
(
child:
UsersList
(
onPressed:
_isLoading
?
null
:
_loginWithGoogle
,
usersStream:
_firestoreService
.
getUsers
(),
icon:
SvgPicture
.
asset
(
'assets/icons/google_icon.svg'
,
width:
20
,
height:
20
),
onAssignDriver:
_assignDriverRole
,
label:
const
Text
(
'Sign in with Google'
),
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
12
),
),
),
),
const
SizedBox
(
height:
20
),
const
Row
(
children:
[
Expanded
(
child:
Divider
()),
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'OR'
),
),
Expanded
(
child:
Divider
()),
],
),
),
const
SizedBox
(
height:
20
),
Form
(
key:
_formKey
,
child:
Column
(
children:
[
TextFormField
(
controller:
_emailController
,
decoration:
const
InputDecoration
(
labelText:
'Email'
,
border:
OutlineInputBorder
()),
keyboardType:
TextInputType
.
emailAddress
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
null
,
),
),
const
SizedBox
(
height:
16
),
const
SizedBox
(
height:
16
),
TextFormField
(
Text
(
'Drivers'
,
style:
Theme
.
of
(
context
).
textTheme
.
headlineSmall
),
controller:
_passwordController
,
Expanded
(
decoration:
const
InputDecoration
(
labelText:
'Password'
,
border:
OutlineInputBorder
()),
child:
DriversList
(
obscureText:
true
,
driversStream:
_firestoreService
.
getDrivers
(),
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your password"
:
null
,
onRemoveDriver:
_removeDriverRole
,
),
Align
(
alignment:
Alignment
.
centerRight
,
child:
TextButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/forgot'
);
},
child:
const
Text
(
'Forgot Password?'
),
),
),
const
SizedBox
(
height:
10
),
_isLoading
?
const
CircularProgressIndicator
()
:
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
_loginWithEmail
,
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
20
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
'Login with Email'
),
),
),
],
),
),
),
const
SizedBox
(
height:
20
),
TextButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/signup'
);
},
child:
const
Text
(
"Don't have an account? Sign Up"
),
),
),
],
],
),
),
),
),
],
],
),
),
),
);
);
}
}
}
}
lib/screens/assigned_addresses_screen.dart
0 → 100644
View file @
a2d2df27
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:flutter/material.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/user_model.dart'
;
import
'../services/firestore_service.dart'
;
class
AssignedAddressesScreen
extends
StatelessWidget
{
final
FirestoreService
_firestoreService
=
FirestoreService
();
AssignedAddressesScreen
({
super
.
key
});
Future
<
UserModel
?>
_getDriver
(
String
driverId
)
async
{
return
await
_firestoreService
.
getUserById
(
driverId
);
}
@override
Widget
build
(
BuildContext
context
)
{
final
user
=
FirebaseAuth
.
instance
.
currentUser
;
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'Assigned Addresses'
),
),
body:
user
==
null
?
const
Center
(
child:
Text
(
'Please log in to see your assigned addresses.'
))
:
StreamBuilder
<
List
<
DeliveryAddress
>>(
stream:
_firestoreService
.
getAssignedAddresses
(
user
.
uid
),
builder:
(
context
,
snapshot
)
{
if
(
snapshot
.
connectionState
==
ConnectionState
.
waiting
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
snapshot
.
hasError
)
{
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
}
if
(!
snapshot
.
hasData
||
snapshot
.
data
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
'No addresses have been assigned yet.'
));
}
final
assignedAddresses
=
snapshot
.
data
!;
return
ListView
.
builder
(
itemCount:
assignedAddresses
.
length
,
itemBuilder:
(
context
,
index
)
{
final
address
=
assignedAddresses
[
index
];
if
(
address
.
driverId
==
null
)
{
return
Card
(
margin:
const
EdgeInsets
.
all
(
8.0
),
child:
ListTile
(
title:
Text
(
address
.
fullAddress
),
subtitle:
const
Text
(
'Error: Driver ID is missing.'
),
),
);
}
return
Card
(
margin:
const
EdgeInsets
.
all
(
8.0
),
child:
ListTile
(
title:
Text
(
address
.
fullAddress
),
subtitle:
FutureBuilder
<
UserModel
?>(
future:
_getDriver
(
address
.
driverId
!),
builder:
(
context
,
driverSnapshot
)
{
if
(
driverSnapshot
.
connectionState
==
ConnectionState
.
waiting
)
{
return
const
Text
(
'Loading driver...'
);
}
if
(
driverSnapshot
.
hasError
||
driverSnapshot
.
data
==
null
)
{
return
const
Text
(
'Driver not found'
);
}
final
driver
=
driverSnapshot
.
data
!;
final
capitalizedStatus
=
address
.
status
.
isEmpty
?
''
:
'
${address.status[0].toUpperCase()}${address.status.substring(1)}
'
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
'Assigned to:
${driver.displayName ?? driver.email ?? 'Unknown Driver'}
'
),
Text
(
'Status:
$capitalizedStatus
'
),
],
);
},
),
),
);
},
);
},
),
);
}
}
lib/screens/driver_assignments_screen.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../models/delivery_address.dart'
;
import
'../services/firestore_service.dart'
;
class
DriverAssignmentsScreen
extends
StatefulWidget
{
const
DriverAssignmentsScreen
({
super
.
key
});
@override
State
<
DriverAssignmentsScreen
>
createState
()
=>
_DriverAssignmentsScreenState
();
}
class
_DriverAssignmentsScreenState
extends
State
<
DriverAssignmentsScreen
>
{
final
FirestoreService
_firestoreService
=
FirestoreService
();
final
User
?
currentUser
=
FirebaseAuth
.
instance
.
currentUser
;
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'My Assignments'
),
),
body:
currentUser
==
null
?
const
Center
(
child:
Text
(
'Please log in to see your assignments.'
))
:
StreamBuilder
<
List
<
DeliveryAddress
>>(
stream:
_firestoreService
.
getDriverDeliveries
(
currentUser
!.
uid
),
builder:
(
context
,
snapshot
)
{
if
(
snapshot
.
connectionState
==
ConnectionState
.
waiting
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
snapshot
.
hasError
)
{
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
}
if
(!
snapshot
.
hasData
||
snapshot
.
data
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
'No Assignments Yet'
));
}
final
addresses
=
snapshot
.
data
!;
return
ListView
.
builder
(
itemCount:
addresses
.
length
,
itemBuilder:
(
context
,
index
)
{
final
address
=
addresses
[
index
];
final
capitalizedStatus
=
address
.
status
.
isEmpty
?
''
:
'
${address.status[0].toUpperCase()}${address.status.substring(1)}
'
;
return
Card
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
8.0
,
horizontal:
16.0
),
child:
ListTile
(
title:
Text
(
address
.
fullAddress
),
subtitle:
Text
(
'Status:
$capitalizedStatus
'
),
trailing:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
ElevatedButton
(
onPressed:
()
{
_firestoreService
.
updateDeliveryStatus
(
address
.
id
,
'accepted'
);
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
deepPurple
,
foregroundColor:
Colors
.
white
,
),
child:
const
Text
(
'Accept'
),
),
const
SizedBox
(
width:
8
),
ElevatedButton
(
onPressed:
()
{
_firestoreService
.
denyAssignment
(
address
.
id
);
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
grey
.
shade700
,
foregroundColor:
Colors
.
white
,
),
child:
const
Text
(
'Deny'
),
),
],
),
),
);
},
);
},
),
);
}
}
lib/screens/login.dart
View file @
a2d2df27
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:flutter_svg/flutter_svg.dart'
;
import
'package:provider/provider.dart'
;
import
'package:provider/provider.dart'
;
import
'../providers/settings_provider.dart'
;
import
'../providers/settings_provider.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../colors.dart'
;
class
LoginPage
extends
StatefulWidget
{
class
LoginPage
extends
StatefulWidget
{
const
LoginPage
({
super
.
key
});
const
LoginPage
({
super
.
key
});
...
@@ -18,50 +19,53 @@ class _LoginPageState extends State<LoginPage> {
...
@@ -18,50 +19,53 @@ class _LoginPageState extends State<LoginPage> {
final
TextEditingController
_passwordController
=
TextEditingController
();
final
TextEditingController
_passwordController
=
TextEditingController
();
final
_formKey
=
GlobalKey
<
FormState
>();
final
_formKey
=
GlobalKey
<
FormState
>();
bool
_isLoading
=
false
;
bool
_isLoading
=
false
;
bool
_rememberMe
=
false
;
String
_selectedRole
=
'Admin'
;
// Default role
void
_navigateBasedOnRole
(
String
role
)
{
if
(!
mounted
)
return
;
switch
(
role
)
{
case
'Admin'
:
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/admin-dashboard'
);
break
;
case
'Driver'
:
default
:
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
break
;
}
}
Future
<
void
>
_login
()
async
{
Future
<
void
>
_login
()
async
{
if
(!
_formKey
.
currentState
!.
validate
())
return
;
if
(!
_formKey
.
currentState
!.
validate
())
return
;
setState
(()
=>
_isLoading
=
true
);
setState
(()
=>
_isLoading
=
true
);
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
,
listen:
false
);
try
{
try
{
await
FirebaseAuth
.
instance
.
signInWithEmailAndPassword
(
await
FirebaseAuth
.
instance
.
signInWithEmailAndPassword
(
email:
_emailController
.
text
.
trim
(),
email:
_emailController
.
text
.
trim
(),
password:
_passwordController
.
text
.
trim
(),
password:
_passwordController
.
text
.
trim
(),
);
);
_navigateBasedOnRole
(
_selectedRole
);
if
(
_rememberMe
)
{
}
on
FirebaseAuthException
catch
(
e
)
{
settingsProvider
.
login
();
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
}
SnackBar
(
content:
Text
(
"Login Failed:
${e.message}
"
)),
);
if
(
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
catch
(
e
)
{
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Login Failed:
${e.toString()}
"
)),
SnackBar
(
content:
Text
(
"Login Failed:
${e.toString()}
"
)),
);
);
}
finally
{
}
finally
{
if
(
mounted
)
{
if
(
mounted
)
setState
(()
=>
_isLoading
=
false
);
setState
(()
=>
_isLoading
=
false
);
}
}
}
}
}
Future
<
void
>
_loginWithGoogle
()
async
{
Future
<
void
>
_loginWithGoogle
()
async
{
setState
(()
=>
_isLoading
=
true
);
setState
(()
=>
_isLoading
=
true
);
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
,
listen:
false
);
try
{
try
{
final
UserCredential
?
userCredential
=
await
GoogleAuthService
.
signInWithGoogle
();
final
UserCredential
?
userCredential
=
await
GoogleAuthService
.
signInWithGoogle
();
if
(
FirebaseAuth
.
instance
.
currentUser
!=
null
)
{
if
(
FirebaseAuth
.
instance
.
currentUser
!=
null
)
{
settingsProvider
.
login
();
_navigateBasedOnRole
(
_selectedRole
);
if
(
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
else
if
(
userCredential
==
null
&&
mounted
)
{
}
else
if
(
userCredential
==
null
&&
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Google Sign-In was cancelled or failed."
)),
const
SnackBar
(
content:
Text
(
"Google Sign-In was cancelled or failed."
)),
...
@@ -74,100 +78,157 @@ class _LoginPageState extends State<LoginPage> {
...
@@ -74,100 +78,157 @@ class _LoginPageState extends State<LoginPage> {
);
);
}
}
}
finally
{
}
finally
{
if
(
mounted
)
{
if
(
mounted
)
setState
(()
=>
_isLoading
=
false
);
setState
(()
=>
_isLoading
=
false
);
}
}
}
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
ThemeData
currentTheme
=
Theme
.
of
(
context
);
final
bool
darkMode
=
currentTheme
.
brightness
==
Brightness
.
dark
;
final
Color
welcomeTextColor
=
darkMode
?
Colors
.
white
:
Colors
.
black87
;
final
Color
sloganTextColor
=
darkMode
?
Colors
.
grey
[
300
]!
:
Colors
.
black54
;
final
Color
iconColor
=
darkMode
?
Colors
.
white
:
const
Color
(
0xFF0D2B0D
);
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
appBar:
AppBar
(
title:
const
Text
(
'GraphGo Login'
),
leading:
IconButton
(
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
icon:
const
Icon
(
Icons
.
settings
,
color:
Colors
.
white
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
onPressed:
()
=>
Navigator
.
of
(
context
).
pushNamed
(
'/settings'
),
tooltip:
'Back to Home'
,
),
),
title:
const
Text
(
'GraphGo'
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
)),
centerTitle:
true
,
),
),
body:
SingleChildScrollView
(
body:
Center
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
SingleChildScrollView
(
child:
Form
(
padding:
const
EdgeInsets
.
all
(
32.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
Icon
(
Icons
.
account_tree
,
size:
100
,
color:
iconColor
,
),
const
SizedBox
(
height:
20
),
Text
(
'Welcome to GraphGo'
,
style:
currentTheme
.
textTheme
.
headlineMedium
?.
copyWith
(
fontSize:
(
currentTheme
.
textTheme
.
headlineMedium
?.
fontSize
??
28
)
*
1.15
,
fontWeight:
FontWeight
.
bold
,
color:
welcomeTextColor
,
),
),
const
SizedBox
(
height:
10
),
Text
(
'Log in as Admin or Driver to start exploring'
,
style:
currentTheme
.
textTheme
.
bodyLarge
?.
copyWith
(
fontSize:
16
,
color:
sloganTextColor
,
),
textAlign:
TextAlign
.
center
,
),
const
SizedBox
(
height:
50
),
ConstrainedBox
(
constraints:
const
BoxConstraints
(
maxWidth:
400
),
child:
Column
(
children:
[
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
.
icon
(
onPressed:
_isLoading
?
null
:
_loginWithGoogle
,
icon:
SvgPicture
.
asset
(
'assets/icons/google_icon.svg'
,
width:
20
,
height:
20
),
label:
const
Text
(
'Sign in with Google'
),
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
12
),
),
),
),
const
SizedBox
(
height:
20
),
const
Row
(
children:
[
Expanded
(
child:
Divider
()),
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'OR'
),
),
Expanded
(
child:
Divider
()),
],
),
const
SizedBox
(
height:
20
),
Form
(
key:
_formKey
,
key:
_formKey
,
child:
Column
(
child:
Column
(
children:
[
children:
[
DropdownButtonFormField
<
String
>(
value:
_selectedRole
,
decoration:
const
InputDecoration
(
labelText:
'Role'
,
border:
OutlineInputBorder
(),
),
items:
[
'Admin'
,
'Driver'
]
.
map
<
DropdownMenuItem
<
String
>>((
String
value
)
{
return
DropdownMenuItem
<
String
>(
value:
value
,
child:
Text
(
value
),
);
}).
toList
(),
onChanged:
(
String
?
newValue
)
{
if
(
newValue
!=
null
)
{
setState
(()
=>
_selectedRole
=
newValue
);
}
},
),
const
SizedBox
(
height:
16
),
TextFormField
(
TextFormField
(
controller:
_emailController
,
controller:
_emailController
,
decoration:
const
InputDecoration
(
labelText:
'Email'
),
decoration:
const
InputDecoration
(
labelText:
'Email'
,
border:
OutlineInputBorder
()
),
keyboardType:
TextInputType
.
emailAddress
,
keyboardType:
TextInputType
.
emailAddress
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
null
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
null
,
),
),
const
SizedBox
(
height:
16
),
TextFormField
(
TextFormField
(
controller:
_passwordController
,
controller:
_passwordController
,
decoration:
const
InputDecoration
(
labelText:
'Password'
),
decoration:
const
InputDecoration
(
labelText:
'Password'
,
border:
OutlineInputBorder
()
),
obscureText:
true
,
obscureText:
true
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your password"
:
null
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your password"
:
null
,
),
),
Align
(
Align
(
alignment:
Alignment
.
centerLeft
,
alignment:
Alignment
.
centerRight
,
child:
Row
(
child:
TextButton
(
children:
[
Checkbox
(
value:
_rememberMe
,
onChanged:
(
val
)
{
setState
(()
=>
_rememberMe
=
val
??
false
);
},
),
const
Text
(
"Remember Me"
),
],
),
),
TextButton
(
onPressed:
()
{
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/forgot'
);
Navigator
.
of
(
context
).
pushNamed
(
'/forgot'
);
},
},
child:
const
Text
(
"Forgot Password?"
),
child:
const
Text
(
'Forgot Password?'
),
),
const
SizedBox
(
height:
20
),
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
.
icon
(
onPressed:
_isLoading
?
null
:
_loginWithGoogle
,
icon:
const
Icon
(
Icons
.
login
,
size:
20
,
color:
Colors
.
blue
),
label:
const
Text
(
'Sign in with Google'
),
),
),
),
const
SizedBox
(
height:
16
),
Row
(
children:
[
const
Expanded
(
child:
Divider
()),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'OR'
),
),
),
const
Expanded
(
child:
Divider
()),
const
SizedBox
(
height:
10
),
],
),
const
SizedBox
(
height:
16
),
_isLoading
_isLoading
?
const
CircularProgressIndicator
()
?
const
CircularProgressIndicator
()
:
SizedBox
(
:
SizedBox
(
width:
double
.
infinity
,
width:
double
.
infinity
,
child:
ElevatedButton
(
child:
ElevatedButton
(
onPressed:
_login
,
onPressed:
_login
,
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
20
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
'Login with Email'
),
child:
const
Text
(
'Login with Email'
),
),
),
),
),
],
),
),
const
SizedBox
(
height:
20
),
TextButton
(
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pushNamed
(
'/signup'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/signup'
);
},
child:
const
Text
(
"Don't have an account? Sign Up"
),
child:
const
Text
(
"Don't have an account? Sign Up"
),
),
),
],
// The debug override button has been removed.
),
),
],
],
),
),
),
),
...
...
lib/screens/map_screen.dart
View file @
a2d2df27
...
@@ -3,6 +3,11 @@ import 'package:flutter/material.dart';
...
@@ -3,6 +3,11 @@ import 'package:flutter/material.dart';
import
'package:google_maps_flutter/google_maps_flutter.dart'
;
import
'package:google_maps_flutter/google_maps_flutter.dart'
;
import
'package:location/location.dart'
;
import
'package:location/location.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:geocoding/geocoding.dart'
as
geocoding
;
import
'../models/delivery_address.dart'
;
import
'../services/firestore_service.dart'
;
class
MapScreen
extends
StatefulWidget
{
class
MapScreen
extends
StatefulWidget
{
const
MapScreen
({
super
.
key
});
const
MapScreen
({
super
.
key
});
...
@@ -13,8 +18,10 @@ class MapScreen extends StatefulWidget {
...
@@ -13,8 +18,10 @@ class MapScreen extends StatefulWidget {
class
_MapScreenState
extends
State
<
MapScreen
>
{
class
_MapScreenState
extends
State
<
MapScreen
>
{
final
Completer
<
GoogleMapController
>
_controller
=
Completer
();
final
Completer
<
GoogleMapController
>
_controller
=
Completer
();
final
FirestoreService
_firestoreService
=
FirestoreService
();
LocationData
?
_currentLocation
;
LocationData
?
_currentLocation
;
StreamSubscription
<
LocationData
>?
_locationSubscription
;
StreamSubscription
<
LocationData
>?
_locationSubscription
;
Set
<
Marker
>
_markers
=
{};
final
User
?
user
=
FirebaseAuth
.
instance
.
currentUser
;
final
User
?
user
=
FirebaseAuth
.
instance
.
currentUser
;
...
@@ -26,29 +33,28 @@ class _MapScreenState extends State<MapScreen> {
...
@@ -26,29 +33,28 @@ class _MapScreenState extends State<MapScreen> {
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
_initializeLocation
();
_initializeLocationAndMarkers
();
}
Future
<
void
>
_initializeLocationAndMarkers
()
async
{
await
_initializeLocation
();
if
(
user
!=
null
)
{
_loadAddressMarkers
(
user
!.
uid
);
}
}
}
Future
<
void
>
_initializeLocation
()
async
{
Future
<
void
>
_initializeLocation
()
async
{
Location
location
=
Location
();
Location
location
=
Location
();
bool
serviceEnabled
=
await
location
.
serviceEnabled
();
bool
serviceEnabled
;
PermissionStatus
permissionGranted
;
serviceEnabled
=
await
location
.
serviceEnabled
();
if
(!
serviceEnabled
)
{
if
(!
serviceEnabled
)
{
serviceEnabled
=
await
location
.
requestService
();
serviceEnabled
=
await
location
.
requestService
();
if
(!
serviceEnabled
)
{
if
(!
serviceEnabled
)
return
;
return
;
}
}
}
permissionGranted
=
await
location
.
hasPermission
();
PermissionStatus
permissionGranted
=
await
location
.
hasPermission
();
if
(
permissionGranted
==
PermissionStatus
.
denied
)
{
if
(
permissionGranted
==
PermissionStatus
.
denied
)
{
permissionGranted
=
await
location
.
requestPermission
();
permissionGranted
=
await
location
.
requestPermission
();
if
(
permissionGranted
!=
PermissionStatus
.
granted
)
{
if
(
permissionGranted
!=
PermissionStatus
.
granted
)
return
;
return
;
}
}
}
_currentLocation
=
await
location
.
getLocation
();
_currentLocation
=
await
location
.
getLocation
();
...
@@ -57,15 +63,47 @@ class _MapScreenState extends State<MapScreen> {
...
@@ -57,15 +63,47 @@ class _MapScreenState extends State<MapScreen> {
}
}
_locationSubscription
=
location
.
onLocationChanged
.
listen
((
LocationData
newLocation
)
{
_locationSubscription
=
location
.
onLocationChanged
.
listen
((
LocationData
newLocation
)
{
if
(
mounted
)
{
if
(
mounted
)
{
setState
(()
{
setState
(()
=>
_currentLocation
=
newLocation
);
_currentLocation
=
newLocation
;
});
_moveCameraToLocation
(
newLocation
);
_moveCameraToLocation
(
newLocation
);
}
}
});
});
}
}
Future
<
void
>
_loadAddressMarkers
(
String
userId
)
async
{
_firestoreService
.
getDriverDeliveries
(
userId
).
listen
((
addresses
)
async
{
Set
<
Marker
>
newMarkers
=
{};
for
(
var
address
in
addresses
)
{
// Check for null or empty required fields before geocoding
if
(
address
.
streetAddress
.
isNotEmpty
&&
address
.
city
.
isNotEmpty
&&
address
.
state
.
isNotEmpty
&&
address
.
zipCode
.
isNotEmpty
)
{
try
{
List
<
geocoding
.
Location
>
locations
=
await
geocoding
.
locationFromAddress
(
'
${address.streetAddress}
,
${address.city}
,
${address.state}
${address.zipCode}
'
);
if
(
locations
.
isNotEmpty
)
{
final
loc
=
locations
.
first
;
newMarkers
.
add
(
Marker
(
markerId:
MarkerId
(
address
.
id
),
position:
LatLng
(
loc
.
latitude
,
loc
.
longitude
),
infoWindow:
InfoWindow
(
title:
address
.
streetAddress
,
snippet:
address
.
notes
),
),
);
}
}
catch
(
e
)
{
print
(
"Error geocoding address:
${e}
"
);
}
}
}
if
(
mounted
)
{
setState
(()
=>
_markers
=
newMarkers
);
}
});
}
Future
<
void
>
_moveCameraToLocation
(
LocationData
locationData
)
async
{
Future
<
void
>
_moveCameraToLocation
(
LocationData
locationData
)
async
{
final
GoogleMapController
controller
=
await
_controller
.
future
;
final
GoogleMapController
controller
=
await
_controller
.
future
;
controller
.
animateCamera
(
CameraUpdate
.
newCameraPosition
(
controller
.
animateCamera
(
CameraUpdate
.
newCameraPosition
(
...
@@ -76,6 +114,13 @@ class _MapScreenState extends State<MapScreen> {
...
@@ -76,6 +114,13 @@ class _MapScreenState extends State<MapScreen> {
));
));
}
}
Future
<
void
>
_logout
()
async
{
await
FirebaseAuth
.
instance
.
signOut
();
if
(
mounted
)
{
Navigator
.
of
(
context
).
pushNamedAndRemoveUntil
(
'/'
,
(
Route
<
dynamic
>
route
)
=>
false
);
}
}
@override
@override
void
dispose
()
{
void
dispose
()
{
_locationSubscription
?.
cancel
();
_locationSubscription
?.
cancel
();
...
@@ -86,23 +131,24 @@ class _MapScreenState extends State<MapScreen> {
...
@@ -86,23 +131,24 @@ class _MapScreenState extends State<MapScreen> {
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
appBar:
AppBar
(
title:
const
Text
(
'Your Location'
),
title:
const
Text
(
"GraphGo Driver"
),
leading:
IconButton
(
automaticallyImplyLeading:
false
,
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
actions:
[
actions:
[
TextButton
.
icon
(
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/driver-assignments'
);
},
icon:
const
Icon
(
Icons
.
assignment
,
color:
Colors
.
white
),
label:
const
Text
(
'View Assignments'
,
style:
TextStyle
(
color:
Colors
.
white
)),
),
if
(
user
?.
email
!=
null
)
if
(
user
?.
email
!=
null
)
Padding
(
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
16.0
),
padding:
const
EdgeInsets
.
only
(
right:
16.0
),
child:
Center
(
child:
Center
(
child:
Text
(
user
!.
email
!,
style:
const
TextStyle
(
fontSize:
12
))),
child:
Text
(
user
!.
email
!,
style:
const
TextStyle
(
fontSize:
12
,
),
),
),
),
IconButton
(
icon:
const
Icon
(
Icons
.
logout
),
onPressed:
_logout
,
),
),
],
],
),
),
...
@@ -116,14 +162,8 @@ class _MapScreenState extends State<MapScreen> {
...
@@ -116,14 +162,8 @@ class _MapScreenState extends State<MapScreen> {
},
},
myLocationEnabled:
true
,
myLocationEnabled:
true
,
myLocationButtonEnabled:
true
,
myLocationButtonEnabled:
true
,
markers:
{
if
(
_currentLocation
!=
null
)
markers:
_markers
,
Marker
(
markerId:
const
MarkerId
(
'currentLocation'
),
position:
LatLng
(
_currentLocation
!.
latitude
!,
_currentLocation
!.
longitude
!),
infoWindow:
const
InfoWindow
(
title:
'My Location'
),
),
},
),
),
);
);
}
}
...
...
lib/screens/signup.dart
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../colors.dart'
;
class
SignupPage
extends
StatefulWidget
{
class
SignupPage
extends
StatefulWidget
{
const
SignupPage
({
super
.
key
});
const
SignupPage
({
super
.
key
});
...
@@ -13,19 +11,27 @@ class SignupPage extends StatefulWidget {
...
@@ -13,19 +11,27 @@ class SignupPage extends StatefulWidget {
class
_SignupPageState
extends
State
<
SignupPage
>
{
class
_SignupPageState
extends
State
<
SignupPage
>
{
final
_formKey
=
GlobalKey
<
FormState
>();
final
_formKey
=
GlobalKey
<
FormState
>();
final
TextEditingController
_firstNameController
=
TextEditingController
();
final
TextEditingController
_lastNameController
=
TextEditingController
();
final
TextEditingController
_emailController
=
TextEditingController
();
final
TextEditingController
_emailController
=
TextEditingController
();
final
TextEditingController
_passwordController
=
TextEditingController
();
final
TextEditingController
_passwordController
=
TextEditingController
();
final
TextEditingController
_confirmPasswordController
=
TextEditingController
();
bool
_isLoading
=
false
;
bool
_isLoading
=
false
;
String
?
_validateEmail
(
String
?
value
)
{
if
(
value
==
null
||
value
.
isEmpty
)
{
return
"Email is required."
;
}
final
emailRegex
=
RegExp
(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\$'
);
if
(!
emailRegex
.
hasMatch
(
value
))
{
return
"Please enter a valid email address."
;
}
return
null
;
}
String
?
_validatePassword
(
String
?
value
)
{
String
?
_validatePassword
(
String
?
value
)
{
if
(
value
==
null
||
value
.
isEmpty
)
return
"Password is required."
;
if
(
value
==
null
||
value
.
isEmpty
)
return
"Password is required."
;
if
(
value
.
length
<
12
)
return
"Password must be at least 12 characters."
;
if
(
value
.
length
<
12
)
return
"Password must be at least 12 characters."
;
if
(!
RegExp
(
r'[A-Z]'
).
hasMatch
(
value
))
return
"Must contain 1 uppercase letter."
;
if
(!
RegExp
(
r'[A-Z]'
).
hasMatch
(
value
))
return
"Must contain 1 uppercase letter."
;
if
(!
RegExp
(
r'\d'
).
hasMatch
(
value
))
return
"Must contain 1 number."
;
if
(!
RegExp
(
r'\
\
d'
).
hasMatch
(
value
))
return
"Must contain 1 number."
;
if
(!
RegExp
(
r'[!@#\
$%^&*(),.?\
":{}|<>]'
).
hasMatch
(
value
))
return
"Must contain 1 special character."
;
if
(!
RegExp
(
r'[!@#\
\\$%^&*(),.?
":{}|<>]'
).
hasMatch
(
value
))
return
"Must contain 1 special character."
;
return
null
;
return
null
;
}
}
...
@@ -41,45 +47,30 @@ class _SignupPageState extends State<SignupPage> {
...
@@ -41,45 +47,30 @@ class _SignupPageState extends State<SignupPage> {
);
);
await
FirebaseFirestore
.
instance
.
collection
(
'users'
).
doc
(
userCredential
.
user
!.
uid
).
set
({
await
FirebaseFirestore
.
instance
.
collection
(
'users'
).
doc
(
userCredential
.
user
!.
uid
).
set
({
'first_name'
:
_firstNameController
.
text
.
trim
(),
'last_name'
:
_lastNameController
.
text
.
trim
(),
'email'
:
_emailController
.
text
.
trim
(),
'email'
:
_emailController
.
text
.
trim
(),
'provider'
:
'email'
,
'provider'
:
'email'
,
'created_at'
:
Timestamp
.
now
(),
'created_at'
:
Timestamp
.
now
(),
});
});
if
(
mounted
)
{
if
(
mounted
)
{
// After signing up, go to the map screen
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
}
catch
(
e
)
{
}
on
FirebaseAuthException
catch
(
e
)
{
String
errorMessage
;
if
(
e
.
code
==
'email-already-in-use'
)
{
errorMessage
=
"This email is already registered. Please log in or use a different email."
;
}
else
{
errorMessage
=
"Signup Failed:
\
${e.message}
"
;
}
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Signup Failed:
${e.toString()}
"
)),
SnackBar
(
content:
Text
(
errorMessage
)),
);
);
}
finally
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
}
Future
<
void
>
_signUpWithGoogle
()
async
{
setState
(()
=>
_isLoading
=
true
);
try
{
await
GoogleAuthService
.
signInWithGoogle
();
final
user
=
FirebaseAuth
.
instance
.
currentUser
;
if
(
user
!=
null
&&
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
catch
(
e
)
{
}
catch
(
e
)
{
if
(
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Google Sign-Up Failed:
${e.toString()}
"
)),
SnackBar
(
content:
Text
(
"An unexpected error occurred:
\
${e.toString()}
"
)),
);
);
}
}
finally
{
}
finally
{
if
(
mounted
)
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
setState
(()
=>
_isLoading
=
false
);
}
}
}
}
...
@@ -87,95 +78,71 @@ class _SignupPageState extends State<SignupPage> {
...
@@ -87,95 +78,71 @@ class _SignupPageState extends State<SignupPage> {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
bool
isDarkMode
=
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
;
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
appBar:
AppBar
(
title:
const
Text
(
'GraphGo Sign Up'
),
title:
const
Text
(
'Create Account'
),
// The back button is now handled correctly by the Navigator
leading:
IconButton
(
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
// Corrected line
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
tooltip:
'Back to Login'
,
tooltip:
'Back to Login'
,
),
),
),
),
body:
SingleChildScrollView
(
body:
Center
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
32.0
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
maxWidth:
400
),
child:
Form
(
child:
Form
(
key:
_formKey
,
key:
_formKey
,
child:
Column
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
children:
[
TextFormField
(
controller:
_firstNameController
,
decoration:
const
InputDecoration
(
labelText:
'First Name'
),
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your first name"
:
null
,
),
TextFormField
(
controller:
_lastNameController
,
decoration:
const
InputDecoration
(
labelText:
'Last Name'
),
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your last name"
:
null
,
),
TextFormField
(
TextFormField
(
controller:
_emailController
,
controller:
_emailController
,
decoration:
const
InputDecoration
(
labelText:
'Email'
),
decoration:
const
InputDecoration
(
labelText:
'Username (Email)'
,
border:
OutlineInputBorder
(),
),
keyboardType:
TextInputType
.
emailAddress
,
keyboardType:
TextInputType
.
emailAddress
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
nul
l
,
validator:
_validateEmai
l
,
),
),
const
SizedBox
(
height:
16
),
TextFormField
(
TextFormField
(
controller:
_passwordController
,
controller:
_passwordController
,
decoration:
InputDecoration
(
decoration:
InputDecoration
(
labelText:
'Password'
,
labelText:
'Password'
,
border:
const
OutlineInputBorder
(),
suffixIcon:
Tooltip
(
suffixIcon:
Tooltip
(
message:
'Password must be at least 12 characters long and include:
\n
'
message:
'Password must be at least 12 characters long and include:
\
\
n'
'- 1 uppercase letter
\n
'
'- 1 uppercase letter
\
\
n'
'- 1 number
\n
'
'- 1 number
\
\
n'
'- 1 special character (!@#
\$
%^&*(),.?":{}|<>)'
,
'- 1 special character (!@#
\\
\$
%^&*(),.?":{}|<>)'
,
child:
Icon
(
Icons
.
help_outline
),
child:
const
Icon
(
Icons
.
help_outline
),
),
),
),
),
obscureText:
true
,
obscureText:
true
,
validator:
_validatePassword
,
validator:
_validatePassword
,
),
),
TextFormField
(
const
SizedBox
(
height:
30
),
controller:
_confirmPasswordController
,
decoration:
const
InputDecoration
(
labelText:
'Confirm Password'
),
obscureText:
true
,
validator:
(
value
)
=>
value
!=
_passwordController
.
text
?
"Passwords do not match"
:
null
,
),
const
SizedBox
(
height:
20
),
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
.
icon
(
onPressed:
_isLoading
?
null
:
_signUpWithGoogle
,
icon:
const
Icon
(
Icons
.
login
,
size:
20
,
color:
Colors
.
blue
),
label:
const
Text
(
'Sign up with Google'
),
),
),
const
SizedBox
(
height:
16
),
Row
(
children:
[
const
Expanded
(
child:
Divider
()),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'OR'
),
),
const
Expanded
(
child:
Divider
()),
],
),
const
SizedBox
(
height:
16
),
_isLoading
_isLoading
?
const
CircularProgressIndicator
()
?
const
CircularProgressIndicator
()
:
SizedBox
(
:
SizedBox
(
width:
double
.
infinity
,
width:
double
.
infinity
,
child:
ElevatedButton
(
child:
ElevatedButton
(
onPressed:
_signUp
,
onPressed:
_signUp
,
child:
const
Text
(
'Sign Up with Email'
),
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
20
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
'Create Account'
),
),
),
),
),
],
],
),
),
),
),
),
),
),
),
);
);
}
}
}
}
lib/services/firestore_service.dart
View file @
a2d2df27
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/user_model.dart'
;
class
FirestoreService
{
class
FirestoreService
{
final
FirebaseFirestore
_db
=
FirebaseFirestore
.
instance
;
final
FirebaseFirestore
_db
=
FirebaseFirestore
.
instance
;
final
String
_collectionPath
=
'addresses'
;
final
String
_addressesCollectionPath
=
'addresses'
;
final
String
_usersCollectionPath
=
'users'
;
// Get a stream of addresses for a specific user
// Get a stream of addresses for a specific user
Stream
<
List
<
DeliveryAddress
>>
getAddresses
(
String
userId
)
{
Stream
<
List
<
DeliveryAddress
>>
getAddresses
(
String
userId
)
{
return
_db
return
_db
.
collection
(
_
c
ollectionPath
)
.
collection
(
_
addressesC
ollectionPath
)
.
where
(
'userId'
,
isEqualTo:
userId
)
.
where
(
'userId'
,
isEqualTo:
userId
)
.
snapshots
()
.
snapshots
()
.
map
((
snapshot
)
=>
.
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
DeliveryAddress
.
fromJson
(
doc
.
data
())).
toList
());
snapshot
.
docs
.
map
((
doc
)
=>
DeliveryAddress
.
fromJson
(
doc
.
data
())).
toList
());
}
}
// Get a stream of unassigned addresses
Stream
<
List
<
DeliveryAddress
>>
getUnassignedAddresses
(
String
userId
)
{
return
_db
.
collection
(
_addressesCollectionPath
)
.
where
(
'userId'
,
isEqualTo:
userId
)
.
where
(
'status'
,
isEqualTo:
'pending'
)
.
snapshots
()
.
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
DeliveryAddress
.
fromJson
(
doc
.
data
())).
toList
());
}
// Add or update an address
// Add or update an address
Future
<
void
>
saveAddress
(
DeliveryAddress
address
)
{
Future
<
void
>
saveAddress
(
DeliveryAddress
address
)
{
return
_db
.
collection
(
_
c
ollectionPath
).
doc
(
address
.
id
).
set
(
address
.
toJson
());
return
_db
.
collection
(
_
addressesC
ollectionPath
).
doc
(
address
.
id
).
set
(
address
.
toJson
());
}
}
// Save a list of addresses from a CSV
// Save a list of addresses from a CSV
Future
<
void
>
saveAddressesFromCsv
(
List
<
DeliveryAddress
>
addresses
)
async
{
Future
<
void
>
saveAddressesFromCsv
(
List
<
DeliveryAddress
>
addresses
)
async
{
final
batch
=
_db
.
batch
();
final
batch
=
_db
.
batch
();
for
(
final
address
in
addresses
)
{
for
(
final
address
in
addresses
)
{
final
docRef
=
_db
.
collection
(
_
c
ollectionPath
).
doc
(
address
.
id
);
final
docRef
=
_db
.
collection
(
_
addressesC
ollectionPath
).
doc
(
address
.
id
);
batch
.
set
(
docRef
,
address
.
toJson
());
batch
.
set
(
docRef
,
address
.
toJson
());
}
}
await
batch
.
commit
();
await
batch
.
commit
();
...
@@ -32,6 +46,125 @@ class FirestoreService {
...
@@ -32,6 +46,125 @@ class FirestoreService {
// Delete an address
// Delete an address
Future
<
void
>
deleteAddress
(
String
addressId
)
{
Future
<
void
>
deleteAddress
(
String
addressId
)
{
return
_db
.
collection
(
_collectionPath
).
doc
(
addressId
).
delete
();
return
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
).
delete
();
}
// Get a stream of addresses for a specific driver
Stream
<
List
<
DeliveryAddress
>>
getDriverDeliveries
(
String
driverId
)
{
return
_db
.
collection
(
_addressesCollectionPath
)
.
where
(
'driverId'
,
isEqualTo:
driverId
)
.
snapshots
()
.
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
DeliveryAddress
.
fromJson
(
doc
.
data
())).
toList
());
}
// Update delivery status to accepted
Future
<
void
>
updateDeliveryStatus
(
String
addressId
,
String
status
)
{
return
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
).
update
({
'status'
:
status
,
});
}
// Deny an assignment
Future
<
void
>
denyAssignment
(
String
addressId
)
{
return
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
).
update
({
'status'
:
'denied'
,
'driverId'
:
FieldValue
.
delete
(),
});
}
// Get all users
Stream
<
List
<
UserModel
>>
getUsers
()
{
return
_db
.
collection
(
_usersCollectionPath
).
snapshots
().
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
UserModel
.
fromFirestore
(
doc
)).
toList
());
}
// Get all drivers
Stream
<
List
<
UserModel
>>
getDrivers
()
{
return
_db
.
collection
(
_usersCollectionPath
)
.
where
(
'role'
,
isEqualTo:
'driver'
)
.
snapshots
()
.
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
UserModel
.
fromFirestore
(
doc
)).
toList
());
}
// Get a stream of assigned addresses for a specific user
Stream
<
List
<
DeliveryAddress
>>
getAssignedAddresses
(
String
userId
)
{
return
_db
.
collection
(
_addressesCollectionPath
)
.
where
(
'userId'
,
isEqualTo:
userId
)
.
where
(
'status'
,
whereIn:
[
'assigned'
,
'accepted'
])
.
snapshots
()
.
map
((
snapshot
)
=>
snapshot
.
docs
.
map
((
doc
)
=>
DeliveryAddress
.
fromJson
(
doc
.
data
())).
toList
());
}
// Get a user by their ID
Future
<
UserModel
?>
getUserById
(
String
uid
)
async
{
final
doc
=
await
_db
.
collection
(
_usersCollectionPath
).
doc
(
uid
).
get
();
if
(
doc
.
exists
)
{
return
UserModel
.
fromFirestore
(
doc
);
}
return
null
;
}
// Assign a list of addresses to a list of drivers in a round-robin fashion
Future
<
void
>
assignAddressesToDrivers
(
List
<
String
>
addressIds
,
List
<
String
>
driverIds
)
async
{
if
(
addressIds
.
isEmpty
||
driverIds
.
isEmpty
)
return
;
final
batch
=
_db
.
batch
();
int
driverIndex
=
0
;
for
(
final
addressId
in
addressIds
)
{
final
driverId
=
driverIds
[
driverIndex
];
final
docRef
=
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
);
batch
.
update
(
docRef
,
{
'driverId'
:
driverId
,
'status'
:
'assigned'
});
driverIndex
=
(
driverIndex
+
1
)
%
driverIds
.
length
;
}
await
batch
.
commit
();
}
// Unassign all addresses for a specific user
Future
<
void
>
unassignAllAddresses
(
String
userId
)
async
{
final
addresses
=
await
getAssignedAddresses
(
userId
).
first
;
final
batch
=
_db
.
batch
();
for
(
final
address
in
addresses
)
{
final
docRef
=
_db
.
collection
(
_addressesCollectionPath
).
doc
(
address
.
id
);
batch
.
update
(
docRef
,
{
'driverId'
:
FieldValue
.
delete
(),
'status'
:
'pending'
});
}
await
batch
.
commit
();
}
// Reassign a denied address
Future
<
void
>
reassignAddress
(
String
addressId
)
{
return
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
).
update
({
'status'
:
'pending'
,
'driverId'
:
FieldValue
.
delete
(),
});
}
// Assign a user the driver role
Future
<
void
>
assignDriverRole
(
String
uid
)
{
return
_db
.
collection
(
_usersCollectionPath
).
doc
(
uid
).
update
({
'role'
:
'driver'
});
}
// Remove the driver role from a user
Future
<
void
>
removeDriverRole
(
String
uid
)
{
return
_db
.
collection
(
_usersCollectionPath
).
doc
(
uid
).
update
({
'role'
:
FieldValue
.
delete
()});
}
// Assign an address to a driver
Future
<
void
>
assignAddressToDriver
(
String
addressId
,
String
driverId
)
{
return
_db
.
collection
(
_addressesCollectionPath
).
doc
(
addressId
).
update
({
'driverId'
:
driverId
,
'status'
:
'assigned'
,
});
}
}
}
}
lib/widgets/address_list.dart
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/delivery_address.dart'
;
class
AddressList
extends
StatelessWidget
{
typedef
SelectionChangedCallback
=
void
Function
(
Set
<
String
>
selectedIds
);
class
AddressList
extends
StatefulWidget
{
final
Stream
<
List
<
DeliveryAddress
>>
addressesStream
;
final
Stream
<
List
<
DeliveryAddress
>>
addressesStream
;
final
Function
(
DeliveryAddress
)
onEdit
;
final
Function
(
DeliveryAddress
)
onEdit
;
final
Function
(
String
)
onDelete
;
final
Function
(
String
)
onDelete
;
final
Function
(
String
)
onReassign
;
final
SelectionChangedCallback
onSelectionChanged
;
const
AddressList
({
const
AddressList
({
super
.
key
,
super
.
key
,
required
this
.
addressesStream
,
required
this
.
addressesStream
,
required
this
.
onEdit
,
required
this
.
onEdit
,
required
this
.
onDelete
,
required
this
.
onDelete
,
required
this
.
onReassign
,
required
this
.
onSelectionChanged
,
});
@override
_AddressListState
createState
()
=>
_AddressListState
();
}
class
_AddressListState
extends
State
<
AddressList
>
{
Set
<
String
>
_selectedAddressIds
=
{};
List
<
DeliveryAddress
>
_currentAddresses
=
[];
bool
_isSelectAll
=
false
;
void
_handleAddressSelection
(
String
addressId
,
bool
isSelected
)
{
setState
(()
{
if
(
isSelected
)
{
_selectedAddressIds
.
add
(
addressId
);
}
else
{
_selectedAddressIds
.
remove
(
addressId
);
}
_isSelectAll
=
_currentAddresses
.
isNotEmpty
&&
_selectedAddressIds
.
length
==
_currentAddresses
.
length
;
});
});
widget
.
onSelectionChanged
(
_selectedAddressIds
);
}
void
_toggleSelectAll
()
{
setState
(()
{
if
(
_isSelectAll
)
{
_selectedAddressIds
.
clear
();
_isSelectAll
=
false
;
}
else
{
_selectedAddressIds
=
_currentAddresses
.
map
((
addr
)
=>
addr
.
id
).
toSet
();
_isSelectAll
=
true
;
}
});
widget
.
onSelectionChanged
(
_selectedAddressIds
);
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
StreamBuilder
<
List
<
DeliveryAddress
>>(
return
StreamBuilder
<
List
<
DeliveryAddress
>>(
stream:
addressesStream
,
stream:
widget
.
addressesStream
,
builder:
(
context
,
snapshot
)
{
builder:
(
context
,
snapshot
)
{
if
(
snapshot
.
hasError
)
{
if
(
snapshot
.
hasError
)
{
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
...
@@ -28,34 +69,73 @@ class AddressList extends StatelessWidget {
...
@@ -28,34 +69,73 @@ class AddressList extends StatelessWidget {
return
const
Center
(
child:
Text
(
'No addresses found.'
));
return
const
Center
(
child:
Text
(
'No addresses found.'
));
}
}
final
addresses
=
snapshot
.
data
!;
_currentAddresses
=
snapshot
.
data
!;
final
currentIds
=
_currentAddresses
.
map
((
e
)
=>
e
.
id
).
toSet
();
_selectedAddressIds
.
removeWhere
((
id
)
=>
!
currentIds
.
contains
(
id
));
return
ListView
.
builder
(
return
Column
(
itemCount:
addresses
.
length
,
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8.0
),
child:
Row
(
children:
[
Checkbox
(
value:
_isSelectAll
,
onChanged:
(
bool
?
value
)
{
_toggleSelectAll
();
},
),
const
Text
(
'Select All'
),
],
),
),
Expanded
(
child:
ListView
.
builder
(
itemCount:
_currentAddresses
.
length
,
itemBuilder:
(
context
,
index
)
{
itemBuilder:
(
context
,
index
)
{
final
address
=
addresses
[
index
];
final
address
=
_currentAddresses
[
index
];
final
isSelected
=
_selectedAddressIds
.
contains
(
address
.
id
);
final
capitalizedStatus
=
address
.
status
.
isEmpty
?
''
:
'
${address.status[0].toUpperCase()}${address.status.substring(1)}
'
;
return
Card
(
return
Card
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
8
.0
,
horizontal:
4.0
),
margin:
const
EdgeInsets
.
symmetric
(
vertical:
4
.0
,
horizontal:
4.0
),
child:
ListTile
(
child:
ListTile
(
leading:
CircleAvatar
(
child:
Text
(
'
${index + 1}
'
)),
leading:
Checkbox
(
value:
isSelected
,
onChanged:
(
bool
?
value
)
{
if
(
value
!=
null
)
{
_handleAddressSelection
(
address
.
id
,
value
);
}
},
),
title:
Text
(
address
.
fullAddress
),
title:
Text
(
address
.
fullAddress
),
subtitle:
const
Text
(
'Status: Pending
'
),
subtitle:
Text
(
'Status:
$capitalizedStatus
'
),
trailing:
Row
(
trailing:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
children:
[
if
(
address
.
status
==
'denied'
)
TextButton
(
onPressed:
()
=>
widget
.
onReassign
(
address
.
id
),
child:
const
Text
(
'Reassign'
,
style:
TextStyle
(
color:
Colors
.
orange
)),
),
IconButton
(
IconButton
(
icon:
const
Icon
(
Icons
.
edit
,
color:
Colors
.
blue
),
icon:
const
Icon
(
Icons
.
edit
,
color:
Colors
.
blue
),
onPressed:
()
=>
onEdit
(
address
),
onPressed:
()
=>
widget
.
onEdit
(
address
),
),
),
IconButton
(
IconButton
(
icon:
const
Icon
(
Icons
.
delete
,
color:
Colors
.
red
),
icon:
const
Icon
(
Icons
.
delete
,
color:
Colors
.
red
),
onPressed:
()
=>
onDelete
(
address
.
id
),
onPressed:
()
=>
widget
.
onDelete
(
address
.
id
),
),
),
],
],
),
),
),
),
);
);
},
},
),
),
],
);
);
},
},
);
);
...
...
lib/widgets/assign_address_dialog.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'../models/delivery_address.dart'
;
import
'../models/user_model.dart'
;
class
AssignAddressDialog
extends
StatefulWidget
{
final
List
<
UserModel
>
drivers
;
final
List
<
DeliveryAddress
>
addresses
;
final
Function
(
String
,
String
)
onAssign
;
const
AssignAddressDialog
({
super
.
key
,
required
this
.
drivers
,
required
this
.
addresses
,
required
this
.
onAssign
,
});
@override
State
<
AssignAddressDialog
>
createState
()
=>
_AssignAddressDialogState
();
}
class
_AssignAddressDialogState
extends
State
<
AssignAddressDialog
>
{
String
?
_selectedDriverId
;
String
?
_selectedAddressId
;
@override
Widget
build
(
BuildContext
context
)
{
return
AlertDialog
(
title:
const
Text
(
'Assign Address to Driver'
),
content:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
DropdownButtonFormField
<
String
>(
value:
_selectedDriverId
,
hint:
const
Text
(
'Select a driver'
),
onChanged:
(
value
)
{
setState
(()
{
_selectedDriverId
=
value
;
});
},
items:
widget
.
drivers
.
map
((
driver
)
{
return
DropdownMenuItem
(
value:
driver
.
uid
,
child:
Text
(
driver
.
displayName
??
driver
.
email
??
'N/A'
),
);
}).
toList
(),
),
const
SizedBox
(
height:
16
),
DropdownButtonFormField
<
String
>(
value:
_selectedAddressId
,
hint:
const
Text
(
'Select an address'
),
onChanged:
(
value
)
{
setState
(()
{
_selectedAddressId
=
value
;
});
},
items:
widget
.
addresses
.
where
((
address
)
=>
address
.
driverId
==
null
).
map
((
address
)
{
return
DropdownMenuItem
(
value:
address
.
id
,
child:
Text
(
address
.
fullAddress
),
);
}).
toList
(),
),
],
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
const
Text
(
'Cancel'
),
),
ElevatedButton
(
onPressed:
(
_selectedDriverId
!=
null
&&
_selectedAddressId
!=
null
)
?
()
{
widget
.
onAssign
(
_selectedAddressId
!,
_selectedDriverId
!);
Navigator
.
of
(
context
).
pop
();
}
:
null
,
child:
const
Text
(
'Assign'
),
),
],
);
}
}
lib/widgets/assign_drivers_dialog.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'../models/user_model.dart'
;
typedef
AssignCallback
=
void
Function
(
List
<
String
>
driverIds
);
class
AssignDriversDialog
extends
StatefulWidget
{
final
List
<
UserModel
>
drivers
;
final
AssignCallback
onAssign
;
const
AssignDriversDialog
({
super
.
key
,
required
this
.
drivers
,
required
this
.
onAssign
,
});
@override
_AssignDriversDialogState
createState
()
=>
_AssignDriversDialogState
();
}
class
_AssignDriversDialogState
extends
State
<
AssignDriversDialog
>
{
Set
<
String
>
_selectedDriverIds
=
{};
bool
_isSelectAll
=
false
;
void
_handleDriverSelection
(
String
driverId
,
bool
isSelected
)
{
setState
(()
{
if
(
isSelected
)
{
_selectedDriverIds
.
add
(
driverId
);
}
else
{
_selectedDriverIds
.
remove
(
driverId
);
}
_isSelectAll
=
widget
.
drivers
.
isNotEmpty
&&
_selectedDriverIds
.
length
==
widget
.
drivers
.
length
;
});
}
void
_toggleSelectAll
()
{
setState
(()
{
if
(
_isSelectAll
)
{
_selectedDriverIds
.
clear
();
}
else
{
_selectedDriverIds
=
widget
.
drivers
.
map
((
d
)
=>
d
.
uid
).
toSet
();
}
_isSelectAll
=
!
_isSelectAll
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
AlertDialog
(
title:
const
Text
(
'Assign to Drivers'
),
content:
SizedBox
(
width:
double
.
maxFinite
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
if
(
widget
.
drivers
.
isNotEmpty
)
CheckboxListTile
(
title:
const
Text
(
'Select All Drivers'
),
value:
_isSelectAll
,
onChanged:
(
value
)
=>
_toggleSelectAll
(),
),
Expanded
(
child:
ListView
.
builder
(
shrinkWrap:
true
,
itemCount:
widget
.
drivers
.
length
,
itemBuilder:
(
context
,
index
)
{
final
driver
=
widget
.
drivers
[
index
];
final
isSelected
=
_selectedDriverIds
.
contains
(
driver
.
uid
);
return
CheckboxListTile
(
title:
Text
(
driver
.
displayName
??
driver
.
email
??
driver
.
uid
),
value:
isSelected
,
onChanged:
(
bool
?
value
)
{
if
(
value
!=
null
)
{
_handleDriverSelection
(
driver
.
uid
,
value
);
}
},
);
},
),
),
],
),
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
const
Text
(
'Cancel'
),
),
ElevatedButton
(
onPressed:
_selectedDriverIds
.
isNotEmpty
?
()
{
widget
.
onAssign
(
_selectedDriverIds
.
toList
());
Navigator
.
of
(
context
).
pop
();
}
:
null
,
child:
const
Text
(
'Assign'
),
),
],
);
}
}
lib/widgets/drivers_list.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'../models/user_model.dart'
;
class
DriversList
extends
StatelessWidget
{
final
Stream
<
List
<
UserModel
>>
driversStream
;
final
Function
(
String
)
onRemoveDriver
;
const
DriversList
({
super
.
key
,
required
this
.
driversStream
,
required
this
.
onRemoveDriver
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
StreamBuilder
<
List
<
UserModel
>>(
stream:
driversStream
,
builder:
(
context
,
snapshot
)
{
if
(
snapshot
.
connectionState
==
ConnectionState
.
waiting
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
snapshot
.
hasError
)
{
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
}
if
(!
snapshot
.
hasData
||
snapshot
.
data
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
'No drivers found.'
));
}
final
drivers
=
snapshot
.
data
!;
return
ListView
.
builder
(
itemCount:
drivers
.
length
,
itemBuilder:
(
context
,
index
)
{
final
driver
=
drivers
[
index
];
final
String
displayTitle
;
if
(
driver
.
displayName
!=
null
&&
driver
.
displayName
!.
isNotEmpty
)
{
displayTitle
=
driver
.
displayName
!;
}
else
if
(
driver
.
email
!=
null
&&
driver
.
email
!.
contains
(
'@'
))
{
displayTitle
=
driver
.
email
!.
split
(
'@'
).
first
;
}
else
{
displayTitle
=
driver
.
email
??
'N/A'
;
}
final
role
=
driver
.
role
??
'driver'
;
final
capitalizedRole
=
role
.
isEmpty
?
''
:
'
${role[0].toUpperCase()}${role.substring(1)}
'
;
return
Card
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
8
,
horizontal:
0
),
child:
ListTile
(
title:
Text
(
displayTitle
),
subtitle:
Text
(
'Role:
$capitalizedRole
'
),
trailing:
IconButton
(
icon:
const
Icon
(
Icons
.
remove_circle_outline
,
color:
Colors
.
red
),
tooltip:
'Remove Driver Role'
,
onPressed:
()
=>
onRemoveDriver
(
driver
.
uid
),
),
),
);
},
);
},
);
}
}
lib/widgets/select_address_dialog.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'../models/delivery_address.dart'
;
class
SelectAddressDialog
extends
StatefulWidget
{
final
List
<
DeliveryAddress
>
addresses
;
final
Function
(
List
<
String
>)
onSelect
;
const
SelectAddressDialog
({
super
.
key
,
required
this
.
addresses
,
required
this
.
onSelect
,
});
@override
State
<
SelectAddressDialog
>
createState
()
=>
_SelectAddressDialogState
();
}
class
_SelectAddressDialogState
extends
State
<
SelectAddressDialog
>
{
final
List
<
String
>
_selectedAddressIds
=
[];
void
_addAddress
()
{
setState
(()
{
_selectedAddressIds
.
add
(
""
);
});
}
@override
Widget
build
(
BuildContext
context
)
{
final
unassignedAddresses
=
widget
.
addresses
.
where
((
a
)
=>
a
.
driverId
==
null
).
toList
();
return
AlertDialog
(
title:
const
Text
(
'Select Addresses'
),
content:
SizedBox
(
width:
double
.
maxFinite
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
...
_selectedAddressIds
.
asMap
().
entries
.
map
((
entry
)
{
int
index
=
entry
.
key
;
return
DropdownButtonFormField
<
String
>(
value:
_selectedAddressIds
[
index
].
isEmpty
?
null
:
_selectedAddressIds
[
index
],
hint:
const
Text
(
'Choose an address'
),
onChanged:
(
value
)
{
setState
(()
{
_selectedAddressIds
[
index
]
=
value
!;
});
},
items:
unassignedAddresses
.
map
((
address
)
{
return
DropdownMenuItem
(
value:
address
.
id
,
child:
Text
(
address
.
fullAddress
,
overflow:
TextOverflow
.
ellipsis
),
);
}).
toList
(),
);
}).
toList
(),
const
SizedBox
(
height:
16
),
IconButton
(
icon:
const
Icon
(
Icons
.
add
),
onPressed:
_addAddress
,
),
],
),
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
const
Text
(
'Cancel'
),
),
ElevatedButton
(
onPressed:
_selectedAddressIds
.
isNotEmpty
&&
_selectedAddressIds
.
every
((
id
)
=>
id
.
isNotEmpty
)
?
()
{
widget
.
onSelect
(
_selectedAddressIds
);
Navigator
.
of
(
context
).
pop
();
}
:
null
,
child:
const
Text
(
'Assign'
),
),
],
);
}
}
lib/widgets/users_list.dart
0 → 100644
View file @
a2d2df27
import
'package:flutter/material.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../models/user_model.dart'
;
class
UsersList
extends
StatelessWidget
{
final
Stream
<
List
<
UserModel
>>
usersStream
;
final
Function
(
String
)
onAssignDriver
;
const
UsersList
({
super
.
key
,
required
this
.
usersStream
,
required
this
.
onAssignDriver
});
@override
Widget
build
(
BuildContext
context
)
{
return
StreamBuilder
<
List
<
UserModel
>>(
stream:
usersStream
,
builder:
(
context
,
snapshot
)
{
if
(
snapshot
.
connectionState
==
ConnectionState
.
waiting
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
snapshot
.
hasError
)
{
return
Center
(
child:
Text
(
'Error:
${snapshot.error}
'
));
}
if
(!
snapshot
.
hasData
||
snapshot
.
data
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
'No users found.'
));
}
final
users
=
snapshot
.
data
!;
return
ListView
.
builder
(
itemCount:
users
.
length
,
itemBuilder:
(
context
,
index
)
{
final
user
=
users
[
index
];
final
String
displayTitle
;
if
(
user
.
displayName
!=
null
&&
user
.
displayName
!.
isNotEmpty
)
{
displayTitle
=
user
.
displayName
!;
}
else
if
(
user
.
email
!=
null
&&
user
.
email
!.
contains
(
'@'
))
{
displayTitle
=
user
.
email
!.
split
(
'@'
).
first
;
}
else
{
displayTitle
=
user
.
email
??
'N/A'
;
}
final
role
=
user
.
role
??
'user'
;
final
capitalizedRole
=
role
.
isEmpty
?
''
:
'
${role[0].toUpperCase()}${role.substring(1)}
'
;
return
Card
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
8
,
horizontal:
0
),
child:
ListTile
(
title:
Text
(
displayTitle
),
subtitle:
Text
(
'Role:
$capitalizedRole
'
),
trailing:
user
.
role
!=
'driver'
?
ElevatedButton
(
onPressed:
()
=>
onAssignDriver
(
user
.
uid
),
child:
const
Text
(
'Make Driver'
),
)
:
null
,
),
);
},
);
},
);
}
}
web/index.html
View file @
a2d2df27
...
@@ -33,8 +33,29 @@
...
@@ -33,8 +33,29 @@
<title>
graph_go
</title>
<title>
graph_go
</title>
<link
rel=
"manifest"
href=
"manifest.json"
>
<link
rel=
"manifest"
href=
"manifest.json"
>
<script
src=
"https://maps.googleapis.com/maps/api/js?key=AIzaSyCFx_8PW_R6rGq-julkwV4JJGixbzmnP74"
></script>
<!-- Firebase SDK -->
<script
src=
"https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"
></script>
<script
src=
"https://www.gstatic.com/firebasejs/8.6.1/firebase-auth.js"
></script>
<script
src=
"https://www.gstatic.com/firebasejs/8.6.1/firebase-firestore.js"
></script>
</head>
</head>
<body>
<body>
<script>
// Your web app's Firebase configuration
var
firebaseConfig
=
{
apiKey
:
"AIzaSyByWSG8ewS_QX2jLfsmO5YsnbKE7HH8HRE"
,
authDomain
:
"graph-go-bd4f0.firebaseapp.com"
,
projectId
:
"graph-go-bd4f0"
,
storageBucket
:
"graph-go-bd4f0.firebasestorage.app"
,
messagingSenderId
:
"627645762372"
,
appId
:
"1:627645762372:web:45d648b5ef756be6f2a511"
,
measurementId
:
"G-DW8MH83H28"
};
// Initialize Firebase
firebase
.
initializeApp
(
firebaseConfig
);
</script>
<script
src=
"flutter_bootstrap.js"
async
></script>
<script
src=
"flutter_bootstrap.js"
async
></script>
</body>
</body>
</html>
</html>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment