inventory_mobile/lib/screens/login_screen.dart
2025-12-15 15:35:35 +08:00

395 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'package:inventory_system/services/api_service.dart'; // 1. Import ApiService
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/routes/slide_route.dart';
import 'package:inventory_system/services/session_manager.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 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) {
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,
),
);
}
}
}
// 2. ADD THIS METHOD 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( // 3. Use a Stack to overlay the settings button
children: [
// This is your existing UI
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);
},
),
),
],
),
);
}
}