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
b32c988a
Commit
b32c988a
authored
Oct 06, 2025
by
Emily Carroll
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Feat: Add driver and admin login for webpage
parent
c2c69eec
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
338 additions
and
269 deletions
+338
-269
.metadata
.metadata
+15
-15
main.dart
lib/main.dart
+2
-1
admin_dashboard_screen.dart
lib/screens/admin_dashboard_screen.dart
+4
-0
login.dart
lib/screens/login.dart
+169
-108
map_screen.dart
lib/screens/map_screen.dart
+74
-39
signup.dart
lib/screens/signup.dart
+73
-106
verify_email_screen.dart
lib/screens/verify_email_screen.dart
+0
-0
index.html
web/index.html
+1
-0
No files found.
.metadata
View file @
b32c988a
...
...
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "a
c4e799d237041cf905519190471f657b657155a
"
revision: "a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
"
channel: "stable"
project_type: app
...
...
@@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: android
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: ios
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: linux
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: macos
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: web
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: windows
create_revision: a
c4e799d237041cf905519190471f657b657155a
base_revision: a
c4e799d237041cf905519190471f657b657155a
create_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: a
402d9a4376add5bc2d6b1e33e53edaae58c07f8
# User provided section
...
...
lib/main.dart
View file @
b32c988a
...
...
@@ -79,7 +79,7 @@ class MyApp extends StatelessWidget {
),
useMaterial3:
true
,
),
home:
kIsWeb
?
const
AdminDashboardScreen
()
:
const
HomeScreen
(),
home:
kIsWeb
?
const
LoginPage
()
:
const
HomeScreen
(),
routes:
{
"/login"
:
(
context
)
=>
const
LoginPage
(),
"/signup"
:
(
context
)
=>
const
SignupPage
(),
...
...
@@ -87,6 +87,7 @@ class MyApp extends StatelessWidget {
"/map"
:
(
context
)
=>
const
MapScreen
(),
"/settings"
:
(
context
)
=>
const
SettingsScreen
(),
"/profile"
:
(
context
)
=>
const
ProfileScreen
(),
"/adminDashboard"
:
(
context
)
=>
const
AdminDashboardScreen
(),
},
);
},
...
...
lib/screens/admin_dashboard_screen.dart
View file @
b32c988a
...
...
@@ -35,6 +35,10 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
setState
(()
{
_user
=
user
;
});
if
(
user
==
null
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/login'
);
}
}
});
}
...
...
lib/screens/login.dart
View file @
b32c988a
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:flutter_svg/flutter_svg.dart'
;
import
'package:provider/provider.dart'
;
import
'../providers/settings_provider.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../colors.dart'
;
class
LoginPage
extends
StatefulWidget
{
const
LoginPage
({
super
.
key
});
...
...
@@ -18,50 +19,53 @@ class _LoginPageState extends State<LoginPage> {
final
TextEditingController
_passwordController
=
TextEditingController
();
final
_formKey
=
GlobalKey
<
FormState
>();
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
(
'/adminDashboard'
);
break
;
case
'Driver'
:
default
:
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
break
;
}
}
Future
<
void
>
_login
()
async
{
if
(!
_formKey
.
currentState
!.
validate
())
return
;
setState
(()
=>
_isLoading
=
true
);
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
,
listen:
false
);
try
{
await
FirebaseAuth
.
instance
.
signInWithEmailAndPassword
(
email:
_emailController
.
text
.
trim
(),
password:
_passwordController
.
text
.
trim
(),
);
if
(
_rememberMe
)
{
settingsProvider
.
login
();
}
if
(
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
_navigateBasedOnRole
(
_selectedRole
);
}
on
FirebaseAuthException
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Login Failed:
${e.message}
"
)),
);
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Login Failed:
${e.toString()}
"
)),
);
}
finally
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
if
(
mounted
)
setState
(()
=>
_isLoading
=
false
);
}
}
Future
<
void
>
_loginWithGoogle
()
async
{
setState
(()
=>
_isLoading
=
true
);
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
,
listen:
false
);
try
{
final
UserCredential
?
userCredential
=
await
GoogleAuthService
.
signInWithGoogle
();
if
(
FirebaseAuth
.
instance
.
currentUser
!=
null
)
{
settingsProvider
.
login
();
if
(
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
_navigateBasedOnRole
(
_selectedRole
);
}
else
if
(
userCredential
==
null
&&
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Google Sign-In was cancelled or failed."
)),
...
...
@@ -74,100 +78,157 @@ class _LoginPageState extends State<LoginPage> {
);
}
}
finally
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
if
(
mounted
)
setState
(()
=>
_isLoading
=
false
);
}
}
@override
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
(
appBar:
AppBar
(
title:
const
Text
(
'GraphGo Login'
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
tooltip:
'Back to Home'
,
icon:
const
Icon
(
Icons
.
settings
,
color:
Colors
.
white
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pushNamed
(
'/settings'
),
),
title:
const
Text
(
'GraphGo'
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
)),
centerTitle:
true
,
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Form
(
body:
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
(
'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
,
child:
Column
(
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
(
controller:
_emailController
,
decoration:
const
InputDecoration
(
labelText:
'Email'
),
decoration:
const
InputDecoration
(
labelText:
'Email'
,
border:
OutlineInputBorder
()
),
keyboardType:
TextInputType
.
emailAddress
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
null
,
),
const
SizedBox
(
height:
16
),
TextFormField
(
controller:
_passwordController
,
decoration:
const
InputDecoration
(
labelText:
'Password'
),
decoration:
const
InputDecoration
(
labelText:
'Password'
,
border:
OutlineInputBorder
()
),
obscureText:
true
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your password"
:
null
,
),
Align
(
alignment:
Alignment
.
centerLeft
,
child:
Row
(
children:
[
Checkbox
(
value:
_rememberMe
,
onChanged:
(
val
)
{
setState
(()
=>
_rememberMe
=
val
??
false
);
},
),
const
Text
(
"Remember Me"
),
],
),
),
TextButton
(
alignment:
Alignment
.
centerRight
,
child:
TextButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/forgot'
);
},
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'
),
),
child:
const
Text
(
'Forgot Password?'
),
),
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
),
const
SizedBox
(
height:
10
),
_isLoading
?
const
CircularProgressIndicator
()
:
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
_login
,
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'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/signup'
);
},
child:
const
Text
(
"Don't have an account? Sign Up"
),
),
// The debug override button has been removed.
],
),
),
],
),
),
...
...
lib/screens/map_screen.dart
View file @
b32c988a
...
...
@@ -3,6 +3,11 @@ import 'package:flutter/material.dart';
import
'package:google_maps_flutter/google_maps_flutter.dart'
;
import
'package:location/location.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
{
const
MapScreen
({
super
.
key
});
...
...
@@ -13,8 +18,10 @@ class MapScreen extends StatefulWidget {
class
_MapScreenState
extends
State
<
MapScreen
>
{
final
Completer
<
GoogleMapController
>
_controller
=
Completer
();
final
FirestoreService
_firestoreService
=
FirestoreService
();
LocationData
?
_currentLocation
;
StreamSubscription
<
LocationData
>?
_locationSubscription
;
Set
<
Marker
>
_markers
=
{};
final
User
?
user
=
FirebaseAuth
.
instance
.
currentUser
;
...
...
@@ -26,29 +33,28 @@ class _MapScreenState extends State<MapScreen> {
@override
void
initState
()
{
super
.
initState
();
_initializeLocation
();
_initializeLocationAndMarkers
();
}
Future
<
void
>
_initializeLocationAndMarkers
()
async
{
await
_initializeLocation
();
if
(
user
!=
null
)
{
_loadAddressMarkers
(
user
!.
uid
);
}
}
Future
<
void
>
_initializeLocation
()
async
{
Location
location
=
Location
();
bool
serviceEnabled
;
PermissionStatus
permissionGranted
;
serviceEnabled
=
await
location
.
serviceEnabled
();
bool
serviceEnabled
=
await
location
.
serviceEnabled
();
if
(!
serviceEnabled
)
{
serviceEnabled
=
await
location
.
requestService
();
if
(!
serviceEnabled
)
{
return
;
}
if
(!
serviceEnabled
)
return
;
}
permissionGranted
=
await
location
.
hasPermission
();
PermissionStatus
permissionGranted
=
await
location
.
hasPermission
();
if
(
permissionGranted
==
PermissionStatus
.
denied
)
{
permissionGranted
=
await
location
.
requestPermission
();
if
(
permissionGranted
!=
PermissionStatus
.
granted
)
{
return
;
}
if
(
permissionGranted
!=
PermissionStatus
.
granted
)
return
;
}
_currentLocation
=
await
location
.
getLocation
();
...
...
@@ -57,15 +63,49 @@ class _MapScreenState extends State<MapScreen> {
}
_locationSubscription
=
location
.
onLocationChanged
.
listen
((
LocationData
newLocation
)
{
if
(
mounted
)
{
setState
(()
{
_currentLocation
=
newLocation
;
});
if
(
mounted
)
{
setState
(()
=>
_currentLocation
=
newLocation
);
_moveCameraToLocation
(
newLocation
);
}
});
}
Future
<
void
>
_loadAddressMarkers
(
String
userId
)
async
{
_firestoreService
.
getAddresses
(
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}
"
);
}
}
else
{
print
(
"Skipping address due to missing fields:
${address.id}
"
);
}
}
if
(
mounted
)
{
setState
(()
=>
_markers
=
newMarkers
);
}
});
}
Future
<
void
>
_moveCameraToLocation
(
LocationData
locationData
)
async
{
final
GoogleMapController
controller
=
await
_controller
.
future
;
controller
.
animateCamera
(
CameraUpdate
.
newCameraPosition
(
...
...
@@ -76,6 +116,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
void
dispose
()
{
_locationSubscription
?.
cancel
();
...
...
@@ -86,23 +133,17 @@ class _MapScreenState extends State<MapScreen> {
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'Your Location'
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
title:
const
Text
(
"Driver's View"
),
automaticallyImplyLeading:
false
,
actions:
[
if
(
user
?.
email
!=
null
)
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
16.0
),
child:
Center
(
child:
Text
(
user
!.
email
!,
style:
const
TextStyle
(
fontSize:
12
,
),
),
child:
Center
(
child:
Text
(
user
!.
email
!,
style:
const
TextStyle
(
fontSize:
12
))),
),
IconButton
(
icon:
const
Icon
(
Icons
.
logout
),
onPressed:
_logout
,
),
],
),
...
...
@@ -116,14 +157,8 @@ class _MapScreenState extends State<MapScreen> {
},
myLocationEnabled:
true
,
myLocationButtonEnabled:
true
,
markers:
{
if
(
_currentLocation
!=
null
)
Marker
(
markerId:
const
MarkerId
(
'currentLocation'
),
position:
LatLng
(
_currentLocation
!.
latitude
!,
_currentLocation
!.
longitude
!),
infoWindow:
const
InfoWindow
(
title:
'My Location'
),
),
},
markers:
_markers
,
),
);
}
...
...
lib/screens/signup.dart
View file @
b32c988a
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../colors.dart'
;
class
SignupPage
extends
StatefulWidget
{
const
SignupPage
({
super
.
key
});
...
...
@@ -13,13 +11,21 @@ class SignupPage extends StatefulWidget {
class
_SignupPageState
extends
State
<
SignupPage
>
{
final
_formKey
=
GlobalKey
<
FormState
>();
final
TextEditingController
_firstNameController
=
TextEditingController
();
final
TextEditingController
_lastNameController
=
TextEditingController
();
final
TextEditingController
_emailController
=
TextEditingController
();
final
TextEditingController
_passwordController
=
TextEditingController
();
final
TextEditingController
_confirmPasswordController
=
TextEditingController
();
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
)
{
if
(
value
==
null
||
value
.
isEmpty
)
return
"Password is required."
;
if
(
value
.
length
<
12
)
return
"Password must be at least 12 characters."
;
...
...
@@ -41,45 +47,30 @@ class _SignupPageState extends State<SignupPage> {
);
await
FirebaseFirestore
.
instance
.
collection
(
'users'
).
doc
(
userCredential
.
user
!.
uid
).
set
({
'first_name'
:
_firstNameController
.
text
.
trim
(),
'last_name'
:
_lastNameController
.
text
.
trim
(),
'email'
:
_emailController
.
text
.
trim
(),
'provider'
:
'email'
,
'created_at'
:
Timestamp
.
now
(),
});
if
(
mounted
)
{
// After signing up, go to the map screen
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
(
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
)
{
if
(
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Google Sign-Up Fail
ed:
${e.toString()}
"
)),
SnackBar
(
content:
Text
(
"An unexpected error occurr
ed:
${e.toString()}
"
)),
);
}
}
finally
{
if
(
mounted
)
{
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
...
...
@@ -87,95 +78,71 @@ class _SignupPageState extends State<SignupPage> {
@override
Widget
build
(
BuildContext
context
)
{
final
bool
isDarkMode
=
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
;
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'GraphGo Sign Up'
),
// The back button is now handled correctly by the Navigator
title:
const
Text
(
'Create Account'
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
// Corrected line
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
tooltip:
'Back to Login'
,
),
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16.0
),
body:
Center
(
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
32.0
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
maxWidth:
400
),
child:
Form
(
key:
_formKey
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
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
(
controller:
_emailController
,
decoration:
const
InputDecoration
(
labelText:
'Email'
),
decoration:
const
InputDecoration
(
labelText:
'Username (Email)'
,
border:
OutlineInputBorder
(),
),
keyboardType:
TextInputType
.
emailAddress
,
validator:
(
value
)
=>
value
!.
isEmpty
?
"Enter your email"
:
nul
l
,
validator:
_validateEmai
l
,
),
const
SizedBox
(
height:
16
),
TextFormField
(
controller:
_passwordController
,
decoration:
InputDecoration
(
labelText:
'Password'
,
border:
const
OutlineInputBorder
(),
suffixIcon:
Tooltip
(
message:
'Password must be at least 12 characters long and include:
\n
'
'- 1 uppercase letter
\n
'
'- 1 number
\n
'
'- 1 special character (!@#
\$
%^&*(),.?":{}|<>)'
,
child:
Icon
(
Icons
.
help_outline
),
child:
const
Icon
(
Icons
.
help_outline
),
),
),
obscureText:
true
,
validator:
_validatePassword
,
),
TextFormField
(
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
),
const
SizedBox
(
height:
30
),
_isLoading
?
const
CircularProgressIndicator
()
:
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
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/screens/verify_email_screen.dart
0 → 100644
View file @
b32c988a
web/index.html
View file @
b32c988a
...
...
@@ -33,6 +33,7 @@
<title>
graph_go
</title>
<link
rel=
"manifest"
href=
"manifest.json"
>
<script
src=
"https://maps.googleapis.com/maps/api/js?key=AIzaSyCFx_8PW_R6rGq-julkwV4JJGixbzmnP74"
></script>
</head>
<body>
<script
src=
"flutter_bootstrap.js"
async
></script>
...
...
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