Commit 23d89820 authored by aleenaasghar's avatar aleenaasghar

Added home page, login, and map integration

parent 20408a09
......@@ -27,3 +27,5 @@ plugins {
}
include(":app")
rootProject.name = "GraphGo"
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 'signup.dart';
import 'screens/login.dart';
import 'screens/signup.dart';
import 'screens/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
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();
}
}
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'),
),
],
......
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 context) {
builder: (BuildContext dialogContext) {
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(context).pop(),
child: const Text('Cancel'),
onPressed: () => Navigator.of(dialogContext).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(context).pop();
Navigator.of(dialogContext).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.blue
..color = darkMode ? Colors.tealAccent[400]! : Colors.blue // Brighter node for dark mode
..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;
}
}
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:location/location.dart';
import '../providers/delivery_provider.dart';
import '../colors.dart';
import '../providers/settings_provider.dart';
class HomeScreen extends StatefulWidget {
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
GoogleMapController? _mapController;
Location _location = Location();
LocationData? _currentLocation;
bool _isLocationLoading = true;
bool _locationPermissionGranted = false;
static const LatLng _defaultLocation = LatLng(40.7128, -74.0060); // New York City
Set<Marker> _markers = {};
@override
void initState() {
super.initState();
_initializeLocation();
// Listen to authentication state changes
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (mounted) {
setState(() {});
}
});
}
Future<void> _initializeLocation() async {
try {
// Check if location service is enabled
bool serviceEnabled = await _location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await _location.requestService();
if (!serviceEnabled) {
setState(() {
_isLocationLoading = false;
});
return;
}
}
// Check location permission
PermissionStatus permissionGranted = await _location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await _location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
setState(() {
_isLocationLoading = false;
});
return;
}
}
setState(() {
_locationPermissionGranted = true;
});
// Get current location
_currentLocation = await _location.getLocation();
if (_currentLocation != null) {
setState(() {
_isLocationLoading = false;
});
// Move camera to current location
if (_mapController != null) {
_mapController!.animateCamera(
CameraUpdate.newLatLng(
LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!),
void _handleLogout(BuildContext context) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
return Center(
child: Material(
borderRadius: BorderRadius.circular(15),
child: Container(
width: MediaQuery.of(context).size.width - 40,
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Are you sure you want to logout?',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(
width: 120,
child: ElevatedButton( // Changed to ElevatedButton
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey.shade300, // Neutral background
foregroundColor: Colors.black87, // Darker text for contrast
),
child: const Text('No', style: TextStyle(fontSize: 16)),
),
),
SizedBox(
width: 120,
child: ElevatedButton(
onPressed: () {
final settings = Provider.of<SettingsProvider>(context, listen: false);
settings.logout();
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple.shade900,
foregroundColor: Colors.white,
),
child: const Text('Yes', style: TextStyle(fontSize: 16)),
),
),
],
),
],
),
),
);
}
}
),
);
},
transitionBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1),
end: Offset.zero,
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOut)),
child: child,
);
},
);
}
// Listen to location changes
_location.onLocationChanged.listen((LocationData locationData) {
if (mounted) {
setState(() {
_currentLocation = locationData;
});
// Update camera position if needed
if (_mapController != null) {
_mapController!.animateCamera(
CameraUpdate.newLatLng(
LatLng(locationData.latitude!, locationData.longitude!),
),
);
}
}
});
} catch (e) {
print('Error getting location: $e');
setState(() {
_isLocationLoading = false;
});
}
Widget _buildStartDrivingButton(BuildContext context, bool darkMode) {
return ElevatedButton(
key: const ValueKey('startButton'),
onPressed: () {
Navigator.of(context).pushNamed('/login');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple.shade900,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 20),
side: BorderSide(color: const Color(0xFF0D2B0D), width: darkMode ? 2 : 4),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
minimumSize: const Size(200, 60),
),
child: const Text(
'Start Driving',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
);
}
Future<void> _getCurrentLocation() async {
try {
_currentLocation = await _location.getLocation();
if (_currentLocation != null && _mapController != null) {
_mapController!.animateCamera(
CameraUpdate.newLatLng(
LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!),
Widget _buildLoggedInView(BuildContext context, bool darkMode) {
return KeyedSubtree(
key: const ValueKey('loggedInView'),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pushNamed('/map');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple.shade900,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 20),
side: BorderSide(color: const Color(0xFF0D2B0D), width: darkMode ? 2 : 4),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
minimumSize: const Size(200, 60),
),
child: const Text(
'View Route',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
);
}
} catch (e) {
print('Error getting current location: $e');
}
],
),
);
}
@override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser;
final isLoggedIn = user != null;
return Scaffold(
body: Stack(
children: [
// Full-screen Google Maps
GoogleMap(
onMapCreated: (GoogleMapController controller) {
_mapController = controller;
// Move to current location if available
if (_currentLocation != null) {
controller.animateCamera(
CameraUpdate.newLatLng(
LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!),
),
);
}
},
initialCameraPosition: CameraPosition(
target: _currentLocation != null
? LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!)
: _defaultLocation,
zoom: 15,
return Consumer<SettingsProvider>(
builder: (context, settings, child) {
final bool darkMode = settings.darkMode;
final ThemeData currentTheme = Theme.of(context);
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);
Widget currentActionArea;
if (settings.isLoggedIn) {
currentActionArea = _buildLoggedInView(context, darkMode);
} else {
currentActionArea = _buildStartDrivingButton(context, darkMode);
}
return Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xFF0D2B0D),
leading: IconButton(
icon: const Icon(Icons.settings, color: Colors.white),
onPressed: () => Navigator.of(context).pushNamed('/settings'),
),
markers: _markers,
mapType: MapType.normal,
myLocationEnabled: _locationPermissionGranted,
myLocationButtonEnabled: false, // We'll add our own button
zoomControlsEnabled: true,
compassEnabled: true,
mapToolbarEnabled: false,
buildingsEnabled: true,
trafficEnabled: false,
indoorViewEnabled: true,
tiltGesturesEnabled: true,
rotateGesturesEnabled: true,
scrollGesturesEnabled: true,
zoomGesturesEnabled: true,
),
// Top App Bar
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.7),
Colors.black.withOpacity(0.3),
Colors.transparent,
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
title: const Text(
'GraphGo',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
),
centerTitle: true,
actions: [
if (settings.isLoggedIn)
TextButton(
onPressed: () => _handleLogout(context),
style: TextButton.styleFrom(foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 16.0)),
child: const Row(
children: [
// App Title
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'GraphGo',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
),
Text(
'Route Optimization',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 4,
offset: const Offset(0, 1),
),
],
),
),
],
),
),
// User Actions
if (isLoggedIn) ...[
// Location Status
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _locationPermissionGranted
? Colors.green.withOpacity(0.8)
: Colors.orange.withOpacity(0.8),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_locationPermissionGranted ? Icons.location_on : Icons.location_off,
color: Colors.white,
size: 16,
),
const SizedBox(width: 4),
Text(
_locationPermissionGranted ? 'Live' : 'Offline',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
const SizedBox(width: 8),
// Profile Button
CircleAvatar(
radius: 20,
backgroundColor: kPrimaryColor,
child: IconButton(
icon: const Icon(Icons.person, color: Colors.white, size: 20),
onPressed: () => context.go('/profile'),
tooltip: 'Profile',
),
),
] else
// Login Button
ElevatedButton.icon(
onPressed: () => context.go('/login'),
icon: const Icon(Icons.login, size: 18),
label: const Text('Login'),
style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
Text('Logout'),
SizedBox(width: 8),
Icon(Icons.logout, size: 18),
],
),
),
),
),
],
toolbarHeight: kToolbarHeight + 20,
),
// Bottom Control Panel (only for logged-in users)
if (isLoggedIn)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.8),
Colors.black.withOpacity(0.4),
Colors.transparent,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Transform.translate(
offset: const Offset(0, -15),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Transform.translate(
offset: const Offset(0, -25),
child: 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(
'Smarter routes, faster deliveries',
style: currentTheme.textTheme.bodyLarge?.copyWith(
fontSize: (currentTheme.textTheme.bodyLarge?.fontSize ?? 16) * 1.1,
color: sloganTextColor,
),
textAlign: TextAlign.center,
),
],
),
),
child: SafeArea(
child: Consumer<DeliveryProvider>(
builder: (context, deliveryProvider, child) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Quick Stats
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildQuickStat(
'Addresses',
'${deliveryProvider.addressCount}',
Icons.location_on,
kPrimaryColor,
),
Container(
width: 1,
height: 30,
color: Colors.grey.withOpacity(0.3),
),
_buildQuickStat(
'Routes',
'0',
Icons.route,
kAccentColor,
),
Container(
width: 1,
height: 30,
color: Colors.grey.withOpacity(0.3),
),
_buildQuickStat(
'Distance',
'0 km',
Icons.straighten,
Colors.orange,
),
],
),
),
const SizedBox(height: 16),
// Action Buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => context.go('/addresses'),
icon: const Icon(Icons.add_location),
label: const Text('Add Address'),
style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: deliveryProvider.addressCount >= 2
? () => context.go('/optimize')
: null,
icon: const Icon(Icons.route),
label: const Text('Optimize'),
style: ElevatedButton.styleFrom(
backgroundColor: kAccentColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
],
),
const SizedBox(height: 70),
Container(
height: 180,
alignment: Alignment.topCenter,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: currentActionArea,
),
),
),
),
// Current Location Button
Positioned(
bottom: isLoggedIn ? 200 : 100,
right: 16,
child: FloatingActionButton(
onPressed: _getCurrentLocation,
backgroundColor: Colors.white,
foregroundColor: kPrimaryColor,
child: const Icon(Icons.my_location),
tooltip: 'Current Location',
],
),
),
// Loading Overlay
if (_isLocationLoading)
Container(
color: Colors.black.withOpacity(0.3),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(kPrimaryColor),
),
SizedBox(height: 16),
Text(
'Getting your location...',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
),
);
}
Widget _buildQuickStat(String label, String value, IconData icon, Color color) {
return Column(
children: [
Icon(icon, color: color, size: 20),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
);
},
);
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:go_router/go_router.dart';
import 'services/google_auth_service.dart';
import 'forgot_password.dart';
import 'colors.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});
......@@ -17,11 +18,13 @@ class _LoginPageState extends State<LoginPage> {
final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
bool _rememberMe = false;
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(
......@@ -29,86 +32,70 @@ class _LoginPageState extends State<LoginPage> {
password: _passwordController.text.trim(),
);
// Update last sign-in time
await GoogleAuthService.updateLastSignIn();
if (_rememberMe) {
settingsProvider.login();
}
// Navigate to home and refresh the state
if (mounted) {
context.go('/');
Navigator.of(context).pushReplacementNamed('/map');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Login Failed: ${e.toString()}")),
);
} finally {
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();
// Check if user is signed in (either through successful credential or error handling)
final user = FirebaseAuth.instance.currentUser;
if (user != null) {
if (FirebaseAuth.instance.currentUser != null) {
settingsProvider.login();
if (mounted) {
context.go('/');
Navigator.of(context).pushReplacementNamed('/map');
}
} else if (userCredential == null) {
// Handle the case where Google Sign-In had issues but user might still be signed in
} else if (userCredential == null && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Google Sign-In had issues, but you might still be signed in")),
const SnackBar(content: Text("Google Sign-In was cancelled or failed.")),
);
// 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 Login Failed: ${e.toString()}")),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Google Login Failed: ${e.toString()}")),
);
}
} finally {
setState(() => _isLoading = false);
if(mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
title: const Text(
'GraphGo Login',
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 Login'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.go('/'),
onPressed: () => Navigator.of(context).pop(),
tooltip: 'Back to Home',
),
iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground,
),
foregroundColor: isDarkMode ? kDarkBackground : kLightBackground,
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _emailController,
......@@ -122,97 +109,65 @@ class _LoginPageState extends State<LoginPage> {
obscureText: true,
validator: (value) => value!.isEmpty ? "Enter your password" : null,
),
TextButton(
style: TextButton.styleFrom(
foregroundColor:isDarkMode ? kLightBackground : kDarkBackground, // Text color
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15), // Padding
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), // Rounded corners
),
Align(
alignment: Alignment.centerLeft,
child: Row(
children: [
Checkbox(
value: _rememberMe,
onChanged: (val) {
setState(() => _rememberMe = val ?? false);
},
),
const Text("Remember Me"),
],
),
),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ForgotPasswordPage()),
);
Navigator.of(context).pushNamed('/forgot');
},
child: Text("Forgot Password?"),
child: const Text("Forgot Password?"),
),
const SizedBox(height: 20),
// Google Sign-In Button
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _isLoading ? null : _loginWithGoogle,
icon: const Icon(
Icons.login,
size: 20,
color: Colors.blue,
),
icon: const Icon(Icons.login, size: 20, color: Colors.blue),
label: const Text('Sign in 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 Login Button
_isLoading
? const CircularProgressIndicator()
: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _login,
child: const Text('Login with Email'),
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),
),
),
),
),
TextButton(
style: TextButton.styleFrom(
foregroundColor: isDarkMode ? kLightBackground : kDarkBackground, // Text color
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15), // Padding
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), // Rounded corners
),
width: double.infinity,
child: ElevatedButton(
onPressed: _login,
child: const Text('Login with Email'),
),
onPressed: () => context.go('/signup'),
),
TextButton(
onPressed: () => Navigator.of(context).pushNamed('/signup'),
child: const Text("Don't have an account? Sign Up"),
),
// The debug override button has been removed.
],
),
),
......
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'),
),
},
),
);
}
}
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.
}
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'),
),
......
......@@ -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:
......
......@@ -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/
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment