import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:Intaleq/controller/auth/login_controller.dart'; import '../../constant/colors.dart'; import '../../controller/auth/otp_controller.dart'; import '../../controller/local/phone_intel/intl_phone_field.dart'; // ───────────────────────────────────────────────────────────────────────────── // SHARED DESIGN TOKENS // ───────────────────────────────────────────────────────────────────────────── Color _textMain(bool isDark) => isDark ? Colors.white : const Color(0xFF1A1A2E); Color _textSub(bool isDark) => isDark ? Colors.white60 : const Color(0xFF6B7280); InputBorder _inputBorder(bool isDark, {bool focused = false}) => OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: focused ? BorderSide(color: AppColor.primaryColor, width: 1.5) : BorderSide( color: isDark ? Colors.white.withOpacity(0.12) : const Color(0xFFE5E7EB), ), ); // ───────────────────────────────────────────────────────────────────────────── // AUTH SCREEN (shared scaffold for all auth steps) // ───────────────────────────────────────────────────────────────────────────── /// A visually revamped authentication screen with a glassmorphism effect. /// It provides a consistent and beautiful UI for all authentication steps. /// /// A hidden feature for testers is included: a long-press on the logo /// will open a dialog for email/password login, suitable for app reviews. class AuthScreen extends StatelessWidget { final String title; final String subtitle; final Widget form; const AuthScreen({ super.key, required this.title, required this.subtitle, required this.form, }); /// Shows a dialog for testers to log in using email and password. void _showTesterLoginDialog( BuildContext context, LoginController controller) { final isDark = Theme.of(context).brightness == Brightness.dark; final testerEmailController = TextEditingController(); final testerPasswordController = TextEditingController(); final testerFormKey = GlobalKey(); showDialog( context: context, barrierDismissible: true, builder: (BuildContext dialogContext) { return BackdropFilter( filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), child: AlertDialog( backgroundColor: isDark ? const Color(0xFF161622).withOpacity(0.97) : Colors.white.withOpacity(0.97), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)), titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), contentPadding: const EdgeInsets.all(24), title: Column( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( shape: BoxShape.circle, color: AppColor.primaryColor.withOpacity(0.12), ), child: Icon(Icons.admin_panel_settings_outlined, color: AppColor.primaryColor, size: 28), ), const SizedBox(height: 12), Text( 'App Tester Login'.tr, textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.w700, fontSize: 17, color: _textMain(isDark), ), ), ], ), content: Form( key: testerFormKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ _AuthTextField( controller: testerEmailController, label: 'Email'.tr, icon: Icons.email_outlined, isDark: isDark, keyboardType: TextInputType.emailAddress, validator: (value) => value == null || !value.contains('@') ? 'Enter a valid email'.tr : null, ), const SizedBox(height: 14), _AuthTextField( controller: testerPasswordController, label: 'Password'.tr, icon: Icons.lock_outline, isDark: isDark, obscureText: true, validator: (value) => value == null || value.isEmpty ? 'Enter a password'.tr : null, ), ], ), ), actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), actions: [ Row( children: [ Expanded( child: TextButton( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isDark ? Colors.white12 : const Color(0xFFE5E7EB), ), ), ), onPressed: () => Navigator.of(dialogContext).pop(), child: Text('Cancel'.tr, style: TextStyle( color: _textSub(isDark), fontWeight: FontWeight.w500)), ), ), const SizedBox(width: 10), Expanded( child: _PrimaryButton( label: 'Login'.tr, onPressed: () { if (testerFormKey.currentState!.validate()) { controller.emailController.text = testerEmailController.text; controller.passwordController.text = testerPasswordController.text; controller.login(); Navigator.of(dialogContext).pop(); } }, ), ), ], ), ], ), ); }, ); } @override Widget build(BuildContext context) { final loginController = Get.find(); final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( body: Stack( children: [ // ── Gradient background ──────────────────────────────────── Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ const Color(0xFF0D0D14), const Color(0xFF12121E), const Color(0xFF161622), ] : [ const Color(0xFFF8F9FF), const Color(0xFFEFF1FB), const Color(0xFFFFFFFF), ], ), ), ), // ── Decorative Shape 1 (top-left) ───────────────────────── Positioned( top: -90, left: -70, child: Container( width: 280, height: 280, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ AppColor.primaryColor.withOpacity(isDark ? 0.18 : 0.10), AppColor.primaryColor.withOpacity(0.0), ], ), ), ), ), // ── Decorative Shape 2 (bottom-right) ──────────────────── Positioned( bottom: -110, right: -90, child: Container( width: 340, height: 340, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ AppColor.primaryColor.withOpacity(isDark ? 0.12 : 0.07), AppColor.primaryColor.withOpacity(0.0), ], ), ), ), ), // ── Content ─────────────────────────────────────────────── SafeArea( child: Center( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Logo GestureDetector( onLongPress: () => _showTesterLoginDialog(context, loginController), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white.withOpacity(0.05) : Colors.white.withOpacity(0.9), border: Border.all( color: isDark ? Colors.white.withOpacity(0.1) : AppColor.primaryColor.withOpacity(0.15), width: 1.5, ), boxShadow: [ BoxShadow( color: AppColor.primaryColor.withOpacity(0.18), blurRadius: 24, offset: const Offset(0, 8), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(50), child: Image.asset('assets/images/logo.gif', height: 96), ), ), ), const SizedBox(height: 22), // Title Text( title, textAlign: TextAlign.center, style: TextStyle( fontSize: 26, fontWeight: FontWeight.w700, letterSpacing: 0.2, color: _textMain(isDark), ), ), const SizedBox(height: 8), // Subtitle Text( subtitle, textAlign: TextAlign.center, style: TextStyle( fontSize: 14.5, height: 1.55, color: _textSub(isDark), ), ), const SizedBox(height: 28), // ── Glass card ───────────────────────────────── ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16), child: Container( padding: const EdgeInsets.all(24.0), decoration: BoxDecoration( color: isDark ? Colors.white.withOpacity(0.05) : Colors.white.withOpacity(0.72), borderRadius: BorderRadius.circular(24), border: Border.all( color: isDark ? Colors.white.withOpacity(0.09) : Colors.white.withOpacity(0.9), width: 1, ), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withOpacity(0.28) : Colors.black.withOpacity(0.06), blurRadius: 28, offset: const Offset(0, 10), ), ], ), child: form, ), ), ), const SizedBox(height: 18), // Tester link Material( color: Colors.transparent, child: InkWell( onTap: () => _showTesterLoginDialog(context, loginController), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 14), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.admin_panel_settings_outlined, size: 15, color: isDark ? Colors.white24 : Colors.black26, ), const SizedBox(width: 7), Text( 'For App Reviewers / Testers'.tr, style: TextStyle( color: isDark ? Colors.white24 : Colors.black26, fontWeight: FontWeight.w400, fontSize: 12, ), ), ], ), ), ), ), ], ), ), ), ), ], ), ); } } // ───────────────────────────────────────────────────────────────────────────── // PHONE NUMBER SCREEN // ───────────────────────────────────────────────────────────────────────────── class PhoneNumberScreen extends StatefulWidget { const PhoneNumberScreen({super.key}); @override State createState() => _PhoneNumberScreenState(); } class _PhoneNumberScreenState extends State { final _phoneController = TextEditingController(); final _formKey = GlobalKey(); bool _isLoading = false; void _submit() async { if (_formKey.currentState!.validate()) { setState(() => _isLoading = true); final rawPhone = _phoneController.text.trim().replaceFirst('+', ''); final success = await PhoneAuthHelper.sendOtp(rawPhone); if (success && mounted) { Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone)); } if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return AuthScreen( title: 'welcome to intaleq'.tr, subtitle: 'login or register subtitle'.tr, form: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ // Label row Row( children: [ Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: AppColor.primaryColor.withOpacity(0.12), ), child: Icon(Icons.phone_outlined, size: 18, color: AppColor.primaryColor), ), const SizedBox(width: 10), Text( 'Enter your phone number'.tr, style: TextStyle( color: _textMain(isDark), fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 18), // Phone field IntlPhoneField( showCountryFlag: false, searchText: 'Search country'.tr, languageCode: 'ar', style: TextStyle(color: _textMain(isDark), fontSize: 15), dropdownTextStyle: TextStyle(color: _textMain(isDark)), decoration: InputDecoration( labelText: 'Phone Number'.tr, hintText: 'witout zero'.tr, labelStyle: TextStyle(color: _textSub(isDark), fontSize: 13.5), hintStyle: TextStyle( color: isDark ? Colors.white24 : const Color(0xFFD1D5DB), ), filled: true, fillColor: isDark ? Colors.white.withOpacity(0.05) : Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), enabledBorder: _inputBorder(isDark), focusedBorder: _inputBorder(isDark, focused: true), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1.5), ), ), initialCountryCode: 'SY', onChanged: (phone) { _phoneController.text = phone.completeNumber; }, validator: (phone) { if (phone == null || phone.number.isEmpty) { return 'Please enter your phone number'.tr; } if (phone.number.startsWith('0')) { return 'Please enter the number without the leading 0'.tr; } if (phone.completeNumber.length < 10) { return 'Phone number seems too short'.tr; } return null; }, ), const SizedBox(height: 24), _isLoading ? _LoadingIndicator() : _PrimaryButton( label: 'send otp button'.tr, onPressed: _submit, ), ], ), ), ); } } // ───────────────────────────────────────────────────────────────────────────── // OTP VERIFICATION SCREEN // ───────────────────────────────────────────────────────────────────────────── class OtpVerificationScreen extends StatefulWidget { final String phoneNumber; const OtpVerificationScreen({super.key, required this.phoneNumber}); @override State createState() => _OtpVerificationScreenState(); } class _OtpVerificationScreenState extends State { final _formKey = GlobalKey(); final _otpController = TextEditingController(); bool _isLoading = false; void _submit() async { if (_formKey.currentState!.validate()) { setState(() => _isLoading = true); await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text.trim()); if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return AuthScreen( title: 'verify your number title'.tr, subtitle: 'otp sent subtitle'.trParams({'phoneNumber': widget.phoneNumber}), form: Column( mainAxisSize: MainAxisSize.min, children: [ // Label row Row( children: [ Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: AppColor.primaryColor.withOpacity(0.12), ), child: Icon(Icons.mark_email_read_outlined, size: 18, color: AppColor.primaryColor), ), const SizedBox(width: 10), Text( 'Enter the 5-digit code'.tr, style: TextStyle( color: _textMain(isDark), fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 24), // OTP Input — underline style, large characters Form( key: _formKey, child: Container( decoration: BoxDecoration( color: isDark ? Colors.white.withOpacity(0.05) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? Colors.white.withOpacity(0.1) : const Color(0xFFE5E7EB), ), ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: TextFormField( controller: _otpController, textAlign: TextAlign.center, keyboardType: TextInputType.number, maxLength: 3, style: TextStyle( fontSize: 30, fontWeight: FontWeight.w700, color: AppColor.primaryColor, letterSpacing: 20, ), decoration: InputDecoration( counterText: '', hintText: '···', hintStyle: TextStyle( color: isDark ? Colors.white12 : const Color(0xFFD1D5DB), letterSpacing: 20, fontSize: 30, fontWeight: FontWeight.w300, ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(vertical: 6), ), validator: (v) => v == null || v.length < 3 ? '' : null, ), ), ), // Progress dots const SizedBox(height: 14), ValueListenableBuilder( valueListenable: _otpController, builder: (_, value, __) { final filled = value.text.length.clamp(0, 3); return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (i) { final active = i < filled; return AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.symmetric(horizontal: 4), width: active ? 22 : 8, height: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), color: active ? AppColor.primaryColor : (isDark ? Colors.white12 : const Color(0xFFE5E7EB)), ), ); }), ); }, ), const SizedBox(height: 28), _isLoading ? _LoadingIndicator() : _PrimaryButton( label: 'verify and continue button'.tr, onPressed: _submit, ), ], ), ); } } // ───────────────────────────────────────────────────────────────────────────── // REGISTRATION SCREEN // ───────────────────────────────────────────────────────────────────────────── class RegistrationScreen extends StatefulWidget { final String phoneNumber; const RegistrationScreen({super.key, required this.phoneNumber}); @override State createState() => _RegistrationScreenState(); } class _RegistrationScreenState extends State { final _formKey = GlobalKey(); final _firstNameController = TextEditingController(); final _lastNameController = TextEditingController(); final _emailController = TextEditingController(); bool _isLoading = false; void _submit() async { if (_formKey.currentState!.validate()) { setState(() => _isLoading = true); await PhoneAuthHelper.registerUser( phoneNumber: widget.phoneNumber, firstName: _firstNameController.text.trim(), lastName: _lastNameController.text.trim(), email: _emailController.text.trim(), ); if (mounted) setState(() => _isLoading = false); } } Widget _buildTextFormField({ required BuildContext context, required TextEditingController controller, required String label, required IconData icon, TextInputType keyboardType = TextInputType.text, bool optional = false, String? Function(String?)? validator, }) { final isDark = Theme.of(context).brightness == Brightness.dark; return _AuthTextField( controller: controller, label: label, icon: icon, isDark: isDark, keyboardType: keyboardType, validator: validator, ); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return AuthScreen( title: 'one last step title'.tr, subtitle: 'complete profile subtitle'.tr, form: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header row Row( children: [ Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: AppColor.primaryColor.withOpacity(0.12), ), child: Icon(Icons.person_outline_rounded, size: 18, color: AppColor.primaryColor), ), const SizedBox(width: 10), Text( 'Complete your profile'.tr, style: TextStyle( color: _textMain(isDark), fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 20), Row( children: [ Expanded( child: _buildTextFormField( context: context, controller: _firstNameController, label: 'first name label'.tr, icon: Icons.badge_outlined, validator: (v) => v!.isEmpty ? 'first name required'.tr : null, ), ), const SizedBox(width: 12), Expanded( child: _buildTextFormField( context: context, controller: _lastNameController, label: 'last name label'.tr, icon: Icons.badge_outlined, validator: (v) => v!.isEmpty ? 'last name required'.tr : null, ), ), ], ), const SizedBox(height: 14), _buildTextFormField( context: context, controller: _emailController, label: 'email optional label'.tr, icon: Icons.email_outlined, keyboardType: TextInputType.emailAddress, optional: true, ), const SizedBox(height: 24), _isLoading ? _LoadingIndicator() : _PrimaryButton( label: 'complete registration button'.tr, onPressed: _submit, ), ], ), ), ); } } // ───────────────────────────────────────────────────────────────────────────── // SHARED PRIVATE WIDGETS (scoped to this file) // ───────────────────────────────────────────────────────────────────────────── /// Reusable styled text field with icon prefix. class _AuthTextField extends StatelessWidget { final TextEditingController controller; final String label; final IconData icon; final bool isDark; final bool obscureText; final TextInputType? keyboardType; final String? Function(String?)? validator; const _AuthTextField({ required this.controller, required this.label, required this.icon, required this.isDark, this.obscureText = false, this.keyboardType, this.validator, }); @override Widget build(BuildContext context) { return TextFormField( controller: controller, obscureText: obscureText, keyboardType: keyboardType, style: TextStyle( color: _textMain(isDark), fontSize: 14.5, ), decoration: InputDecoration( labelText: label, prefixIcon: Icon(icon, size: 20, color: isDark ? Colors.white38 : const Color(0xFF9CA3AF)), labelStyle: TextStyle(color: _textSub(isDark), fontSize: 13.5), filled: true, fillColor: isDark ? Colors.white.withOpacity(0.05) : Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), enabledBorder: _inputBorder(isDark), focusedBorder: _inputBorder(isDark, focused: true), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1.5), ), ), validator: validator, ); } } /// Full-width gradient primary button. class _PrimaryButton extends StatelessWidget { final String label; final VoidCallback onPressed; const _PrimaryButton({required this.label, required this.onPressed}); @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: 52, child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ AppColor.primaryColor, AppColor.primaryColor.withOpacity(0.8), ], ), boxShadow: [ BoxShadow( color: AppColor.primaryColor.withOpacity(0.32), blurRadius: 16, offset: const Offset(0, 6), ), ], ), child: ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), child: Text( label, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 15, letterSpacing: 0.4, ), ), ), ), ); } } /// Branded loading indicator. class _LoadingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( width: 28, height: 28, child: CircularProgressIndicator( color: AppColor.primaryColor, strokeWidth: 2.5, ), ); } }