Files

340 lines
13 KiB
Dart

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<SignIn> createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
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<SystemUiOverlayStyle>(
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<LoginController>(
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,
),
),
),
),
),
),
),
);
}
}