340 lines
13 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|