528 lines
20 KiB
Dart
528 lines
20 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import '../../../core/config/app_config.dart';
|
|
import '../../../core/localization/app_strings.dart';
|
|
import '../../../core/theme/app_colors.dart';
|
|
import '../../../core/theme/app_theme.dart';
|
|
import '../../../core/utils/saqel_toast.dart';
|
|
import '../../../logic/cubits/auth_cubit.dart';
|
|
import '../../widgets/luxury_widgets.dart';
|
|
|
|
class AuthScreen extends StatefulWidget {
|
|
const AuthScreen({super.key});
|
|
|
|
@override
|
|
State<AuthScreen> createState() => _AuthScreenState();
|
|
}
|
|
|
|
class _AuthScreenState extends State<AuthScreen> {
|
|
final TextEditingController _phoneController = TextEditingController();
|
|
final TextEditingController _otpController = TextEditingController();
|
|
final TextEditingController _nationalIdController = TextEditingController();
|
|
final TextEditingController _fullNameController = TextEditingController();
|
|
|
|
String _selectedRole = 'student';
|
|
final String _selectedGrade = 'tawjihi_2008';
|
|
String _selectedStream = 'scientific';
|
|
|
|
@override
|
|
void dispose() {
|
|
_phoneController.dispose();
|
|
_otpController.dispose();
|
|
_nationalIdController.dispose();
|
|
_fullNameController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: RadialGradient(
|
|
center: Alignment(0.8, -0.6),
|
|
radius: 1.3,
|
|
colors: [
|
|
Color(0xFF0A2540),
|
|
AppColors.darkBackground,
|
|
],
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: Center(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 480),
|
|
child: BlocConsumer<AuthCubit, AuthState>(
|
|
listener: (context, state) {
|
|
if (state is AuthError) {
|
|
SaqelToast.showError(context, state.message, title: 'تنبيه المصادقة');
|
|
} else if (state is AuthOtpSent) {
|
|
SaqelToast.showWhatsApp(
|
|
context,
|
|
'تم إرسال كود التحقق بنجاح إلى الرقم ${state.phoneNumber}',
|
|
title: 'رسالة واتساب من نبيه 📲',
|
|
);
|
|
} else if (state is AuthNeedsOnboarding) {
|
|
SaqelToast.showInfo(
|
|
context,
|
|
'يرجى استكمال الاسم والفرع لفتح ملفك الأكاديمي',
|
|
title: 'طالب جديد في صَقِل 🎓',
|
|
);
|
|
} else if (state is Authenticated) {
|
|
SaqelToast.showSuccess(
|
|
context,
|
|
'أهلاً بك يا ${state.user.name}',
|
|
title: 'تم تسجيل الدخول بنجاح ✨',
|
|
);
|
|
}
|
|
},
|
|
builder: (context, state) {
|
|
final isLoading = state is AuthLoading;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Server Connection Health Badge
|
|
Center(
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.darkSurface,
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: AppColors.darkCardBorder),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 8,
|
|
height: 8,
|
|
decoration: const BoxDecoration(
|
|
color: AppColors.saqelCyan,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
AppConfig.baseUrl.replaceAll('https://', '').replaceAll('http://', ''),
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textSecondaryDark,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
if (state is AuthNeedsOnboarding)
|
|
_buildOnboardingStep(context, state, isLoading)
|
|
else if (state is AuthNeedsNationalId)
|
|
_buildNationalIdStep(context, state, isLoading)
|
|
else if (state is AuthOtpSent)
|
|
_buildOtpStep(context, state, isLoading)
|
|
else
|
|
_buildPhoneStep(context, isLoading),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Step 1: Phone Input & Role Selector
|
|
Widget _buildPhoneStep(BuildContext context, bool isLoading) {
|
|
return Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Center(
|
|
child: Container(
|
|
width: 76,
|
|
height: 76,
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [AppColors.appleBlue, AppColors.saqelCyan],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(22),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x4D00F5D4),
|
|
blurRadius: 25,
|
|
offset: Offset(0, 10),
|
|
),
|
|
],
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'ص',
|
|
style: TextStyle(
|
|
fontSize: 38,
|
|
fontWeight: FontWeight.w900,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
AppStrings.enterPhoneTitle,
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.displayLarge.copyWith(color: Colors.white),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
AppStrings.enterPhoneSubtitle,
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark),
|
|
),
|
|
const SizedBox(height: 28),
|
|
|
|
// Role Segmented Pill
|
|
Container(
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.darkSurface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: AppColors.darkCardBorder),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: isLoading ? null : () => setState(() => _selectedRole = 'student'),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: _selectedRole == 'student' ? AppColors.appleBlue : Colors.transparent,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
AppStrings.studentRole,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: _selectedRole == 'student' ? Colors.white : AppColors.textSecondaryDark,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: isLoading ? null : () => setState(() => _selectedRole = 'guardian'),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: _selectedRole == 'guardian' ? AppColors.guardianAmber : Colors.transparent,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
AppStrings.guardianRole,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: _selectedRole == 'guardian' ? Colors.black : AppColors.textSecondaryDark,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
LuxuryTextField(
|
|
controller: _phoneController,
|
|
label: AppStrings.phoneNumber,
|
|
hint: '0791234567',
|
|
keyboardType: TextInputType.phone,
|
|
enabled: !isLoading,
|
|
prefixIcon: const Icon(CupertinoIcons.phone_fill, color: AppColors.saqelCyan, size: 20),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryButton(
|
|
text: AppStrings.requestOtp,
|
|
isLoading: isLoading,
|
|
onPressed: () {
|
|
final phone = _phoneController.text.trim();
|
|
if (phone.isEmpty) {
|
|
SaqelToast.showError(context, 'يرجى إدخال رقم الهاتف المسجل في منصة صَقِل');
|
|
return;
|
|
}
|
|
context.read<AuthCubit>().sendOtp(phone, role: _selectedRole);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
/// Step 2: OTP Verification
|
|
Widget _buildOtpStep(BuildContext context, AuthOtpSent state, bool isLoading) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Center(
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.whatsappGreen.withAlpha(35),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(CupertinoIcons.chat_bubble_2_fill, color: AppColors.whatsappGreen, size: 30),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
AppStrings.enterOtpTitle,
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.headlineMedium.copyWith(color: Colors.white),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'${AppStrings.enterOtpSubtitle} (${state.phoneNumber})',
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryTextField(
|
|
controller: _otpController,
|
|
label: 'رمز التحقق (6 أرقام)',
|
|
hint: '123456',
|
|
keyboardType: TextInputType.number,
|
|
enabled: !isLoading,
|
|
prefixIcon: const Icon(CupertinoIcons.lock_shield_fill, color: AppColors.appleBlue, size: 20),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryButton(
|
|
text: AppStrings.verifyOtp,
|
|
isLoading: isLoading,
|
|
onPressed: () {
|
|
final otp = _otpController.text.trim();
|
|
if (otp.isEmpty) {
|
|
SaqelToast.showError(context, 'يرجى إدخال رمز التحقق المستلم عبر الواتساب');
|
|
return;
|
|
}
|
|
context.read<AuthCubit>().verifyOtp(state.phoneNumber, otp, role: state.role);
|
|
},
|
|
),
|
|
const SizedBox(height: 14),
|
|
|
|
TextButton(
|
|
onPressed: isLoading ? null : () => context.read<AuthCubit>().checkSession(),
|
|
child: const Text(
|
|
'تغيير رقم الهاتف ↺',
|
|
style: TextStyle(
|
|
color: AppColors.textSecondaryDark,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
/// Step 3: National ID Verification (For Students)
|
|
Widget _buildNationalIdStep(BuildContext context, AuthNeedsNationalId state, bool isLoading) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Center(
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.saqelCyan.withAlpha(35),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(CupertinoIcons.person_crop_square_fill, color: AppColors.saqelCyan, size: 30),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
AppStrings.enterNationalIdTitle,
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.headlineMedium.copyWith(color: Colors.white),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
AppStrings.enterNationalIdSubtitle,
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryTextField(
|
|
controller: _nationalIdController,
|
|
label: AppStrings.nationalId,
|
|
hint: '2008XXXXXX',
|
|
keyboardType: TextInputType.number,
|
|
enabled: !isLoading,
|
|
prefixIcon: const Icon(CupertinoIcons.number, color: AppColors.saqelCyan, size: 20),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryButton(
|
|
text: AppStrings.continueBtn,
|
|
isLoading: isLoading,
|
|
onPressed: () {
|
|
final nationalId = _nationalIdController.text.trim();
|
|
if (nationalId.isEmpty) {
|
|
SaqelToast.showError(context, 'يرجى إدخال الرقم الوطني الخاص بالطالب');
|
|
return;
|
|
}
|
|
context.read<AuthCubit>().verifyNationalId(state.identityToken, nationalId);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
/// Step 4: Onboarding (For New Students not registered in school roster)
|
|
Widget _buildOnboardingStep(BuildContext context, AuthNeedsOnboarding state, bool isLoading) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Center(
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.appleBlue.withAlpha(35),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(CupertinoIcons.sparkles, color: AppColors.appleBlue, size: 30),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
'استكمال بيانات الطالب 🎓',
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.headlineMedium.copyWith(color: Colors.white),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'يرجى إدخال اسمك الكامل واختيار الفرع الأكاديمي لضبط المنهاج',
|
|
textAlign: TextAlign.center,
|
|
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryTextField(
|
|
controller: _fullNameController,
|
|
label: 'الاسم الكامل للطالب',
|
|
hint: 'حمزة أحمد الغويريين',
|
|
enabled: !isLoading,
|
|
prefixIcon: const Icon(CupertinoIcons.person_fill, color: AppColors.appleBlue, size: 20),
|
|
),
|
|
const SizedBox(height: 18),
|
|
|
|
// Stream Selector (العلمي / الأدبي)
|
|
Text(
|
|
'الفرع الأكاديمي',
|
|
style: AppTypography.titleMedium.copyWith(color: AppColors.textSecondaryDark, fontSize: 13),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: isLoading ? null : () => setState(() => _selectedStream = 'scientific'),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: _selectedStream == 'scientific' ? AppColors.saqelCyan.withAlpha(30) : AppColors.darkSurface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: _selectedStream == 'scientific' ? AppColors.saqelCyan : AppColors.darkCardBorder,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'الفرع العلمي 🔬',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: _selectedStream == 'scientific' ? AppColors.saqelCyan : Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: isLoading ? null : () => setState(() => _selectedStream = 'literary'),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: _selectedStream == 'literary' ? AppColors.guardianAmber.withAlpha(30) : AppColors.darkSurface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: _selectedStream == 'literary' ? AppColors.guardianAmber : AppColors.darkCardBorder,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'الفرع الأدبي 📚',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: _selectedStream == 'literary' ? AppColors.guardianAmber : Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
LuxuryButton(
|
|
text: 'فتح الملف والدخول للمنصة 🚀',
|
|
isLoading: isLoading,
|
|
onPressed: () {
|
|
final name = _fullNameController.text.trim();
|
|
if (name.isEmpty) {
|
|
SaqelToast.showError(context, 'يرجى إدخال الاسم الكامل للطالب');
|
|
return;
|
|
}
|
|
context.read<AuthCubit>().setupProfile(
|
|
identityToken: state.identityToken,
|
|
fullName: name,
|
|
nationalId: state.nationalId,
|
|
gradeLevel: _selectedGrade,
|
|
stream: _selectedStream,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|