Velar/Frontend/lib/login.dart

514 lines
18 KiB
Dart
Raw Normal View History

2025-08-28 01:34:45 -07:00
// ignore_for_file: use_build_context_synchronously, deprecated_member_use, unused_import
2025-08-14 21:54:25 -07:00
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;
2025-08-28 00:24:32 -07:00
import 'package:monarch/main_pages/HomePage/homepage.dart';
2025-08-27 22:50:24 -07:00
import 'package:monarch/main_pages/HomePage/Components/Voice/voice_dialog.dart';
2025-08-14 21:54:25 -07:00
import 'package:monarch/other_pages/enviroment.dart';
import 'dart:convert';
2025-08-28 01:34:45 -07:00
import 'dart:math' as math;
2025-08-14 21:54:25 -07:00
import 'package:shared_preferences/shared_preferences.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
2025-08-28 01:34:45 -07:00
class _LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
2025-08-14 21:54:25 -07:00
final GoogleSignIn _googleSignIn = GoogleSignIn(
2025-08-15 01:27:26 -07:00
scopes: [
'email',
'profile',
'openid',
2025-08-19 09:46:35 -07:00
'https://www.googleapis.com/auth/gmail.readonly',
2025-08-15 01:27:26 -07:00
],
serverClientId: Environment.serverClientId,
2025-08-14 21:54:25 -07:00
);
bool _isLoading = false;
2025-08-28 01:34:45 -07:00
late AnimationController _fadeController;
late AnimationController _slideController;
late AnimationController _scaleController;
late AnimationController _rotationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_initAnimations();
_startAnimations();
}
void _initAnimations() {
_fadeController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_slideController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_scaleController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_rotationController = AnimationController(
duration: const Duration(seconds: 20),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(
CurvedAnimation(parent: _slideController, curve: Curves.elasticOut),
);
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 2 * math.pi,
).animate(_rotationController);
}
void _startAnimations() {
Future.delayed(const Duration(milliseconds: 300), () {
_fadeController.forward();
});
Future.delayed(const Duration(milliseconds: 500), () {
_slideController.forward();
});
Future.delayed(const Duration(milliseconds: 700), () {
_scaleController.forward();
});
_rotationController.repeat();
}
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
_scaleController.dispose();
_rotationController.dispose();
super.dispose();
}
2025-08-14 21:54:25 -07:00
Future<void> _handleGoogleSignIn() async {
setState(() => _isLoading = true);
try {
final GoogleSignInAccount? account = await _googleSignIn.signIn();
if (account == null) {
setState(() => _isLoading = false);
return;
}
final GoogleSignInAuthentication auth = await account.authentication;
2025-08-15 01:27:26 -07:00
2025-08-14 21:54:25 -07:00
final String? idToken = auth.idToken;
2025-08-15 01:27:26 -07:00
final String? accessToken = auth.accessToken;
2025-08-14 21:54:25 -07:00
2025-08-15 01:27:26 -07:00
if (idToken == null || accessToken == null) {
debugPrint("❌ Failed to get tokens");
2025-08-14 21:54:25 -07:00
return;
}
2025-08-19 20:25:41 -07:00
final uri = Uri.parse("${Environment.baseUrl}/auth/google/token");
2025-08-14 21:54:25 -07:00
final res = await http.post(
uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode({"idToken": idToken}),
);
if (res.statusCode == 200) {
final data = jsonDecode(res.body);
if (data['success'] == true) {
final prefs = await SharedPreferences.getInstance();
prefs.setString('userId', data['user']['_id']);
2025-08-15 01:27:26 -07:00
prefs.setString('accessToken', accessToken);
2025-08-14 21:54:25 -07:00
Navigator.pushReplacement(
context,
2025-08-28 01:34:45 -07:00
MaterialPageRoute(builder: (_) => FinTrackHomePage()),
2025-08-14 21:54:25 -07:00
);
} else {
debugPrint("❌ Login failed: ${data['error']}");
}
} else {
debugPrint("❌ Backend error: ${res.body}");
}
} catch (e) {
debugPrint("⚠ Google Sign-In error: $e");
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
2025-08-28 01:34:45 -07:00
final size = MediaQuery.of(context).size;
2025-08-14 21:54:25 -07:00
return Scaffold(
2025-08-28 01:34:45 -07:00
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF1DB584), // Your app's primary green
Color(0xFF0FA968),
Color(0xFF059649),
],
stops: [0.0, 0.5, 1.0],
),
),
child: Stack(
children: [
// Animated Background Elements
...List.generate(6, (index) {
return AnimatedBuilder(
animation: _rotationController,
builder: (context, child) {
return Positioned(
top: size.height * (0.1 + (index * 0.15)),
left: size.width * (0.1 + (index * 0.12)),
child: Transform.rotate(
angle: _rotationAnimation.value + (index * 0.5),
child: Container(
width: 60 + (index * 10),
height: 60 + (index * 10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.05),
border: Border.all(
color: Colors.white.withOpacity(0.1),
width: 2,
),
),
),
),
);
},
);
}),
// Floating Particles
...List.generate(12, (index) {
return AnimatedBuilder(
animation: _rotationController,
builder: (context, child) {
return Positioned(
top: size.height * (0.2 + (index * 0.06)),
right: size.width * (0.1 + (index * 0.05)),
child: Transform.translate(
offset: Offset(
math.sin(_rotationAnimation.value + index) * 20,
math.cos(_rotationAnimation.value + index) * 15,
),
child: Container(
width: 8 + (index % 3) * 4,
height: 8 + (index % 3) * 4,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.3),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 2,
),
],
),
),
),
);
},
);
}),
// Main Content
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
children: [
const Spacer(),
// Logo Section
FadeTransition(
opacity: _fadeAnimation,
child: Column(
children: [
ScaleTransition(
scale: _scaleAnimation,
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 30,
spreadRadius: 5,
offset: const Offset(0, 10),
),
],
),
child: const Icon(
Icons.account_balance_wallet_rounded,
size: 60,
color: Color(0xFF1DB584),
),
),
),
const SizedBox(height: 32),
// App Title
const Text(
'FinTrack',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 42,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: -1.5,
),
),
const SizedBox(height: 12),
// Subtitle
Text(
'Your Smart Financial Companion',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 16,
color: Colors.white.withOpacity(0.9),
fontWeight: FontWeight.w400,
letterSpacing: 0.3,
),
),
],
),
),
const Spacer(),
// Features Section
SlideTransition(
position: _slideAnimation,
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1,
),
),
child: Column(
children: [
Row(
children: [
_buildFeatureItem(
Icons.trending_up_rounded,
'Track Expenses',
),
const SizedBox(width: 24),
_buildFeatureItem(
Icons.insights_rounded,
'Smart Analytics',
),
],
),
const SizedBox(height: 16),
Row(
children: [
_buildFeatureItem(
Icons.security_rounded,
'Secure & Private',
),
const SizedBox(width: 24),
_buildFeatureItem(
Icons.sync_rounded,
'Auto Sync',
),
],
),
],
),
),
2025-08-19 09:46:35 -07:00
),
2025-08-28 01:34:45 -07:00
const SizedBox(height: 48),
// Google Sign In Button
SlideTransition(
position: _slideAnimation,
child:
_isLoading
? Container(
width: double.infinity,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
spreadRadius: 2,
offset: const Offset(0, 8),
),
],
),
child: const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFF1DB584),
),
),
),
),
)
: Container(
width: double.infinity,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
spreadRadius: 2,
offset: const Offset(0, 8),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _handleGoogleSignIn,
borderRadius: BorderRadius.circular(30),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Image.network(
'https://developers.google.com/identity/images/g-logo.png',
width: 24,
height: 24,
),
const SizedBox(width: 16),
const Text(
'Continue with Google',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
letterSpacing: 0.2,
),
),
],
),
),
),
),
),
2025-08-19 09:46:35 -07:00
),
2025-08-28 01:34:45 -07:00
const SizedBox(height: 24),
// Terms Text
FadeTransition(
opacity: _fadeAnimation,
child: Text(
'By continuing, you agree to our Terms of Service\nand Privacy Policy',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 12,
color: Colors.white.withOpacity(0.7),
fontWeight: FontWeight.w400,
height: 1.5,
),
),
),
const SizedBox(height: 48),
],
2025-08-14 21:54:25 -07:00
),
2025-08-28 01:34:45 -07:00
),
),
],
),
),
);
}
Widget _buildFeatureItem(IconData icon, String text) {
return Expanded(
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: Colors.white, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Text(
text,
style: const TextStyle(
fontFamily: 'Inter',
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
],
2025-08-14 21:54:25 -07:00
),
);
}
}