feat: login page with sucessful login authentication into secure shared preferences
This commit is contained in:
parent
930e6e5284
commit
622db644fa
9
frontend/lib/constants/api_constants.dart
Normal file
9
frontend/lib/constants/api_constants.dart
Normal file
@ -0,0 +1,9 @@
|
||||
class ApiConstants {
|
||||
static const String baseUrl = 'http://localhost:8080';
|
||||
|
||||
// Auth endpoints
|
||||
static const String loginEndpoint = '/login';
|
||||
|
||||
// User endpoints
|
||||
static const String getUserEndpoint = '/getUser';
|
||||
}
|
||||
@ -3,6 +3,7 @@ import 'package:firebase_core/firebase_core.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'screens/home_screen.dart';
|
||||
import 'services/notification_service.dart';
|
||||
import 'services/auth_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -28,63 +29,16 @@ class LandingPage extends StatefulWidget {
|
||||
_LandingPageState createState() => _LandingPageState();
|
||||
}
|
||||
|
||||
class _LandingPageState extends State<LandingPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _slideAnimation;
|
||||
bool _showBottomSheet = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: Duration(milliseconds: 800),
|
||||
vsync: this,
|
||||
);
|
||||
_slideAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showGetStartedBottomSheet() {
|
||||
setState(() {
|
||||
_showBottomSheet = true;
|
||||
});
|
||||
_animationController.forward();
|
||||
|
||||
class _LandingPageState extends State<LandingPage> {
|
||||
void _navigateDirectlyToLogin() {
|
||||
// Request notification permissions on user action
|
||||
NotificationService().requestPermissionAndSetup();
|
||||
}
|
||||
|
||||
void _hideBottomSheet() {
|
||||
_animationController.reverse().then((_) {
|
||||
setState(() {
|
||||
_showBottomSheet = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _navigateToSignIn() {
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (context) => SignInPage()));
|
||||
}
|
||||
|
||||
void _navigateToHome() {
|
||||
// Request notification permissions on user action
|
||||
NotificationService().requestPermissionAndSetup();
|
||||
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (context) => HomeScreen()));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -156,7 +110,7 @@ class _LandingPageState extends State<LandingPage>
|
||||
width: 280,
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: _showGetStartedBottomSheet,
|
||||
onPressed: _navigateDirectlyToLogin,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
foregroundColor: Colors.white,
|
||||
@ -183,25 +137,6 @@ class _LandingPageState extends State<LandingPage>
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Login link
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _navigateToHome,
|
||||
child: Text(
|
||||
'Skip to Home',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -209,158 +144,6 @@ class _LandingPageState extends State<LandingPage>
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom sheet overlay
|
||||
if (_showBottomSheet)
|
||||
AnimatedBuilder(
|
||||
animation: _slideAnimation,
|
||||
builder: (context, child) {
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, _slideAnimation.value * 400),
|
||||
child: Container(
|
||||
height: 400,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 20,
|
||||
offset: Offset(0, -5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Handle bar
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
||||
// Close button
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton(
|
||||
onPressed: _hideBottomSheet,
|
||||
icon: Icon(Icons.close, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Welcome In',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 12),
|
||||
|
||||
Text(
|
||||
'Join your community.. change your social life!',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40),
|
||||
|
||||
// Sign Up button
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// Handle sign up
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Sign Up pressed'),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
),
|
||||
elevation: 2,
|
||||
),
|
||||
child: Text(
|
||||
'Sign Up Now',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Login button
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: OutlinedButton(
|
||||
onPressed: _navigateToSignIn,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Color(0xFF6A4C93),
|
||||
side: BorderSide(
|
||||
color: Color(0xFF6A4C93),
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -379,6 +162,72 @@ class _SignInPageState extends State<SignInPage> {
|
||||
bool _isPasswordVisible = false;
|
||||
bool _isLoading = false;
|
||||
|
||||
void _showHelpBottomSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
builder: (context) => Container(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Icon(Icons.contact_support, size: 48, color: Color(0xFF6A4C93)),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Need Help?',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'For account creation or password reset, please contact ERP Management Group from your Aramco email address.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[700],
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: Text(
|
||||
'Got It',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
@ -392,19 +241,44 @@ class _SignInPageState extends State<SignInPage> {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
// Simulate API call
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
final result = await AuthService.login(
|
||||
_emailController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Sign in successful!')));
|
||||
if (result['success'] == true) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (context) => HomeScreen()),
|
||||
);
|
||||
} else {
|
||||
_showErrorAlert(result['message'] ?? 'Login failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showErrorAlert(String message) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Login Failed'),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: TextStyle(color: Color(0xFF6A4C93)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -493,7 +367,7 @@ class _SignInPageState extends State<SignInPage> {
|
||||
SizedBox(height: 8),
|
||||
|
||||
Text(
|
||||
'Sign in to your account',
|
||||
'Sign in to socialize with your colleagues\nand transform your social life!',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[600],
|
||||
@ -575,13 +449,7 @@ class _SignInPageState extends State<SignInPage> {
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Forgot password pressed'),
|
||||
),
|
||||
);
|
||||
},
|
||||
onPressed: _showHelpBottomSheet,
|
||||
child: Text(
|
||||
'Forgot Password?',
|
||||
style: TextStyle(
|
||||
@ -625,7 +493,7 @@ class _SignInPageState extends State<SignInPage> {
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
// Sign up link
|
||||
// Contact link for new accounts
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@ -634,13 +502,9 @@ class _SignInPageState extends State<SignInPage> {
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Sign up pressed')),
|
||||
);
|
||||
},
|
||||
onTap: _showHelpBottomSheet,
|
||||
child: Text(
|
||||
'Sign Up',
|
||||
'Contact Support',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF6A4C93),
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../services/notification_service.dart';
|
||||
import '../../services/user_service.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
@override
|
||||
@ -11,6 +12,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
String? fcmToken;
|
||||
final TextEditingController _tokenController = TextEditingController();
|
||||
bool isLoading = false;
|
||||
Map<String, dynamic>? userData;
|
||||
bool isLoadingUser = true;
|
||||
|
||||
final List<Map<String, dynamic>> mockUserPosts = [
|
||||
{
|
||||
@ -46,6 +49,7 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadFCMToken();
|
||||
_loadUserData();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -75,6 +79,43 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadUserData() async {
|
||||
setState(() {
|
||||
isLoadingUser = true;
|
||||
});
|
||||
|
||||
final result = await UserService.getCurrentUser();
|
||||
|
||||
setState(() {
|
||||
isLoadingUser = false;
|
||||
if (result['success'] == true) {
|
||||
userData = result['data'];
|
||||
} else {
|
||||
userData = null;
|
||||
_showErrorAlert(result['message'] ?? 'Failed to load user data');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _showErrorAlert(String message) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Error'),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: TextStyle(color: Color(0xFF6A4C93)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _copyToClipboard() async {
|
||||
if (fcmToken != null && fcmToken!.isNotEmpty) {
|
||||
await Clipboard.setData(ClipboardData(text: fcmToken!));
|
||||
@ -119,200 +160,232 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
child: Text(
|
||||
'A',
|
||||
if (isLoadingUser)
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF6A4C93)),
|
||||
)
|
||||
else if (userData != null) ...[
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
child: Text(
|
||||
(userData!['displayName'] ?? 'U').substring(0, 1).toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
userData!['displayName'] ?? 'Unknown User',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Abu Norah',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'@${userData!['username'] ?? 'unknown'}',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'@yasser_hajri',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
] else ...[
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 50,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Failed to load user data',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: _loadUserData,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text('Retry'),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'${mockUserPosts.length}',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF6A4C93),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Posts',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'127',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF6A4C93),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Followers',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
color: Colors.grey[200],
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: mockUserPosts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final post = mockUserPosts[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
if (userData != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Row(
|
||||
Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
child: Text(
|
||||
'J',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
Text(
|
||||
'${mockUserPosts.length}',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF6A4C93),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Abu Norah',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
post['timestamp'],
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
'Posts',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
post['content'],
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
height: 1.4,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
Column(
|
||||
children: [
|
||||
Icon(
|
||||
post['isLiked']
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color: post['isLiked']
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'${post['likes']}',
|
||||
'127',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF6A4C93),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Icon(
|
||||
Icons.chat_bubble_outline,
|
||||
color: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'${post['comments']}',
|
||||
'Followers',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
if (userData != null) ...[
|
||||
Container(
|
||||
height: 1,
|
||||
color: Colors.grey[200],
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: mockUserPosts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final post = mockUserPosts[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xFF6A4C93),
|
||||
child: Text(
|
||||
(userData!['displayName'] ?? 'U').substring(0, 1).toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
userData!['displayName'] ?? 'Unknown User',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
post['timestamp'],
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
post['content'],
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
height: 1.4,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
post['isLiked']
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color: post['isLiked']
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'${post['likes']}',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Icon(
|
||||
Icons.chat_bubble_outline,
|
||||
color: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'${post['comments']}',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
@ -525,4 +598,4 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
frontend/lib/services/auth_service.dart
Normal file
72
frontend/lib/services/auth_service.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../constants/api_constants.dart';
|
||||
|
||||
class AuthService {
|
||||
static const FlutterSecureStorage _storage = FlutterSecureStorage();
|
||||
static const String _tokenKey = 'jwt_token';
|
||||
|
||||
static Future<Map<String, dynamic>> login(
|
||||
String emailOrUsername,
|
||||
String password,
|
||||
) async {
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse('${ApiConstants.baseUrl}${ApiConstants.loginEndpoint}'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'emailOrUsername': emailOrUsername,
|
||||
'password': password,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
final token = data['token'];
|
||||
|
||||
if (token != null) {
|
||||
await _storage.write(key: _tokenKey, value: token);
|
||||
return {'success': true};
|
||||
}
|
||||
return {'success': false, 'message': 'No token received'};
|
||||
} else if (response.statusCode == 403 || response.statusCode == 400) {
|
||||
return {'success': false, 'message': 'Invalid credentials'};
|
||||
} else if (response.statusCode == 401) {
|
||||
return {'success': false, 'message': 'Invalid credentials'};
|
||||
} else {
|
||||
return {
|
||||
'success': false,
|
||||
'message': 'Server error (${response.statusCode})',
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print('Login error: $e');
|
||||
return {
|
||||
'success': false,
|
||||
'message': 'Network error. Please check your connection.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> getToken() async {
|
||||
return await _storage.read(key: _tokenKey);
|
||||
}
|
||||
|
||||
static Future<bool> isLoggedIn() async {
|
||||
final token = await getToken();
|
||||
return token != null && token.isNotEmpty;
|
||||
}
|
||||
|
||||
static Future<void> logout() async {
|
||||
await _storage.delete(key: _tokenKey);
|
||||
}
|
||||
|
||||
static Future<Map<String, String>> getAuthHeaders() async {
|
||||
final token = await getToken();
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
if (token != null) 'Authorization': 'Bearer $token',
|
||||
};
|
||||
}
|
||||
}
|
||||
40
frontend/lib/services/http_service.dart
Normal file
40
frontend/lib/services/http_service.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../constants/api_constants.dart';
|
||||
import 'auth_service.dart';
|
||||
|
||||
class HttpService {
|
||||
static Future<http.Response> get(String endpoint) async {
|
||||
final headers = await AuthService.getAuthHeaders();
|
||||
return await http.get(
|
||||
Uri.parse('${ApiConstants.baseUrl}$endpoint'),
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<http.Response> post(String endpoint, Map<String, dynamic> body) async {
|
||||
final headers = await AuthService.getAuthHeaders();
|
||||
return await http.post(
|
||||
Uri.parse('${ApiConstants.baseUrl}$endpoint'),
|
||||
headers: headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<http.Response> put(String endpoint, Map<String, dynamic> body) async {
|
||||
final headers = await AuthService.getAuthHeaders();
|
||||
return await http.put(
|
||||
Uri.parse('${ApiConstants.baseUrl}$endpoint'),
|
||||
headers: headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<http.Response> delete(String endpoint) async {
|
||||
final headers = await AuthService.getAuthHeaders();
|
||||
return await http.delete(
|
||||
Uri.parse('${ApiConstants.baseUrl}$endpoint'),
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
}
|
||||
25
frontend/lib/services/user_service.dart
Normal file
25
frontend/lib/services/user_service.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'dart:convert';
|
||||
import '../constants/api_constants.dart';
|
||||
import 'http_service.dart';
|
||||
|
||||
class UserService {
|
||||
static Future<Map<String, dynamic>> getCurrentUser() async {
|
||||
try {
|
||||
final response = await HttpService.get(ApiConstants.getUserEndpoint);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return {'success': true, 'data': data};
|
||||
} else if (response.statusCode == 401) {
|
||||
return {'success': false, 'message': 'Session expired. Please login again.'};
|
||||
} else if (response.statusCode == 403) {
|
||||
return {'success': false, 'message': 'Access denied. Invalid credentials.'};
|
||||
} else {
|
||||
return {'success': false, 'message': 'Server error (${response.statusCode})'};
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error fetching user: $e');
|
||||
return {'success': false, 'message': 'Network error. Please check your connection.'};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@ -7,8 +7,12 @@ import Foundation
|
||||
|
||||
import firebase_core
|
||||
import firebase_messaging
|
||||
import flutter_secure_storage_macos
|
||||
import path_provider_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
}
|
||||
|
||||
@ -81,6 +81,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"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -142,6 +150,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.4"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
flutter_secure_storage_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_macos
|
||||
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -184,6 +240,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -248,6 +312,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
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"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -341,6 +461,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.8.1 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
@ -14,6 +14,7 @@ dependencies:
|
||||
firebase_messaging: ^15.2.9
|
||||
http: ^1.1.0
|
||||
googleapis_auth: ^1.6.0
|
||||
flutter_secure_storage: ^9.2.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
firebase_core
|
||||
flutter_secure_storage_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Loading…
Reference in New Issue
Block a user