inventory_mobile/lib/screens/login_screen.dart
2026-01-13 06:26:37 +08:00

422 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:inventory_system/services/api_service.dart';
import 'package:inventory_system/services/auth_service.dart';
import 'package:inventory_system/screens/admin/home_screen/home_screen.dart';
import 'package:inventory_system/screens/user/home_screen/home_screen_user.dart';
import 'package:inventory_system/services/session_manager.dart'; //To set the server URL
import 'package:shared_preferences/shared_preferences.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
bool _isLoading = false;
bool _rememberMe = false;
final AuthService _authService = AuthService();
@override
void initState() {
super.initState();
_loadSavedCredentials();
}
Future<void> _loadSavedCredentials() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_rememberMe = prefs.getBool('remember_me') ?? false;
if (_rememberMe) {
_emailController.text = prefs.getString('saved_email') ?? '';
_passwordController.text = prefs.getString('saved_password') ?? '';
}
});
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _handleSignIn() async {
if (_formKey.currentState?.validate() ?? false) {
setState(() {
_isLoading = true;
});
final result = await _authService.signIn(
usernameOrEmail: _emailController.text.trim(),
password: _passwordController.text,
);
setState(() {
_isLoading = false;
});
if (!mounted) return;
if (result['success'] == true) {
final prefs = await SharedPreferences.getInstance();
if (_rememberMe) {
await prefs.setBool('remember_me', true);
await prefs.setString('saved_email', _emailController.text.trim());
await prefs.setString('saved_password', _passwordController.text);
} else {
await prefs.remove('remember_me');
await prefs.remove('saved_email');
await prefs.remove('saved_password');
}
SessionManager.instance.startSession(result['data']);
final bool isAdmin = result['data']?['isAdmin'] ?? false;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
isAdmin
? const HomeScreen()
: const UserHomeScreen(),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result['message'] ?? 'An unknown error occurred.'),
backgroundColor: Colors.redAccent,
),
);
}
}
}
// To show the URL settings dialog
void _showUrlDialog(BuildContext context) {
final TextEditingController urlController = TextEditingController();
urlController.text = ApiService.baseUrl; // Set current URL
showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text("Set Server URL"),
content: TextField(
controller: urlController,
decoration: const InputDecoration(
labelText: "Base URL",
hintText: "https://...",
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text("Cancel"),
),
ElevatedButton(
onPressed: () async {
if (urlController.text.isNotEmpty) {
// Save the new URL
await ApiService.setBaseUrl(urlController.text);
Navigator.pop(dialogContext); // Close the dialog
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("New URL Saved!"),
backgroundColor: Colors.green,
),
);
}
},
child: const Text("Save"),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack( // Use a Stack to overlay the settings button
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
Color(0xFF90CAF9),
],
stops: [0.0, 0.3, 0.7, 1.0],
),
),
child: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
children: [
const SizedBox(height: 0),
Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
fit: BoxFit.contain,
),
const SizedBox(height: 0),
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: const Icon(
Icons.person_outline,
size: 50,
color: Colors.white,
),
),
const SizedBox(height: 24),
Text(
'Sign in to continue',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color: Colors.white,
shadows: [
Shadow(
offset: const Offset(1, 1),
blurRadius: 4,
color: Colors.black.withOpacity(0.3),
),
],
),
),
const SizedBox(height: 40),
// Login form
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Email or Username',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1976D2),
),
),
const SizedBox(height: 8),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'Enter your email',
prefixIcon: const Icon(Icons.email_outlined,
color: Color(0xFF1976D2)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFF1976D2), width: 2),
),
filled: true,
fillColor: Colors.grey.shade50,
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter your username or email';
}
return null; // Return null if the input is valid
},
),
const SizedBox(height: 20),
const Text(
'Password',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1976D2),
),
),
const SizedBox(height: 8),
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
hintText: 'Enter your password',
prefixIcon: const Icon(Icons.lock_outline,
color: Color(0xFF1976D2)),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
color: Color(0xFF1976D2),
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFF1976D2), width: 2),
),
filled: true,
fillColor: Colors.grey.shade50,
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
Row(
children: [
Checkbox(
value: _rememberMe,
onChanged: (value) {
setState(() {
_rememberMe = value ?? false;
});
},
),
const Text(
'Remember me',
style: TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleSignIn,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1976D2),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'Sign In',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 16),
Center(
// child: TextButton(
// onPressed: () {
// // TODO: Implement forgot password
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text(
// 'Forgot password functionality to be implemented'),
// ),
// );
// },
// child: const Text(
// 'Forgot password?',
// style: TextStyle(
// fontSize: 14,
// color: Color(0xFF1976D2),
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
),
],
),
),
),
],
),
)),
),
),
// 4. ADD THE SETTINGS BUTTON on top of the UI
Positioned(
top: MediaQuery.of(context).padding.top + 8,
right: 8,
child: IconButton(
icon: const Icon(Icons.settings, color: Colors.white, size: 28),
tooltip: 'Server Settings',
onPressed: () {
_showUrlDialog(context);
},
),
),
],
),
);
}
}