Files
velocity-pay-flutter/lib/screens/onboarding/login/login_screen.dart
2026-06-01 14:45:42 +02:00

262 lines
8.5 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:google_sign_in_web/web_only.dart' as web;
import 'package:qpay/models/responsive_policy.dart';
import 'package:qpay/screens/onboarding/login/login_controller.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:skeletonizer/skeletonizer.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
late SharedPreferences prefs;
late GoogleSignIn googleSignIn;
final _formKey = GlobalKey<FormState>();
bool _loading = false;
List<String> scopes = <String>[
'https://www.googleapis.com/auth/contacts.readonly',
];
late LoginController _loginController;
StreamSubscription<GoogleSignInAuthenticationEvent>? _authSubscription;
@override
void initState() {
super.initState();
_loginController = LoginController(context);
googleSignIn = GoogleSignIn.instance;
if (kIsWeb) {
googleSignIn.initialize();
_authSubscription = googleSignIn.authenticationEvents
.handleError(_handleAuthenticationError)
.listen(_handleAuthenticationEvent);
} else {
unawaited(
googleSignIn.initialize(serverClientId: dotenv.env['CLIENTID']),
);
}
}
Future<void> signInWithGoogle() async {
if (GoogleSignIn.instance.supportsAuthenticate()) {
_authSubscription?.cancel();
unawaited(
GoogleSignIn.instance.authenticate().then((value) async {
_authSubscription = googleSignIn.authenticationEvents
.handleError(_handleAuthenticationError)
.listen(_handleAuthenticationEvent);
}),
);
}
}
void saveUser(GoogleSignInAccount user) async {
final Map<String, dynamic> data = <String, dynamic>{
'displayName': user.displayName,
'email': user.email,
'id': user.id,
'photoUrl': user.photoUrl,
};
prefs = await SharedPreferences.getInstance();
if (prefs.getString("googleUser") == null) {
prefs.setString("googleUser", jsonEncode(data));
}
}
Future<void> _handleAuthenticationEvent(
GoogleSignInAuthenticationEvent event,
) async {
if (!mounted) return;
// #docregion CheckAuthorization
final GoogleSignInAccount? user = // ...
// #enddocregion CheckAuthorization
switch (event) {
GoogleSignInAuthenticationEventSignIn() => event.user,
GoogleSignInAuthenticationEventSignOut() => null,
};
if (user == null || !mounted) return;
// Check for existing authorization.
// #docregion CheckAuthorization
final GoogleSignInClientAuthorization? authorization = await user
?.authorizationClient
.authorizationForScopes(scopes);
// #enddocregion CheckAuthorization
print(user);
saveUser(user);
if (!mounted) return;
final response = await _loginController.login(user.email, user.id);
if (response.isSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Login successful!"),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.green,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(response.error ?? "Login failed"),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.deepOrange,
),
);
}
if (!mounted) return;
context.go('/');
}
Future<void> _handleAuthenticationError(Object e) async {
print(e.toString());
}
@override
void dispose() {
_authSubscription?.cancel();
_loginController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Form(
key: _formKey,
child: Center(
child: LayoutBuilder(
builder: (context, constraints) {
final maxWidth = constraints.maxWidth > ResponsivePolicy.md
? ResponsivePolicy.md.toDouble()
: constraints.maxWidth;
return SizedBox(
width: maxWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 75),
Text(
'Login',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
SizedBox(height: 5),
Text(
'Welcome back to Velocity',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
SizedBox(height: 30),
Image.asset('assets/password.png', width: 300),
Text(
'Tap on your preferred social media platform to get started',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
// Username Field
SizedBox(height: 20),
// Forgot Password Link
// Divider with "or" text
Row(
children: [
Expanded(
child: Divider(color: Colors.grey.shade300),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
'choose from the options below to continue',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
Expanded(
child: Divider(color: Colors.grey.shade300),
),
],
),
SizedBox(height: 30),
// Social Login Buttons
if (GoogleSignIn.instance.supportsAuthenticate())
_buildGoogleButton()
else ...<Widget>[if (kIsWeb) web.renderButton()],
SizedBox(height: 40),
TextButton(
onPressed: () {
context.go('/');
},
child: Text(
'Continue as guest?',
style: TextStyle(fontSize: 14),
),
),
],
),
);
},
),
),
),
),
),
);
}
Widget _buildGoogleButton() {
return Skeletonizer(
enabled: _loading,
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
signInWithGoogle();
},
icon: Image.asset('assets/google.png', width: 20, height: 20),
label: Text('Google', style: TextStyle(fontSize: 16)),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
SizedBox(width: 16),
],
),
);
}
}