Files
Siro/siro_admin/lib/views/auth/login_page.dart
2026-06-12 20:40:40 +03:00

523 lines
22 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/env/env.dart';
import '../../controller/auth/login_controller.dart';
import '../../controller/auth/otp_helper.dart';
import '../../controller/functions/crud.dart';
import '../../print.dart';
import '../admin/admin_home_page.dart';
// ─── Colors (نفس نظام الألوان المستخدم في التطبيق) ──────────────────────────
class _C {
static const bg = Color(0xFF0A0D14);
static const card = Color(0xFF161D2E);
static const border = Color(0xFF1F2D4A);
static const accent = Color(0xFF00E5FF);
static const accentGlow = Color(0x2200E5FF);
static const accentDim = Color(0xFF0097A7);
static const textPrimary = Color(0xFFE8F0FE);
static const textSec = Color(0xFF7A8BAA);
static const error = Color(0xFFFF5252);
static const inputBg = Color(0xFF0C1120);
}
class AdminLoginPage extends StatefulWidget {
const AdminLoginPage({super.key});
@override
State<AdminLoginPage> createState() => _AdminLoginPageState();
}
class _AdminLoginPageState extends State<AdminLoginPage>
with SingleTickerProviderStateMixin {
final _phoneController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
late final AnimationController _glowCtrl;
late final Animation<double> _glowAnim;
Future<void> _submit() async {
final password = _passwordController.text.trim();
final phone = _phoneController.text.trim();
if (password.isEmpty) {
Get.snackbar('خطأ', 'يرجى إدخال كلمة المرور');
return;
}
setState(() => _isLoading = true);
final otpHelper = Get.find<OtpHelper>();
bool success = await otpHelper.loginWithPassword(password, phone);
if (success) {
Get.offAll(() => const AdminHomePage());
}
setState(() => _isLoading = false);
}
@override
void initState() {
super.initState();
_initializeToken();
_glowCtrl = AnimationController(
vsync: this,
duration: const Duration(seconds: 4),
)..repeat(reverse: true);
_glowAnim = Tween<double>(begin: 0.3, end: 1.0).animate(
CurvedAnimation(parent: _glowCtrl, curve: Curves.easeInOut),
);
}
void _initializeToken() async {
// await CRUD().getJWT();
}
@override
void dispose() {
_phoneController.dispose();
_glowCtrl.dispose();
super.dispose();
}
// ─── Build ─────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
Get.put(OtpHelper());
return Scaffold(
backgroundColor: _C.bg,
body: Stack(
children: [
// ── Ambient glow top-right ──────────────────────────────────────────
Positioned(
top: -150,
right: -100,
child: AnimatedBuilder(
animation: _glowAnim,
builder: (_, __) => Opacity(
opacity: _glowAnim.value * 0.18,
child: Container(
width: 400,
height: 400,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [Color(0xFF00E5FF), Colors.transparent],
),
),
),
),
),
),
// ── Ambient glow bottom-left ────────────────────────────────────────
Positioned(
bottom: -120,
left: -80,
child: AnimatedBuilder(
animation: _glowAnim,
builder: (_, __) => Opacity(
opacity: (1 - _glowAnim.value) * 0.15,
child: Container(
width: 340,
height: 340,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [Color(0xFF7C4DFF), Colors.transparent],
),
),
),
),
),
),
// ── Main content ───────────────────────────────────────────────────
SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 28),
physics: const BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 440),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 24),
// ── Logo / Icon ─────────────────────────────────────
_buildLogo(),
const SizedBox(height: 32),
// ── Title ───────────────────────────────────────────
const Text(
'لوحة الإدارة',
style: TextStyle(
color: _C.textPrimary,
fontSize: 26,
fontWeight: FontWeight.w800,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
const Text(
'أدخل كلمة المرور للمتابعة',
style: TextStyle(
color: _C.textSec,
fontSize: 14,
),
),
const SizedBox(height: 40),
// ── Card ────────────────────────────────────────────
Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
color: _C.card,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: _C.accent.withOpacity(0.18), width: 1.2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
blurRadius: 32,
offset: const Offset(0, 12),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Field label (Phone) ─────────────────────────────
const Row(
children: [
Icon(Icons.phone_android_rounded,
color: _C.accent, size: 16),
SizedBox(width: 8),
Text(
'رقم الهاتف (لأول دخول فقط)',
style: TextStyle(
color: _C.textSec,
fontSize: 13,
fontWeight: FontWeight.w600,
letterSpacing: 0.3,
),
),
],
),
const SizedBox(height: 10),
// ── Phone field ─────────────────────────────
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
style: const TextStyle(
color: _C.textPrimary,
fontSize: 16,
),
decoration: InputDecoration(
hintText: '07XXXXXXXX',
hintStyle: const TextStyle(color: _C.textSec),
filled: true,
fillColor: _C.inputBg,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.border, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.accent, width: 1.5),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 16),
),
),
const SizedBox(height: 20),
// ── Field label (Password) ─────────────────────────────
const Row(
children: [
Icon(Icons.lock_outline_rounded,
color: _C.accent, size: 16),
SizedBox(width: 8),
Text(
'كلمة المرور',
style: TextStyle(
color: _C.textSec,
fontSize: 13,
fontWeight: FontWeight.w600,
letterSpacing: 0.3,
),
),
],
),
const SizedBox(height: 10),
// ── Password field ─────────────────────────────
TextFormField(
controller: _passwordController,
obscureText: true,
style: const TextStyle(
color: _C.textPrimary,
fontSize: 16,
),
decoration: InputDecoration(
hintText: '••••••••',
hintStyle: const TextStyle(color: _C.textSec),
filled: true,
fillColor: _C.inputBg,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.border, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.accent, width: 1.5),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 16),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'الرجاء إدخال كلمة المرور';
}
return null;
},
),
const SizedBox(height: 28),
// ── Submit button ────────────────────────────
_isLoading
? const Center(
child: SizedBox(
width: 32,
height: 32,
child: CircularProgressIndicator(
color: _C.accent,
strokeWidth: 2.5,
),
),
)
: _SubmitButton(onPressed: () {
if (_formKey.currentState!.validate()) {
_submit();
}
}),
],
),
),
const SizedBox(height: 32),
// ── Register Button ─────────────────────────────────
TextButton(
onPressed: () => Get.toNamed('/register'),
child: const Text(
'ليس لديك حساب؟ طلب انضمام',
style: TextStyle(
color: _C.accent,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 16),
// ── Footer ──────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _C.accent,
boxShadow: [
BoxShadow(
color: _C.accentGlow,
blurRadius: 6,
spreadRadius: 1,
),
],
),
),
const SizedBox(width: 8),
const Text(
'وصول مقيّد للمشرفين فقط',
style: TextStyle(
color: _C.textSec,
fontSize: 12,
),
),
],
),
const SizedBox(height: 30),
// ── Register Link ───────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'ليس لديك حساب مشرف؟',
style: TextStyle(color: _C.textSec, fontSize: 14),
),
TextButton(
onPressed: () {
Get.toNamed('/register'); // أو الانتقال المباشر للصفحة
},
style: TextButton.styleFrom(
foregroundColor: _C.accent,
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
child: const Text('تسجيل حساب جديد'),
),
],
),
const SizedBox(height: 40),
],
),
),
),
),
),
),
],
),
);
}
// ─── Logo Widget ─────────────────────────────────────────────────────────
Widget _buildLogo() {
return AnimatedBuilder(
animation: _glowAnim,
builder: (_, child) => Container(
width: 90,
height: 90,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _C.card,
border: Border.all(
color: _C.accent.withOpacity(0.3 + _glowAnim.value * 0.3),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: _C.accentGlow.withOpacity(_glowAnim.value * 0.6),
blurRadius: 30,
spreadRadius: 4,
),
],
),
child: child,
),
child: const Icon(
Icons.admin_panel_settings_rounded,
color: _C.accent,
size: 42,
),
);
}
}
// ─── Submit Button ─────────────────────────────────────────────────────────────
class _SubmitButton extends StatefulWidget {
final VoidCallback onPressed;
const _SubmitButton({required this.onPressed});
@override
State<_SubmitButton> createState() => _SubmitButtonState();
}
class _SubmitButtonState extends State<_SubmitButton>
with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
late final Animation<double> _scale;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this, duration: const Duration(milliseconds: 100));
_scale = Tween<double>(begin: 1.0, end: 0.96)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => _ctrl.forward(),
onTapUp: (_) => _ctrl.reverse(),
onTapCancel: () => _ctrl.reverse(),
onTap: widget.onPressed,
child: AnimatedBuilder(
animation: _scale,
builder: (_, child) =>
Transform.scale(scale: _scale.value, child: child),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 17),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF00B4D8), Color(0xFF00E5FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: const Color(0x3300E5FF),
blurRadius: 20,
spreadRadius: 1,
offset: const Offset(0, 6),
),
],
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.send_rounded, color: Colors.white, size: 18),
SizedBox(width: 10),
const Text(
'تسجيل الدخول',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
),
),
],
),
),
),
);
}
}