Update Saqel Platform: 2026-08-31 01:52:23

This commit is contained in:
Hamza-Ayed
2026-08-31 01:52:24 +03:00
parent 4e29915383
commit ccf5f13a09
419 changed files with 19777 additions and 41 deletions
@@ -0,0 +1,527 @@
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,
);
},
),
],
);
}
}
@@ -0,0 +1,559 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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 '../../../data/models/user_model.dart';
import '../../../data/models/lesson_model.dart';
import '../../../logic/cubits/auth_cubit.dart';
import '../../../logic/cubits/dashboard_cubits.dart';
import '../../widgets/luxury_widgets.dart';
class UnifiedHomeScreen extends StatefulWidget {
final UserModel user;
final String activeRole;
const UnifiedHomeScreen({
super.key,
required this.user,
required this.activeRole,
});
@override
State<UnifiedHomeScreen> createState() => _UnifiedHomeScreenState();
}
class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
late String _currentRole;
@override
void initState() {
super.initState();
_currentRole = widget.activeRole;
_refreshData();
}
void _refreshData() {
if (_currentRole == 'student') {
context.read<StudentCubit>().fetchLessons(
gradeLevel: widget.user.gradeLevel,
stream: widget.user.stream,
initialReadiness: widget.user.readinessScore ?? 0.0,
);
} else {
context.read<GuardianCubit>().fetchDashboard();
}
}
void _switchRole(String newRole) {
if (_currentRole == newRole) return;
setState(() => _currentRole = newRole);
context.read<AuthCubit>().switchRole(newRole);
_refreshData();
}
@override
Widget build(BuildContext context) {
final isStudent = _currentRole == 'student';
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: isStudent ? const Alignment(0.8, -0.6) : const Alignment(-0.8, -0.6),
radius: 1.3,
colors: isStudent
? const [Color(0xFF0A2540), AppColors.darkBackground]
: const [Color(0xFF332008), AppColors.darkBackground],
),
),
child: SafeArea(
child: Column(
children: [
_buildTopBar(isStudent),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isStudent ? _buildStudentTab() : _buildGuardianTab(),
),
),
],
),
),
),
);
}
/// Top App Bar with Persona Switcher & Logout
Widget _buildTopBar(bool isStudent) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: const BoxDecoration(
color: Color(0x990D111A),
border: Border(
bottom: BorderSide(color: AppColors.darkCardBorder),
),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isStudent
? const [AppColors.appleBlue, AppColors.saqelCyan]
: const [AppColors.guardianAmber, Color(0xFFFBBF24)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'ص',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: Colors.black),
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.user.name,
style: AppTypography.titleMedium.copyWith(color: Colors.white),
),
Text(
isStudent ? AppStrings.studentPortal : AppStrings.guardianPortal,
style: AppTypography.labelSmall.copyWith(color: AppColors.textMutedDark),
),
],
),
const Spacer(),
// Persona Toggle
Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Row(
children: [
_buildRolePill('الطالب', isStudent, () => _switchRole('student')),
_buildRolePill('ولي الأمر', !isStudent, () => _switchRole('guardian')),
],
),
),
const SizedBox(width: 8),
// Logout Icon
IconButton(
icon: const Icon(CupertinoIcons.power, color: AppColors.errorRed, size: 20),
onPressed: () => context.read<AuthCubit>().logout(),
tooltip: AppStrings.logout,
),
],
),
);
}
Widget _buildRolePill(String title, bool isSelected, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isSelected ? const Color(0x33FFFFFF) : Colors.transparent,
borderRadius: BorderRadius.circular(14),
),
child: Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
color: isSelected ? Colors.white : AppColors.textMutedDark,
),
),
),
);
}
/// 🎓 Real Student Tab (Data from API)
Widget _buildStudentTab() {
return BlocBuilder<StudentCubit, StudentState>(
builder: (context, state) {
if (state is StudentLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColors.saqelCyan),
);
} else if (state is StudentError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.exclamationmark_triangle, color: AppColors.errorRed, size: 40),
const SizedBox(height: 12),
Text(state.message, style: const TextStyle(color: Colors.white)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _refreshData,
child: Text(AppStrings.retry),
),
],
),
);
} else if (state is StudentEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.book, color: AppColors.textMutedDark, size: 48),
const SizedBox(height: 12),
Text(AppStrings.noLessonsFound, style: const TextStyle(color: AppColors.textSecondaryDark)),
],
),
);
} else if (state is StudentLoaded) {
return RefreshIndicator(
onRefresh: () async => _refreshData(),
child: ListView(
padding: const EdgeInsets.all(20),
children: [
// Real Readiness Score Card
LuxuryCard(
borderColor: AppColors.saqelCyan.withAlpha(50),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
child: Text(
widget.user.gradeLevel ?? 'توجيهي 2008',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: AppColors.saqelCyan,
),
),
),
const SizedBox(height: 8),
Text(
AppStrings.readinessScore,
style: AppTypography.headlineMedium.copyWith(color: Colors.white),
),
const SizedBox(height: 4),
Text(
'تقييم حقيقي مبني على الكويزات والفحوصات السقراطية',
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark, fontSize: 12),
),
],
),
),
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: AppColors.saqelCyan, width: 3),
),
child: Center(
child: Text(
'${state.readinessScore.toStringAsFixed(0)}%',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: Colors.white),
),
),
),
],
),
),
const SizedBox(height: 20),
// Real Lessons List from API
Text(
AppStrings.tawjihiCurriculum,
style: AppTypography.titleMedium.copyWith(color: Colors.white),
),
const SizedBox(height: 12),
...state.lessons.map((lesson) => _buildRealLessonItem(lesson)),
],
),
);
}
return const SizedBox.shrink();
},
);
}
Widget _buildRealLessonItem(LessonModel lesson) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: LuxuryCard(
padding: const EdgeInsets.all(16),
onTap: () {
SaqelToast.showInfo(
context,
'تشغيل الدرس: ${lesson.title}',
title: lesson.courseTitle,
);
},
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppColors.appleBlue.withAlpha(30),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(CupertinoIcons.play_fill, color: AppColors.appleBlue, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
lesson.courseTitle,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: AppColors.saqelCyan,
),
),
if (lesson.isAiVersion) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: AppColors.adminViolet.withAlpha(40),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'ذكاء اصطناعي ✨',
style: TextStyle(fontSize: 9, fontWeight: FontWeight.w700, color: Color(0xFFA78BFA)),
),
),
],
],
),
const SizedBox(height: 4),
Text(
lesson.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 2),
Text(
lesson.formattedDuration,
style: const TextStyle(fontSize: 11, color: AppColors.textMutedDark),
),
],
),
),
],
),
),
);
}
/// 👨‍👧‍👦 Real Guardian Tab (Data from API)
Widget _buildGuardianTab() {
return BlocBuilder<GuardianCubit, GuardianState>(
builder: (context, state) {
if (state is GuardianLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColors.guardianAmber),
);
} else if (state is GuardianError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.exclamationmark_triangle, color: AppColors.errorRed, size: 40),
const SizedBox(height: 12),
Text(state.message, style: const TextStyle(color: Colors.white)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _refreshData,
child: Text(AppStrings.retry),
),
],
),
);
} else if (state is GuardianEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.person_2, color: AppColors.textMutedDark, size: 48),
const SizedBox(height: 12),
Text(AppStrings.noChildrenLinked, style: const TextStyle(color: AppColors.textSecondaryDark)),
],
),
);
} else if (state is GuardianLoaded) {
final selectedChild = state.children[state.selectedChildIndex];
return RefreshIndicator(
onRefresh: () async => _refreshData(),
child: ListView(
padding: const EdgeInsets.all(20),
children: [
Text(
AppStrings.linkedChildren,
style: AppTypography.titleMedium.copyWith(color: Colors.white),
),
const SizedBox(height: 10),
// Multi-Child Switcher
Row(
children: List.generate(state.children.length, (index) {
final isSelected = index == state.selectedChildIndex;
final child = state.children[index];
return Expanded(
child: GestureDetector(
onTap: () => context.read<GuardianCubit>().selectChild(index),
child: Container(
margin: EdgeInsets.only(left: index == 0 ? 8 : 0),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected ? AppColors.guardianAmber.withAlpha(35) : AppColors.darkCard,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isSelected ? AppColors.guardianAmber : AppColors.darkCardBorder,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
child.name,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: isSelected ? const Color(0xFFFBBF24) : Colors.white,
),
),
const SizedBox(height: 2),
Text(
child.gradeLevel ?? 'طالب مسجل',
style: const TextStyle(fontSize: 10.5, color: AppColors.textMutedDark),
),
],
),
),
),
);
}),
),
const SizedBox(height: 20),
// Selected Child Performance Bento
LuxuryCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'تقرير الطالب: ${selectedChild.name}',
style: AppTypography.titleMedium.copyWith(color: Colors.white),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppColors.teacherEmerald.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'جاهزية ${selectedChild.readinessScore.toStringAsFixed(1)}%',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w800,
color: AppColors.teacherEmerald,
),
),
),
],
),
const SizedBox(height: 6),
Text(
'المدرسة: ${selectedChild.schoolName ?? 'مدرسة الثقافة العسكرية'}',
style: const TextStyle(fontSize: 12, color: AppColors.textMutedDark),
),
const SizedBox(height: 16),
const Divider(color: AppColors.darkCardBorder),
const SizedBox(height: 10),
_buildGuardianStat('الامتحانات المنجزة بنجاح', '${selectedChild.examsPassed} من ${selectedChild.examsTotal}'),
_buildGuardianStat('الرقم الوطني', selectedChild.nationalId ?? 'مسجل بالهاتف'),
],
),
),
const SizedBox(height: 16),
// Real WhatsApp Reports Integration Banner
LuxuryCard(
backgroundColor: const Color(0xFF075E54).withAlpha(35),
borderColor: AppColors.whatsappGreen.withAlpha(80),
child: const Row(
children: [
Icon(CupertinoIcons.chat_bubble_fill, color: AppColors.whatsappGreen, size: 24),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'تقارير بوابة نبيه للواتساب',
style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w700, color: Colors.white),
),
SizedBox(height: 2),
Text(
'تصلك إشعارات الأداء والغياب آلياً عبر رقم الواتساب المسجل',
style: TextStyle(fontSize: 11, color: AppColors.textSecondaryDark),
),
],
),
),
],
),
),
],
),
);
}
return const SizedBox.shrink();
},
);
}
Widget _buildGuardianStat(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondaryDark)),
const Spacer(),
Text(value, style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Colors.white)),
],
),
);
}
}
@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_theme.dart';
/// Reusable Apple-Grade Frosted Glass Card
class LuxuryCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final VoidCallback? onTap;
final Color? borderColor;
final Color? backgroundColor;
const LuxuryCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(20),
this.onTap,
this.borderColor,
this.backgroundColor,
});
@override
Widget build(BuildContext context) {
final cardWidget = Container(
padding: padding,
decoration: BoxDecoration(
color: backgroundColor ?? AppColors.darkCard,
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: borderColor ?? AppColors.darkCardBorder,
width: 1,
),
boxShadow: const [
BoxShadow(
color: Color(0x66000000),
blurRadius: 25,
offset: Offset(0, 10),
),
],
),
child: child,
);
if (onTap != null) {
return GestureDetector(
onTap: onTap,
child: cardWidget,
);
}
return cardWidget;
}
}
/// Reusable Apple-Grade Primary Button
class LuxuryButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool isLoading;
final Color? backgroundColor;
final Color? textColor;
final IconData? icon;
const LuxuryButton({
super.key,
required this.text,
required this.onPressed,
this.isLoading = false,
this.backgroundColor,
this.textColor,
this.icon,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? AppColors.appleBlue,
foregroundColor: textColor ?? Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
),
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, size: 20),
const SizedBox(width: 8),
],
Text(
text,
style: AppTypography.titleMedium.copyWith(
color: textColor ?? Colors.white,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
}
/// Reusable Apple-Grade Text Field
class LuxuryTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final String hint;
final TextInputType keyboardType;
final Widget? prefixIcon;
final bool enabled;
const LuxuryTextField({
super.key,
required this.controller,
required this.label,
required this.hint,
this.keyboardType = TextInputType.text,
this.prefixIcon,
this.enabled = true,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppTypography.titleMedium.copyWith(
color: AppColors.textSecondaryDark,
fontSize: 13,
),
),
const SizedBox(height: 8),
TextField(
controller: controller,
keyboardType: keyboardType,
enabled: enabled,
style: AppTypography.bodyMedium.copyWith(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w600,
),
decoration: InputDecoration(
hintText: hint,
hintStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.textMutedDark,
),
prefixIcon: prefixIcon,
filled: true,
fillColor: AppColors.darkSurface,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: AppColors.darkCardBorder),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: AppColors.darkCardBorder),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: AppColors.appleBlue, width: 1.5),
),
),
),
],
);
}
}