Files
daily_mobileapp_customer/lib/view/authentication/login_view.dart
2026-05-26 18:01:57 +05:30

443 lines
18 KiB
Dart

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../controllers/authentication/auth_controller.dart';
import '../authentication/verification_view.dart';
class Login_view extends StatelessWidget {
Login_view({super.key});
final TextEditingController phoneController = TextEditingController();
// Fix: RxString mirrors the field so Obx rebuilds on every keystroke/clear
final RxString phoneValue = ''.obs;
final RxBool isAgreed = false.obs;
@override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
// Top curved purple background
Positioned(
top: 0,
left: 0,
right: 0,
child: CustomPaint(
size: Size(screenSize.width, screenSize.height * 0.52),
painter: _TopCurvePainter(),
),
),
// Decorative circles
Positioned(
top: screenSize.height * 0.04,
right: -30,
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.06),
),
),
),
Positioned(
top: screenSize.height * 0.10,
left: -20,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.06),
),
),
),
SafeArea(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header area
Padding(
padding: EdgeInsets.symmetric(
horizontal: screenSize.width * 0.06,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: screenSize.height * 0.03),
// Logo / brand chip
SizedBox(height: screenSize.height * 0.02),
const Text(
"Groceries & More,\nDelivered in Minutes!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800,
fontSize: 26,
height: 1.25,
letterSpacing: -0.5,
),
),
SizedBox(height: screenSize.height * 0.008),
const Text(
"Sign in to enjoy lightning-fast delivery!",
style: TextStyle(
color: Colors.white70,
fontSize: 14,
height: 1.4,
),
),
],
),
),
// Image — right-aligned, overlapping curve
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Image.asset(
"assets/images/loginImage.png",
height: screenSize.height * 0.30,
fit: BoxFit.contain,
),
],
),
// White card form area
Container(
margin: EdgeInsets.symmetric(
horizontal: screenSize.width * 0.05),
padding: EdgeInsets.symmetric(
horizontal: screenSize.width * 0.05,
vertical: screenSize.height * 0.03,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: const Color(0xFF662582).withOpacity(0.08),
blurRadius: 30,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Login or Signup",
style: TextStyle(
color: Color(0xFF1A1A2E),
fontWeight: FontWeight.w800,
fontSize: 20,
letterSpacing: -0.3,
),
),
const SizedBox(height: 4),
const Text(
"Enter your mobile number to continue",
style: TextStyle(
color: Color(0xFF9CA3AF),
fontSize: 13,
),
),
SizedBox(height: screenSize.height * 0.025),
// Phone input
TextFormField(
controller: phoneController,
maxLength: 10,
onChanged: (value) {
phoneValue.value = value; // Fix: keep Rx in sync
if (value.length == 10) {
FocusScope.of(context).unfocus();
}
},
decoration: InputDecoration(
labelText: "Mobile Number",
hintText: "Enter 10-digit number",
counterText: "",
labelStyle: const TextStyle(
color: Color(0xFF662582), fontSize: 13),
hintStyle:
const TextStyle(color: Color(0xFFD1D5DB)),
prefixIcon: Container(
width: screenSize.width * 0.2,
padding:
const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"+91",
style: TextStyle(
color: Color(0xFF662582),
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
const SizedBox(width: 8),
Container(
width: 1,
height: 20,
color: const Color(0xFFE5E7EB),
),
],
),
),
// filled: true,
// fillColor: const Color(0xFFF9F5FF),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFFE9D5FF), width: 1.2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFFE9D5FF), width: 1.2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFF662582), width: 1.8),
),
),
style: const TextStyle(
color: Color(0xFF1A1A2E),
fontWeight: FontWeight.w500,
fontSize: 16,
letterSpacing: 1,
),
cursorColor: const Color(0xFF662582),
keyboardType: TextInputType.phone,
),
SizedBox(height: screenSize.height * 0.02),
// Agree checkbox row
Obx(() => GestureDetector(
onTap: () => isAgreed.value = !isAgreed.value,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedContainer(
duration:
const Duration(milliseconds: 200),
width: 20,
height: 20,
decoration: BoxDecoration(
color: isAgreed.value
? const Color(0xFF662582)
: Colors.white,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: isAgreed.value
? const Color(0xFF662582)
: const Color(0xFFD1D5DB),
width: 1.5,
),
),
child: isAgreed.value
? const Icon(Icons.check,
size: 13, color: Colors.white)
: null,
),
const SizedBox(width: 10),
Expanded(
child: RichText(
text: TextSpan(
style: const TextStyle(
color: Color(0xFF6B7280),
fontSize: 12.5,
),
children: [
const TextSpan(
text: "I agree to the "),
TextSpan(
text: "Terms & Privacy Policy",
style: const TextStyle(
color: Color(0xFF662582),
fontWeight: FontWeight.w600,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
Get.to(() => WebViewScreen(
url:
"https://nearle.in/privacy",
title:
"Terms & Privacy Policy",
));
},
),
],
),
),
),
],
),
)),
SizedBox(height: screenSize.height * 0.025),
// Continue button
SizedBox(
width: double.infinity,
height: 52,
child: Obx(() {
final authController =
Get.find<AuthController>();
final phone = phoneValue.value.trim(); // Fix: reactive read
bool isValidMobile(String phone) {
return RegExp(r'^[6-9]\d{9}$').hasMatch(phone);
}
final bool isPhoneValid = isValidMobile(phone);
final bool canProceed = isPhoneValid &&
isAgreed.value &&
!authController.isLoading.value;
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF662582),
foregroundColor: Colors.white,
disabledBackgroundColor:
const Color(0xFFD8B4FE),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 0,
),
onPressed: canProceed
? () =>
authController.signIn(context, phone)
: null,
child: authController.isLoading.value
? const SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(
Colors.white),
),
)
: const Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
"Continue",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
),
),
SizedBox(width: 8),
Icon(Icons.arrow_forward_rounded,
size: 18),
],
),
);
}),
),
],
),
),
SizedBox(height: screenSize.height * 0.04),
],
),
),
),
],
),
);
}
}
// ── Custom painter for top curved purple background ──────────────────────────
class _TopCurvePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..shader = const LinearGradient(
colors: [Color(0xFF8B2FC9), Color(0xFF662582)],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
final path = Path();
path.lineTo(0, size.height * 0.85);
path.quadraticBezierTo(
size.width * 0.25,
size.height * 1.0,
size.width * 0.5,
size.height * 0.92,
);
path.quadraticBezierTo(
size.width * 0.75,
size.height * 0.84,
size.width,
size.height * 0.94,
);
path.lineTo(size.width, 0);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(_TopCurvePainter oldDelegate) => false;
}
// ── WebView screen (unchanged) ────────────────────────────────────────────────
class WebViewScreen extends StatefulWidget {
final String url;
final String title;
const WebViewScreen({
super.key,
required this.url,
required this.title,
});
@override
State<WebViewScreen> createState() => _WebViewScreenState();
}
class _WebViewScreenState extends State<WebViewScreen> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse(widget.url));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: WebViewWidget(controller: controller),
);
}
}