initial commit: push everything
This commit is contained in:
339
lib/views/onboardscreens/Sign_in.dart
Normal file
339
lib/views/onboardscreens/Sign_in.dart
Normal file
@@ -0,0 +1,339 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user