import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:nearle/views/helpers/constants/Colorconstants.dart'; import 'package:nearle/views/helpers/constants/Font_constant.dart'; import 'package:nearle/views/onboardscreens/otp_page.dart'; import 'package:nearle/views/onboardscreens/Mpin.dart'; import 'package:nearle/controllers/auth.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:flutter/gestures.dart'; import 'package:nearle/providers/notifications/notificationservce.dart'; class LoginController extends GetxController { var isChecked = true.obs; final AuthController auth = Get.put(AuthController()); } class SignIn extends StatefulWidget { const SignIn({super.key}); @override State createState() => _SignInState(); } class _SignInState extends State { final LoginController controller = Get.put(LoginController()); final TextEditingController phoneController = TextEditingController(); final FocusNode _phoneFocusNode = FocusNode(); bool isPhoneValid = false; bool isLoading = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { FocusScope.of(context).requestFocus(_phoneFocusNode); // Request notification permission the first time Sign In screen is shown NotificationServce.initialize(context); }); } bool _validatePhoneNumber(String number) { final RegExp regExp = RegExp(r'^[6-9]\d{9}$'); return regExp.hasMatch(number); } @override void dispose() { _phoneFocusNode.dispose(); phoneController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final height = size.height; final width = size.width; double scale = 1.0; if (width < 380) scale = 0.9; if (width > 800) scale = 1.2; return AnnotatedRegion( value: SystemUiOverlayStyle.dark.copyWith( statusBarColor: Colors.white, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, systemNavigationBarColor: Colors.white, ), child: Scaffold( backgroundColor: Colors.white, body: SafeArea( top: false, left: true, right: true, bottom: true, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric( horizontal: width * 0.05, vertical: height * 0.02, ), child: GetBuilder( builder: (_) => Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: height * 0.04), // Restore hero image at the top like before Image.asset( 'assets/images/Nearle Bike.png', height: height * 0.28, fit: BoxFit.contain, ), SizedBox(height: height * 0.03), Text( "Sign In", style: TextStyle( fontSize: height * 0.05 * scale, fontWeight: FontWeight.bold, color: ColorConstants.primaryColor, fontFamily: FontConstants.fontFamily, ), ), SizedBox(height: height * 0.02), Text( "Enter your mobile number to get started.", textAlign: TextAlign.center, style: TextStyle( color: Colors.black54, fontSize: FontConstants.xLarge(context), fontFamily: FontConstants.fontFamily, fontWeight: FontWeight.w500, ), ), SizedBox(height: height * 0.05), // Phone number field SizedBox( height: height * 0.07, width: width * 0.9, child: TextField( controller: phoneController, focusNode: _phoneFocusNode, keyboardType: TextInputType.number, maxLength: 10, style: TextStyle( fontSize: FontConstants.large(context), fontWeight: FontWeight.w500, color: Colors.black, ), onChanged: (value) { setState(() { isPhoneValid = _validatePhoneNumber(value); }); if (value.length == 10 && isPhoneValid) { FocusScope.of(context).unfocus(); } }, decoration: InputDecoration( counterText: '', labelText: 'Enter mobile number', labelStyle: TextStyle( color: Colors.grey, fontSize: width * 0.04, fontFamily: FontConstants.fontFamily, ), prefixIcon: Padding( padding: EdgeInsets.symmetric( horizontal: width * 0.02, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "assets/images/in.png", height: height * 0.045, width: width * 0.09, ), SizedBox(width: width * 0.01), Text( "+91", style: TextStyle( fontSize: FontConstants.large(context), fontFamily: FontConstants.fontFamily, ), ), ], ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide( color: Color(0xFF662582), width: 1.5, ), ), ), ), ), // Validation message if (!isPhoneValid && phoneController.text.isNotEmpty) Padding( padding: EdgeInsets.only( left: width * 0.02, top: height * 0.005, ), child: Text( 'Enter a valid 10-digit mobile number', style: TextStyle( color: Colors.red.shade700, fontSize: width * 0.03, ), ), ), SizedBox(height: height * 0.02), // Terms text with clickable T&C and Privacy Policy RichText( textAlign: TextAlign.center, text: TextSpan( style: TextStyle( fontSize: 15, fontFamily: FontConstants.fontFamily, fontWeight: FontWeight.w500, color: Colors.black87, ), children: [ const TextSpan(text: 'By continuing, you agree to '), TextSpan( text: 'T&C', style: const TextStyle( color: Colors.blue, decoration: TextDecoration.none, ), recognizer: TapGestureRecognizer() ..onTap = () async { final uri = Uri.parse( 'https://nearle.in/terms', ); final ok = await launchUrl( uri, mode: LaunchMode.externalApplication, ); if (!ok) { await launchUrl( uri, mode: LaunchMode.inAppWebView, ); } }, ), const TextSpan(text: ' and '), TextSpan( text: 'Privacy Policy', style: const TextStyle( color: Colors.blue, decoration: TextDecoration.none, ), recognizer: TapGestureRecognizer() ..onTap = () async { final uri = Uri.parse( 'https://nearle.in/privacy', ); final ok = await launchUrl( uri, mode: LaunchMode.externalApplication, ); if (!ok) { await launchUrl( uri, mode: LaunchMode.inAppWebView, ); } }, ), ], ), ), SizedBox(height: height * 0.02), ], ), ), ), ), ), // Bottom Button bottomNavigationBar: SafeArea( child: Padding( padding: EdgeInsets.all(width * 0.04), child: SizedBox( height: height * 0.065, width: double.infinity, child: ElevatedButton( onPressed: isPhoneValid && !isLoading ? () async { setState(() => isLoading = true); final decision = await controller.auth.precheckPhone( phoneController.text, ); setState(() => isLoading = false); if (decision == AuthNext.notRegistered) { return; } else if (decision == AuthNext.otp) { await controller.auth.sendOtp(phoneController.text); Get.to(OtpPage()); } else if (decision == AuthNext.verifyPin) { Get.to(() => Mpin()); } else { Get.snackbar( 'Error', 'Unable to proceed. Please try again.', ); } } : null, style: ElevatedButton.styleFrom( backgroundColor: isPhoneValid ? const Color(0xFF662582) : Colors.grey.shade400, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), padding: EdgeInsets.symmetric(vertical: height * 0.015), ), child: isLoading ? SizedBox( height: height * 0.035, width: height * 0.035, child: const CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ), ) : Text( 'Next', style: TextStyle( color: Colors.white, fontSize: width * 0.06, fontWeight: FontWeight.bold, ), ), ), ), ), ), ), ); } }