Update Saqel Platform: 2026-08-31 01:52:23
This commit is contained in:
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user