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
23d89820
Commit
23d89820
authored
Oct 02, 2025
by
aleenaasghar
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added home page, login, and map integration
parent
20408a09
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
889 additions
and
828 deletions
+889
-828
settings.gradle.kts
android/settings.gradle.kts
+2
-0
main.dart
lib/main.dart
+72
-75
settings_provider.dart
lib/providers/settings_provider.dart
+58
-0
forgot_password.dart
lib/screens/forgot_password.dart
+7
-37
graph_screen.dart
lib/screens/graph_screen.dart
+63
-31
home_screen.dart
lib/screens/home_screen.dart
+202
-446
login.dart
lib/screens/login.dart
+177
-0
map_screen.dart
lib/screens/map_screen.dart
+130
-0
settings_screen.dart
lib/screens/settings_screen.dart
+42
-156
signup.dart
lib/screens/signup.dart
+24
-78
pubspec.lock
pubspec.lock
+96
-0
pubspec.yaml
pubspec.yaml
+16
-5
No files found.
android/settings.gradle.kts
View file @
23d89820
...
...
@@ -27,3 +27,5 @@ plugins {
}
include
(
":app"
)
rootProject
.
name
=
"GraphGo"
lib/main.dart
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:go_router/go_router.dart'
;
import
'package:provider/provider.dart'
;
import
'package:firebase_core/firebase_core.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'firebase_options.dart'
;
// Providers
import
'providers/graph_provider.dart'
;
import
'providers/settings_provider.dart'
;
import
'providers/delivery_provider.dart'
;
// Screens
import
'screens/home_screen.dart'
;
import
'screens/
graph_screen.dart'
;
import
'screens/
map_screen.dart'
;
import
'screens/settings_screen.dart'
;
import
'screens/profile_screen.dart'
;
import
'
providers/delivery_provider
.dart'
;
import
'
login
.dart'
;
import
's
ignup
.dart'
;
import
'
screens/login
.dart'
;
import
'
screens/signup
.dart'
;
import
's
creens/forgot_password
.dart'
;
void
main
(
)
async
{
Future
<
void
>
main
()
async
{
WidgetsFlutterBinding
.
ensureInitialized
();
await
Firebase
.
initializeApp
(
options:
DefaultFirebaseOptions
.
currentPlatform
,
);
runApp
(
const
GraphGoApp
());
final
settingsProvider
=
await
SettingsProvider
.
create
();
runApp
(
MyApp
(
settingsProvider:
settingsProvider
));
}
class
GraphGoApp
extends
StatelessWidget
{
const
GraphGoApp
({
super
.
key
});
class
MyApp
extends
StatelessWidget
{
final
SettingsProvider
settingsProvider
;
const
MyApp
({
super
.
key
,
required
this
.
settingsProvider
});
@override
Widget
build
(
BuildContext
context
)
{
return
ChangeNotifierProvider
(
create:
(
context
)
=>
DeliveryProvider
()..
initialize
(),
child:
MaterialApp
.
router
(
title:
'GraphGo - Route Optimization'
,
theme:
ThemeData
(
colorScheme:
ColorScheme
.
fromSeed
(
seedColor:
Colors
.
deepPurple
),
useMaterial3:
true
,
),
routerConfig:
_router
,
return
MultiProvider
(
providers:
[
ChangeNotifierProvider
(
create:
(
_
)
=>
GraphProvider
()),
ChangeNotifierProvider
(
create:
(
_
)
=>
DeliveryProvider
()),
ChangeNotifierProvider
.
value
(
value:
settingsProvider
),
],
child:
Consumer
<
SettingsProvider
>(
builder:
(
context
,
settings
,
child
)
{
return
MaterialApp
(
debugShowCheckedModeBanner:
false
,
title:
"GraphGo"
,
themeMode:
settings
.
darkMode
?
ThemeMode
.
dark
:
ThemeMode
.
light
,
theme:
ThemeData
(
brightness:
Brightness
.
light
,
colorScheme:
ColorScheme
.
fromSeed
(
seedColor:
Colors
.
deepPurple
,
brightness:
Brightness
.
light
),
appBarTheme:
const
AppBarTheme
(
backgroundColor:
Color
(
0xFF0D2B0D
),
foregroundColor:
Colors
.
white
,
),
useMaterial3:
true
,
),
darkTheme:
ThemeData
(
brightness:
Brightness
.
dark
,
colorScheme:
ColorScheme
.
dark
(
primary:
Colors
.
deepPurple
.
shade300
,
surface:
Colors
.
grey
.
shade800
,
background:
Colors
.
black
,
),
scaffoldBackgroundColor:
Colors
.
black
,
appBarTheme:
const
AppBarTheme
(
backgroundColor:
Color
(
0xFF0D2B0D
),
foregroundColor:
Colors
.
white
,
),
cardTheme:
CardThemeData
(
color:
Colors
.
grey
[
850
],
elevation:
2
,
),
useMaterial3:
true
,
),
initialRoute:
"/"
,
routes:
{
"/"
:
(
context
)
=>
const
HomeScreen
(),
"/login"
:
(
context
)
=>
const
LoginPage
(),
"/signup"
:
(
context
)
=>
const
SignupPage
(),
"/forgot"
:
(
context
)
=>
const
ForgotPasswordPage
(),
"/map"
:
(
context
)
=>
const
MapScreen
(),
"/settings"
:
(
context
)
=>
const
SettingsScreen
(),
"/profile"
:
(
context
)
=>
const
ProfileScreen
(),
},
);
},
),
);
}
}
final
GoRouter
_router
=
GoRouter
(
redirect:
(
BuildContext
context
,
GoRouterState
state
)
{
final
user
=
FirebaseAuth
.
instance
.
currentUser
;
final
isLoggedIn
=
user
!=
null
;
final
isLoggingIn
=
state
.
matchedLocation
==
'/login'
||
state
.
matchedLocation
==
'/signup'
;
// If user is logged in and trying to access login/signup pages, redirect to home
if
(
isLoggedIn
&&
isLoggingIn
)
{
return
'/'
;
}
// No automatic redirect to login - let the home screen handle it
return
null
;
// No redirect needed
},
routes:
<
RouteBase
>[
GoRoute
(
path:
'/'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
HomeScreen
();
},
routes:
<
RouteBase
>[
GoRoute
(
path:
'graph'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
GraphScreen
();
},
),
GoRoute
(
path:
'settings'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
SettingsScreen
();
},
),
GoRoute
(
path:
'profile'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
ProfileScreen
();
},
),
],
),
GoRoute
(
path:
'/login'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
LoginPage
();
},
),
GoRoute
(
path:
'/signup'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
SignupPage
();
},
),
],
);
\ No newline at end of file
lib/providers/settings_provider.dart
0 → 100644
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
SettingsProvider
with
ChangeNotifier
{
static
const
String
_darkModeKey
=
'darkMode'
;
static
const
String
_isLoggedInKey
=
'isLoggedIn'
;
bool
_darkMode
=
false
;
bool
_isLoggedIn
=
false
;
bool
get
darkMode
=>
_darkMode
;
bool
get
isLoggedIn
=>
_isLoggedIn
;
// Private constructor for async initialization
SettingsProvider
.
_
();
// Static async method to create an instance
static
Future
<
SettingsProvider
>
create
()
async
{
final
provider
=
SettingsProvider
.
_
();
await
provider
.
_loadPreferences
();
return
provider
;
}
Future
<
void
>
_loadPreferences
()
async
{
final
prefs
=
await
SharedPreferences
.
getInstance
();
_darkMode
=
prefs
.
getBool
(
_darkModeKey
)
??
false
;
_isLoggedIn
=
prefs
.
getBool
(
_isLoggedInKey
)
??
false
;
notifyListeners
();
}
Future
<
void
>
_saveDarkModePreference
()
async
{
final
prefs
=
await
SharedPreferences
.
getInstance
();
await
prefs
.
setBool
(
_darkModeKey
,
_darkMode
);
}
Future
<
void
>
_saveLoginStatus
()
async
{
final
prefs
=
await
SharedPreferences
.
getInstance
();
await
prefs
.
setBool
(
_isLoggedInKey
,
_isLoggedIn
);
}
void
toggleDarkMode
()
{
_darkMode
=
!
_darkMode
;
_saveDarkModePreference
();
notifyListeners
();
}
void
login
()
{
_isLoggedIn
=
true
;
_saveLoginStatus
();
notifyListeners
();
}
void
logout
()
{
_isLoggedIn
=
false
;
_saveLoginStatus
();
notifyListeners
();
}
}
lib/forgot_password.dart
→
lib/
screens/
forgot_password.dart
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:go_router/go_router.dart'
;
import
'colors.dart'
;
import
'../colors.dart'
;
class
ForgotPasswordPage
extends
StatefulWidget
{
const
ForgotPasswordPage
({
super
.
key
});
...
...
@@ -31,7 +30,9 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
SnackBar
(
content:
Text
(
"Password reset failed:
${e.toString()}
"
)),
);
}
finally
{
setState
(()
=>
_isLoading
=
false
);
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
}
...
...
@@ -41,25 +42,13 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'Reset Password'
,
style:
TextStyle
(
fontFamily:
'Impact'
,
fontSize:
24
,
fontStyle:
FontStyle
.
italic
,
fontWeight:
FontWeight
.
bold
,
color:
kPrimaryColor
,
),
),
title:
const
Text
(
'Reset Password'
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
context
.
go
(
'/login'
),
// Corrected the navigation to simply go back to the previous page
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
tooltip:
'Back to Login'
,
),
iconTheme:
IconThemeData
(
color:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
),
foregroundColor:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
),
body:
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
...
...
@@ -98,10 +87,6 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isDarkMode
?
kLightBackground
:
kDarkBackground
,
foregroundColor:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
30
,
vertical:
15
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
child:
const
Text
(
'Back to Login'
),
),
...
...
@@ -152,26 +137,11 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
?
const
CircularProgressIndicator
()
:
ElevatedButton
(
onPressed:
_resetPassword
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isDarkMode
?
kLightBackground
:
kDarkBackground
,
foregroundColor:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
30
,
vertical:
15
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
child:
const
Text
(
'Send Reset Email'
),
),
const
SizedBox
(
height:
16
),
TextButton
(
onPressed:
()
=>
Navigator
.
pop
(
context
),
style:
TextButton
.
styleFrom
(
foregroundColor:
isDarkMode
?
kLightBackground
:
kDarkBackground
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
30
,
vertical:
15
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
child:
const
Text
(
'Back to Login'
),
),
],
...
...
lib/screens/graph_screen.dart
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:provider/provider.dart'
;
import
'../providers/graph_provider.dart'
;
import
'../providers/settings_provider.dart'
;
// Import SettingsProvider
class
GraphScreen
extends
StatelessWidget
{
const
GraphScreen
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
);
final
bool
darkMode
=
settingsProvider
.
darkMode
;
final
ThemeData
currentTheme
=
Theme
.
of
(
context
);
final
Color
placeholderIconColor
=
darkMode
?
Colors
.
grey
[
700
]!
:
Colors
.
grey
[
400
]!;
final
Color
primaryTextColor
=
darkMode
?
Colors
.
white
:
Colors
.
black87
;
final
Color
secondaryTextColor
=
darkMode
?
Colors
.
grey
[
400
]!
:
Colors
.
grey
[
600
]!;
final
Color
fabBackgroundColor
=
darkMode
?
Colors
.
deepPurple
.
shade300
:
Colors
.
deepPurple
;
final
Color
fabIconColor
=
darkMode
?
Colors
.
black
:
Colors
.
white
;
return
Scaffold
(
// Scaffold background is handled by MaterialApp theme
appBar:
AppBar
(
backgroundColor:
Theme
.
of
(
context
).
colorScheme
.
inversePrimary
,
title:
const
Text
(
'Graph Visualization'
),
backgroundColor:
const
Color
(
0xFF0D2B0D
),
title:
const
Text
(
'Graph Visualization'
,
style:
TextStyle
(
color:
Colors
.
white
),
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
icon:
const
Icon
(
Icons
.
arrow_back
,
color:
Colors
.
white
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
actions:
[
IconButton
(
icon:
const
Icon
(
Icons
.
add
),
icon:
const
Icon
(
Icons
.
add
,
color:
Colors
.
white
),
onPressed:
()
{
// Add node functionality
_showAddNodeDialog
(
context
);
_showAddNodeDialog
(
context
,
darkMode
,
currentTheme
);
},
),
IconButton
(
icon:
const
Icon
(
Icons
.
refresh
),
icon:
const
Icon
(
Icons
.
refresh
,
color:
Colors
.
white
),
onPressed:
()
{
Provider
.
of
<
GraphProvider
>(
context
,
listen:
false
).
clearGraph
();
},
...
...
@@ -43,20 +57,20 @@ class GraphScreen extends StatelessWidget {
Icon
(
Icons
.
account_tree_outlined
,
size:
80
,
color:
Colors
.
grey
[
400
]
,
color:
placeholderIconColor
,
),
const
SizedBox
(
height:
16
),
Text
(
'No graph data yet'
,
style:
Theme
.
of
(
context
)
.
textTheme
.
headlineSmall
?.
copyWith
(
color:
Colors
.
grey
[
600
]
,
style:
currentTheme
.
textTheme
.
headlineSmall
?.
copyWith
(
color:
secondaryTextColor
,
),
),
const
SizedBox
(
height:
8
),
Text
(
'Add nodes to start building your graph'
,
style:
Theme
.
of
(
context
)
.
textTheme
.
bodyMedium
?.
copyWith
(
color:
Colors
.
grey
[
500
]
,
style:
currentTheme
.
textTheme
.
bodyMedium
?.
copyWith
(
color:
secondaryTextColor
,
),
),
],
...
...
@@ -64,18 +78,18 @@ class GraphScreen extends StatelessWidget {
else
Expanded
(
child:
CustomPaint
(
painter:
GraphPainter
(
graphProvider
.
nodes
,
graphProvider
.
edges
),
painter:
GraphPainter
(
graphProvider
.
nodes
,
graphProvider
.
edges
,
darkMode
),
child:
Container
(),
),
),
const
SizedBox
(
height:
20
),
Text
(
'Nodes:
${graphProvider.nodes.length}
'
,
style:
Theme
.
of
(
context
).
textTheme
.
bodyLarge
,
style:
currentTheme
.
textTheme
.
bodyLarge
?.
copyWith
(
color:
primaryTextColor
)
,
),
Text
(
'Edges:
${graphProvider.edges.length}
'
,
style:
Theme
.
of
(
context
).
textTheme
.
bodyLarge
,
style:
currentTheme
.
textTheme
.
bodyLarge
?.
copyWith
(
color:
primaryTextColor
)
,
),
],
),
...
...
@@ -83,39 +97,51 @@ class GraphScreen extends StatelessWidget {
},
),
floatingActionButton:
FloatingActionButton
(
onPressed:
()
=>
_showAddNodeDialog
(
context
),
onPressed:
()
=>
_showAddNodeDialog
(
context
,
darkMode
,
currentTheme
),
tooltip:
'Add Node'
,
child:
const
Icon
(
Icons
.
add
),
backgroundColor:
fabBackgroundColor
,
child:
Icon
(
Icons
.
add
,
color:
fabIconColor
),
),
);
}
void
_showAddNodeDialog
(
BuildContext
context
)
{
void
_showAddNodeDialog
(
BuildContext
context
,
bool
darkMode
,
ThemeData
currentTheme
)
{
final
TextEditingController
controller
=
TextEditingController
();
final
Color
dialogBackgroundColor
=
darkMode
?
Colors
.
grey
[
800
]!
:
Colors
.
white
;
final
Color
dialogTextColor
=
darkMode
?
Colors
.
white
:
Colors
.
black87
;
final
Color
hintTextColor
=
darkMode
?
Colors
.
grey
[
400
]!
:
Colors
.
grey
[
600
]!;
final
Color
buttonTextColor
=
darkMode
?
Colors
.
deepPurple
.
shade200
:
Colors
.
deepPurple
;
showDialog
(
context:
context
,
builder:
(
BuildContext
c
ontext
)
{
builder:
(
BuildContext
dialogC
ontext
)
{
return
AlertDialog
(
title:
const
Text
(
'Add Node'
),
backgroundColor:
dialogBackgroundColor
,
title:
Text
(
'Add Node'
,
style:
TextStyle
(
color:
dialogTextColor
)),
content:
TextField
(
controller:
controller
,
decoration:
const
InputDecoration
(
style:
TextStyle
(
color:
dialogTextColor
),
decoration:
InputDecoration
(
hintText:
'Enter node label'
,
hintStyle:
TextStyle
(
color:
hintTextColor
),
),
autofocus:
true
,
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
c
ontext
).
pop
(),
child:
const
Text
(
'Cancel'
),
onPressed:
()
=>
Navigator
.
of
(
dialogC
ontext
).
pop
(),
child:
Text
(
'Cancel'
,
style:
TextStyle
(
color:
buttonTextColor
)
),
),
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
darkMode
?
Colors
.
deepPurple
.
shade300
:
Colors
.
deepPurple
,
foregroundColor:
darkMode
?
Colors
.
black
:
Colors
.
white
,
),
onPressed:
()
{
if
(
controller
.
text
.
isNotEmpty
)
{
Provider
.
of
<
GraphProvider
>(
context
,
listen:
false
)
.
addNode
(
controller
.
text
);
Navigator
.
of
(
c
ontext
).
pop
();
Navigator
.
of
(
dialogC
ontext
).
pop
();
}
},
child:
const
Text
(
'Add'
),
...
...
@@ -130,20 +156,22 @@ class GraphScreen extends StatelessWidget {
class
GraphPainter
extends
CustomPainter
{
final
List
<
GraphNode
>
nodes
;
final
List
<
GraphEdge
>
edges
;
final
bool
darkMode
;
GraphPainter
(
this
.
nodes
,
this
.
edges
);
GraphPainter
(
this
.
nodes
,
this
.
edges
,
this
.
darkMode
);
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
final
Paint
nodePaint
=
Paint
()
..
color
=
Colors
.
blu
e
..
color
=
darkMode
?
Colors
.
tealAccent
[
400
]!
:
Colors
.
blue
// Brighter node for dark mod
e
..
style
=
PaintingStyle
.
fill
;
final
Paint
edgePaint
=
Paint
()
..
color
=
Colors
.
grey
..
color
=
darkMode
?
Colors
.
grey
[
600
]!
:
Colors
.
grey
// Lighter edge for dark mode
..
strokeWidth
=
2.0
..
style
=
PaintingStyle
.
stroke
;
final
Color
nodeLabelColor
=
darkMode
?
Colors
.
black
:
Colors
.
white
;
// Ensure contrast with node color
// Draw edges first
for
(
final
edge
in
edges
)
{
...
...
@@ -165,11 +193,10 @@ class GraphPainter extends CustomPainter {
nodePaint
,
);
// Draw node label
final
textPainter
=
TextPainter
(
text:
TextSpan
(
text:
node
.
label
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
12
),
style:
TextStyle
(
color:
nodeLabelColor
,
fontSize:
12
),
),
textDirection:
TextDirection
.
ltr
,
);
...
...
@@ -185,5 +212,10 @@ class GraphPainter extends CustomPainter {
}
@override
bool
shouldRepaint
(
covariant
CustomPainter
oldDelegate
)
=>
true
;
bool
shouldRepaint
(
covariant
CustomPainter
oldDelegate
)
{
if
(
oldDelegate
is
GraphPainter
)
{
return
oldDelegate
.
darkMode
!=
darkMode
||
oldDelegate
.
nodes
!=
nodes
||
oldDelegate
.
edges
!=
edges
;
}
return
true
;
}
}
lib/screens/home_screen.dart
View file @
23d89820
This diff is collapsed.
Click to expand it.
lib/login.dart
→
lib/
screens/
login.dart
View file @
23d89820
This diff is collapsed.
Click to expand it.
lib/screens/map_screen.dart
0 → 100644
View file @
23d89820
import
'dart:async'
;
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'
;
class
MapScreen
extends
StatefulWidget
{
const
MapScreen
({
super
.
key
});
@override
State
<
MapScreen
>
createState
()
=>
_MapScreenState
();
}
class
_MapScreenState
extends
State
<
MapScreen
>
{
final
Completer
<
GoogleMapController
>
_controller
=
Completer
();
LocationData
?
_currentLocation
;
StreamSubscription
<
LocationData
>?
_locationSubscription
;
final
User
?
user
=
FirebaseAuth
.
instance
.
currentUser
;
static
const
CameraPosition
_kGooglePlex
=
CameraPosition
(
target:
LatLng
(
37.42796133580664
,
-
122.085749655962
),
zoom:
14.4746
,
);
@override
void
initState
()
{
super
.
initState
();
_initializeLocation
();
}
Future
<
void
>
_initializeLocation
()
async
{
Location
location
=
Location
();
bool
serviceEnabled
;
PermissionStatus
permissionGranted
;
serviceEnabled
=
await
location
.
serviceEnabled
();
if
(!
serviceEnabled
)
{
serviceEnabled
=
await
location
.
requestService
();
if
(!
serviceEnabled
)
{
return
;
}
}
permissionGranted
=
await
location
.
hasPermission
();
if
(
permissionGranted
==
PermissionStatus
.
denied
)
{
permissionGranted
=
await
location
.
requestPermission
();
if
(
permissionGranted
!=
PermissionStatus
.
granted
)
{
return
;
}
}
_currentLocation
=
await
location
.
getLocation
();
if
(
_currentLocation
!=
null
)
{
_moveCameraToLocation
(
_currentLocation
!);
}
_locationSubscription
=
location
.
onLocationChanged
.
listen
((
LocationData
newLocation
)
{
if
(
mounted
)
{
setState
(()
{
_currentLocation
=
newLocation
;
});
_moveCameraToLocation
(
newLocation
);
}
});
}
Future
<
void
>
_moveCameraToLocation
(
LocationData
locationData
)
async
{
final
GoogleMapController
controller
=
await
_controller
.
future
;
controller
.
animateCamera
(
CameraUpdate
.
newCameraPosition
(
CameraPosition
(
target:
LatLng
(
locationData
.
latitude
!,
locationData
.
longitude
!),
zoom:
15.0
,
),
));
}
@override
void
dispose
()
{
_locationSubscription
?.
cancel
();
super
.
dispose
();
}
@override
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
(),
),
actions:
[
if
(
user
?.
email
!=
null
)
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
16.0
),
child:
Center
(
child:
Text
(
user
!.
email
!,
style:
const
TextStyle
(
fontSize:
12
,
),
),
),
),
],
),
body:
_currentLocation
==
null
?
const
Center
(
child:
CircularProgressIndicator
())
:
GoogleMap
(
mapType:
MapType
.
normal
,
initialCameraPosition:
_kGooglePlex
,
onMapCreated:
(
GoogleMapController
controller
)
{
_controller
.
complete
(
controller
);
},
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'
),
),
},
),
);
}
}
lib/screens/settings_screen.dart
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:provider/provider.dart'
;
import
'../providers/settings_provider.dart'
;
// Import SettingsProvider
class
SettingsScreen
extends
StatefulWidget
{
const
SettingsScreen
({
super
.
key
});
...
...
@@ -8,20 +10,33 @@ class SettingsScreen extends StatefulWidget {
}
class
_SettingsScreenState
extends
State
<
SettingsScreen
>
{
bool
_darkMode
=
false
;
double
_nodeSize
=
20.0
;
double
_edgeWidth
=
2.0
;
Color
_nodeColor
=
Colors
.
blue
;
Color
_edgeColor
=
Colors
.
grey
;
// State variables for graph visualization have been removed
@override
Widget
build
(
BuildContext
context
)
{
final
settingsProvider
=
Provider
.
of
<
SettingsProvider
>(
context
);
final
bool
darkMode
=
settingsProvider
.
darkMode
;
final
ThemeData
currentTheme
=
Theme
.
of
(
context
);
// Colors will now be primarily driven by MaterialApp's theme/darkTheme
// but we can still make specific overrides or use theme colors directly.
final
Color
primaryTextColor
=
darkMode
?
Colors
.
white
:
Colors
.
black87
;
final
Color
secondaryTextColor
=
darkMode
?
Colors
.
grey
[
400
]!
:
Colors
.
grey
[
600
]!;
final
Color
iconColor
=
darkMode
?
Colors
.
white70
:
Colors
.
black54
;
final
Color
sliderActiveColor
=
darkMode
?
Colors
.
deepPurple
.
shade300
:
Colors
.
deepPurple
;
final
Color
cardBackgroundColor
=
darkMode
?
Colors
.
grey
[
850
]!
:
currentTheme
.
cardColor
;
return
Scaffold
(
// Scaffold background color will be handled by MaterialApp theme
appBar:
AppBar
(
backgroundColor:
Theme
.
of
(
context
).
colorScheme
.
inversePrimary
,
title:
const
Text
(
'Settings'
),
backgroundColor:
const
Color
(
0xFF0D2B0D
),
title:
const
Text
(
'Settings'
,
style:
TextStyle
(
color:
Colors
.
white
),
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
icon:
const
Icon
(
Icons
.
arrow_back
,
color:
Colors
.
white
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
),
...
...
@@ -29,6 +44,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
padding:
const
EdgeInsets
.
all
(
16
),
children:
[
Card
(
color:
cardBackgroundColor
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
...
...
@@ -36,103 +52,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
children:
[
Text
(
'Appearance'
,
style:
Theme
.
of
(
context
).
textTheme
.
titleLarge
,
style:
currentTheme
.
textTheme
.
titleLarge
?.
copyWith
(
color:
primaryTextColor
)
,
),
const
SizedBox
(
height:
16
),
SwitchListTile
(
title:
const
Text
(
'Dark Mode'
),
subtitle:
const
Text
(
'Toggle dark theme'
),
value:
_darkMode
,
title:
Text
(
'Dark Mode'
,
style:
TextStyle
(
color:
primaryTextColor
)
),
subtitle:
Text
(
'Toggle dark theme'
,
style:
TextStyle
(
color:
secondaryTextColor
)
),
value:
darkMode
,
// Use value from provider
onChanged:
(
value
)
{
setState
(()
{
_darkMode
=
value
;
});
settingsProvider
.
toggleDarkMode
();
// Call provider method
},
activeColor:
sliderActiveColor
,
),
],
),
),
),
const
SizedBox
(
height:
16
),
Card
(
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
'Graph Visualization'
,
style:
Theme
.
of
(
context
).
textTheme
.
titleLarge
,
),
const
SizedBox
(
height:
16
),
ListTile
(
title:
const
Text
(
'Node Size'
),
subtitle:
Text
(
'
${_nodeSize.round()}
px'
),
trailing:
SizedBox
(
width:
200
,
child:
Slider
(
value:
_nodeSize
,
min:
10
,
max:
50
,
divisions:
40
,
onChanged:
(
value
)
{
setState
(()
{
_nodeSize
=
value
;
});
},
),
),
),
ListTile
(
title:
const
Text
(
'Edge Width'
),
subtitle:
Text
(
'
${_edgeWidth.round()}
px'
),
trailing:
SizedBox
(
width:
200
,
child:
Slider
(
value:
_edgeWidth
,
min:
1
,
max:
10
,
divisions:
9
,
onChanged:
(
value
)
{
setState
(()
{
_edgeWidth
=
value
;
});
},
),
),
),
ListTile
(
title:
const
Text
(
'Node Color'
),
trailing:
Container
(
width:
40
,
height:
40
,
decoration:
BoxDecoration
(
color:
_nodeColor
,
shape:
BoxShape
.
circle
,
border:
Border
.
all
(
color:
Colors
.
grey
),
),
),
onTap:
()
=>
_showColorPicker
(
context
,
true
),
),
ListTile
(
title:
const
Text
(
'Edge Color'
),
trailing:
Container
(
width:
40
,
height:
40
,
decoration:
BoxDecoration
(
color:
_edgeColor
,
shape:
BoxShape
.
circle
,
border:
Border
.
all
(
color:
Colors
.
grey
),
),
),
onTap:
()
=>
_showColorPicker
(
context
,
false
),
),
],
),
),
),
// The "Graph Visualization" card has been completely removed.
const
SizedBox
(
height:
16
),
Card
(
color:
cardBackgroundColor
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
...
...
@@ -140,18 +80,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
children:
[
Text
(
'About'
,
style:
Theme
.
of
(
context
).
textTheme
.
titleLarge
,
style:
currentTheme
.
textTheme
.
titleLarge
?.
copyWith
(
color:
primaryTextColor
)
,
),
const
SizedBox
(
height:
16
),
const
ListTile
(
leading:
Icon
(
Icons
.
info
),
title:
Text
(
'Version'
),
subtitle:
Text
(
'1.0.0'
),
ListTile
(
leading:
Icon
(
Icons
.
info
,
color:
iconColor
),
title:
Text
(
'Version'
,
style:
TextStyle
(
color:
primaryTextColor
)
),
subtitle:
Text
(
'1.0.0'
,
style:
TextStyle
(
color:
secondaryTextColor
)
),
),
const
ListTile
(
leading:
Icon
(
Icons
.
code
),
title:
Text
(
'Built with Flutter'
),
subtitle:
Text
(
'GraphGo - Graph Visualization App'
),
ListTile
(
leading:
Icon
(
Icons
.
code
,
color:
iconColor
),
title:
Text
(
'Built with Flutter'
,
style:
TextStyle
(
color:
primaryTextColor
)
),
subtitle:
Text
(
'GraphGo - Graph Visualization App'
,
style:
TextStyle
(
color:
secondaryTextColor
)
),
),
],
),
...
...
@@ -162,59 +102,5 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
void
_showColorPicker
(
BuildContext
context
,
bool
isNodeColor
)
{
final
List
<
Color
>
colors
=
[
Colors
.
red
,
Colors
.
blue
,
Colors
.
green
,
Colors
.
orange
,
Colors
.
purple
,
Colors
.
teal
,
Colors
.
pink
,
Colors
.
indigo
,
];
showDialog
(
context:
context
,
builder:
(
BuildContext
context
)
{
return
AlertDialog
(
title:
Text
(
isNodeColor
?
'Select Node Color'
:
'Select Edge Color'
),
content:
SizedBox
(
width:
300
,
child:
GridView
.
builder
(
shrinkWrap:
true
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
4
,
crossAxisSpacing:
8
,
mainAxisSpacing:
8
,
),
itemCount:
colors
.
length
,
itemBuilder:
(
context
,
index
)
{
final
color
=
colors
[
index
];
return
GestureDetector
(
onTap:
()
{
setState
(()
{
if
(
isNodeColor
)
{
_nodeColor
=
color
;
}
else
{
_edgeColor
=
color
;
}
});
Navigator
.
of
(
context
).
pop
();
},
child:
Container
(
decoration:
BoxDecoration
(
color:
color
,
shape:
BoxShape
.
circle
,
border:
Border
.
all
(
color:
Colors
.
grey
),
),
),
);
},
),
),
);
},
);
}
// _showColorPicker method has been removed as it's no longer needed.
}
lib/signup.dart
→
lib/s
creens/s
ignup.dart
View file @
23d89820
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:go_router/go_router.dart'
;
import
'services/google_auth_service.dart'
;
import
'colors.dart'
;
import
'../services/google_auth_service.dart'
;
import
'../colors.dart'
;
class
SignupPage
extends
StatefulWidget
{
const
SignupPage
({
super
.
key
});
...
...
@@ -26,7 +25,7 @@ class _SignupPageState extends State<SignupPage> {
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'\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
;
}
...
...
@@ -49,16 +48,18 @@ class _SignupPageState extends State<SignupPage> {
'created_at'
:
Timestamp
.
now
(),
});
// Navigate to home and refresh the state
if
(
mounted
)
{
context
.
go
(
'/'
);
// After signing up, go to the map screen
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Signup Failed:
${e.toString()}
"
)),
);
}
finally
{
setState
(()
=>
_isLoading
=
false
);
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
}
...
...
@@ -66,32 +67,21 @@ class _SignupPageState extends State<SignupPage> {
setState
(()
=>
_isLoading
=
true
);
try
{
final
UserCredential
?
userCredential
=
await
GoogleAuthService
.
signInWithGoogle
();
// Check if user is signed in (either through successful credential or error handling)
await
GoogleAuthService
.
signInWithGoogle
();
final
user
=
FirebaseAuth
.
instance
.
currentUser
;
if
(
user
!=
null
)
{
if
(
mounted
)
{
context
.
go
(
'/'
);
}
}
else
if
(
userCredential
==
null
)
{
// Handle the case where Google Sign-In had issues but user might still be signed in
if
(
user
!=
null
&&
mounted
)
{
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/map'
);
}
}
catch
(
e
)
{
if
(
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Google Sign-In had issues, but you might still be signed in
"
)),
SnackBar
(
content:
Text
(
"Google Sign-Up Failed:
${e.toString()}
"
)),
);
// Check again after a short delay
await
Future
.
delayed
(
const
Duration
(
seconds:
1
));
final
userAfterDelay
=
FirebaseAuth
.
instance
.
currentUser
;
if
(
userAfterDelay
!=
null
&&
mounted
)
{
context
.
go
(
'/'
);
}
}
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
"Google Sign-Up Failed:
${e.toString()}
"
)),
);
}
finally
{
setState
(()
=>
_isLoading
=
false
);
if
(
mounted
)
{
setState
(()
=>
_isLoading
=
false
);
}
}
}
...
...
@@ -100,26 +90,14 @@ class _SignupPageState extends State<SignupPage> {
final
bool
isDarkMode
=
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
;
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'GraphGo Sign Up'
,
style:
TextStyle
(
fontFamily:
'Impact'
,
// Ensure "Impact" is available in your fonts
fontSize:
24
,
// Adjust size as needed
fontStyle:
FontStyle
.
italic
,
fontWeight:
FontWeight
.
bold
,
color:
kPrimaryColor
,
),
),
title:
const
Text
(
'GraphGo Sign Up'
),
// The back button is now handled correctly by the Navigator
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
context
.
go
(
'/'
),
tooltip:
'Back to
Home
'
,
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
// Corrected line
tooltip:
'Back to
Login
'
,
),
iconTheme:
IconThemeData
(
color:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
),
foregroundColor:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Form
(
...
...
@@ -165,63 +143,31 @@ class _SignupPageState extends State<SignupPage> {
validator:
(
value
)
=>
value
!=
_passwordController
.
text
?
"Passwords do not match"
:
null
,
),
const
SizedBox
(
height:
20
),
// Google Sign-In Button
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
.
icon
(
onPressed:
_isLoading
?
null
:
_signUpWithGoogle
,
icon:
const
Icon
(
Icons
.
login
,
size:
20
,
color:
Colors
.
blue
,
),
icon:
const
Icon
(
Icons
.
login
,
size:
20
,
color:
Colors
.
blue
),
label:
const
Text
(
'Sign up with Google'
),
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
30
,
vertical:
15
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
),
),
const
SizedBox
(
height:
16
),
// Divider
Row
(
children:
[
const
Expanded
(
child:
Divider
()),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'OR'
,
style:
TextStyle
(
color:
isDarkMode
?
kDarkText
:
kLightText
,
fontWeight:
FontWeight
.
bold
,
),
),
child:
Text
(
'OR'
),
),
const
Expanded
(
child:
Divider
()),
],
),
const
SizedBox
(
height:
16
),
// Email Sign-Up Button
_isLoading
?
const
CircularProgressIndicator
()
:
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isDarkMode
?
kLightBackground
:
kDarkBackground
,
foregroundColor:
isDarkMode
?
kDarkBackground
:
kLightBackground
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
30
,
vertical:
15
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
onPressed:
_signUp
,
child:
const
Text
(
'Sign Up with Email'
),
),
...
...
pubspec.lock
View file @
23d89820
...
...
@@ -217,6 +217,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
...
...
@@ -807,6 +815,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
...
...
@@ -879,6 +911,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
url: "https://pub.dev"
source: hosted
version: "2.4.13"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
...
...
@@ -1076,6 +1164,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
...
...
pubspec.yaml
View file @
23d89820
...
...
@@ -14,17 +14,28 @@ dependencies:
go_router
:
^12.1.3
flutter_svg
:
^2.0.9
provider
:
^6.1.1
# ✅ SharedPrefs for remember me
shared_preferences
:
^2.2.3
# ✅ Firebase + Auth + Firestore + Storage
firebase_core
:
^2.24.2
firebase_auth
:
^4.15.3
cloud_firestore
:
^4.13.6
geocoding
:
^2.1.1
http
:
^1.1.0
firebase_storage
:
^11.5.6
# ✅ Maps + Location
google_maps_flutter
:
^2.5.0
location
:
^5.0.3
uuid
:
^4.2.1
geocoding
:
^2.1.1
# ✅ Google Sign-In
google_sign_in
:
^6.2.1
# ✅ Extras
http
:
^1.1.0
uuid
:
^4.2.1
image_picker
:
^1.0.4
firebase_storage
:
^11.5.6
dev_dependencies
:
flutter_test
:
...
...
@@ -37,7 +48,7 @@ dev_dependencies:
flutter
:
uses-material-design
:
true
assets
:
-
assets/images/
-
assets/icons/
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