change to map-saas api key and env - 2026-04-12
This commit is contained in:
@@ -35,187 +35,675 @@ class LoginPage extends StatelessWidget {
|
||||
if (box.read(BoxName.agreeTerms) != 'agreed')
|
||||
_buildAgreementPage(context, controller)
|
||||
else if (box.read(BoxName.locationPermission) != 'true')
|
||||
_buildLocationPermissionDialog(controller)
|
||||
// else if (box.read(BoxName.isTest).toString() == '0')
|
||||
// buildEmailPasswordForm(controller)
|
||||
_buildLocationPermissionDialog(context, controller)
|
||||
else
|
||||
// _buildLoginContent(controller, authController),
|
||||
PhoneNumberScreen()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAgreementPage(BuildContext context, LoginController controller) {
|
||||
// This UI can be identical to the one in LoginPage for consistency.
|
||||
// I am reusing the improved design from the previous request.
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.policy_outlined, size: 80, color: AppColor.primaryColor),
|
||||
const SizedBox(height: 20),
|
||||
Text("passenger agreement".tr,
|
||||
textAlign: TextAlign.center, style: AppStyle.headTitle2),
|
||||
const SizedBox(height: 30),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
style: AppStyle.title.copyWith(height: 1.5),
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
"To become a passenger, you must review and agree to the "
|
||||
.tr),
|
||||
TextSpan(
|
||||
text: 'Terms of Use'.tr,
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: AppColor.cyanBlue,
|
||||
fontWeight: FontWeight.bold),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(Uri.parse(
|
||||
'https://intaleq.xyz/intaleq/privacy_policy.php'));
|
||||
}),
|
||||
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
|
||||
],
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// SHARED HELPERS
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Subtle geometric background — two soft circles, no heavy blur needed.
|
||||
Widget _buildBackground(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Stack(
|
||||
children: [
|
||||
// Base gradient
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isDark
|
||||
? [const Color(0xFF0D0D14), const Color(0xFF161622)]
|
||||
: [const Color(0xFFF8F9FF), const Color(0xFFEFF1FB)],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar'
|
||||
? AppInformation.privacyPolicyArabic
|
||||
: AppInformation.privacyPolicy),
|
||||
),
|
||||
// Top-right accent circle
|
||||
Positioned(
|
||||
top: -80,
|
||||
right: -60,
|
||||
child: Container(
|
||||
width: 260,
|
||||
height: 260,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
AppColor.primaryColor.withOpacity(isDark ? 0.18 : 0.12),
|
||||
AppColor.primaryColor.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: Text('I Agree'.tr, style: AppStyle.title),
|
||||
value: controller.isAgreeTerms,
|
||||
onChanged: (value) => controller.changeAgreeTerm(),
|
||||
activeColor: AppColor.primaryColor,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: MyElevatedButton(
|
||||
title: 'Continue'.tr,
|
||||
onPressed: controller.isAgreeTerms
|
||||
? () => controller.saveAgreementTerms()
|
||||
: () {},
|
||||
),
|
||||
// Bottom-left accent circle
|
||||
Positioned(
|
||||
bottom: -100,
|
||||
left: -80,
|
||||
child: Container(
|
||||
width: 320,
|
||||
height: 320,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
AppColor.primaryColor.withOpacity(isDark ? 0.12 : 0.08),
|
||||
AppColor.primaryColor.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildEmailPasswordForm(LoginController controller) {
|
||||
/// Glassy card container used across screens.
|
||||
Widget _glassCard({
|
||||
required Widget child,
|
||||
required bool isDark,
|
||||
EdgeInsets padding = const EdgeInsets.all(20),
|
||||
double radius = 20,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
color: isDark
|
||||
? Colors.white.withOpacity(0.05)
|
||||
: Colors.white.withOpacity(0.75),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withOpacity(0.08)
|
||||
: Colors.white.withOpacity(0.9),
|
||||
width: 1,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 3),
|
||||
color: isDark
|
||||
? Colors.black.withOpacity(0.3)
|
||||
: Colors.black.withOpacity(0.06),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextFormField(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
controller: controller.emailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email'.tr,
|
||||
hintText: 'Your email address'.tr,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) => value == null ||
|
||||
value.isEmpty ||
|
||||
!value.contains('@') ||
|
||||
!value.contains('.')
|
||||
? 'Enter a valid email'.tr
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
obscureText: true,
|
||||
controller: controller.passwordController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password'.tr,
|
||||
hintText: 'Your password'.tr,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) => value == null || value.isEmpty
|
||||
? 'Enter your password'.tr
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GetBuilder<LoginController>(
|
||||
builder: (controller) => controller.isloading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (controller.formKey.currentState!.validate()) {
|
||||
controller.login();
|
||||
}
|
||||
},
|
||||
child: Text('Submit'.tr),
|
||||
),
|
||||
),
|
||||
padding: padding,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pill-shaped icon badge with gradient background.
|
||||
Widget _iconBadge(IconData icon, bool isDark) {
|
||||
return Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppColor.primaryColor,
|
||||
AppColor.primaryColor.withOpacity(0.7),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primaryColor.withOpacity(0.35),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 36),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section divider line.
|
||||
Widget _divider(bool isDark) => Container(
|
||||
height: 1,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
isDark
|
||||
? Colors.white.withOpacity(0.1)
|
||||
: Colors.black.withOpacity(0.08),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// AGREEMENT PAGE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
Widget _buildAgreementPage(BuildContext context, LoginController controller) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
|
||||
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
_buildBackground(context),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
// ── Header ──────────────────────────────────────────────
|
||||
const SizedBox(height: 20),
|
||||
_iconBadge(Icons.policy_outlined, isDark),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"passenger agreement".tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: textMain,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Subtitle with link
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
style: AppStyle.title.copyWith(
|
||||
height: 1.6,
|
||||
color: textSub,
|
||||
fontSize: 13.5,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
"To become a passenger, you must review and agree to the "
|
||||
.tr,
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Terms of Use'.tr,
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: AppColor.primaryColor,
|
||||
color: AppColor.primaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(Uri.parse(
|
||||
'https://intaleq.xyz/intaleq/privacy_policy.php'));
|
||||
},
|
||||
),
|
||||
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_divider(isDark),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── Policy scroll area ──────────────────────────────────
|
||||
Expanded(
|
||||
child: _glassCard(
|
||||
isDark: isDark,
|
||||
padding: EdgeInsets.zero,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: HtmlWidget(
|
||||
box.read(BoxName.lang).toString() == 'ar'
|
||||
? AppInformation.privacyPolicyArabic
|
||||
: AppInformation.privacyPolicy,
|
||||
textStyle: TextStyle(
|
||||
color: textSub,
|
||||
fontSize: 13,
|
||||
height: 1.7,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
_divider(isDark),
|
||||
|
||||
// ── Checkbox row ────────────────────────────────────────
|
||||
_glassCard(
|
||||
isDark: isDark,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: CheckboxListTile(
|
||||
title: Text(
|
||||
'I Agree'.tr,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: textMain,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
value: controller.isAgreeTerms,
|
||||
onChanged: (value) => controller.changeAgreeTerm(),
|
||||
activeColor: AppColor.primaryColor,
|
||||
checkColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6)),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── CTA Button ──────────────────────────────────────────
|
||||
_buildPrimaryButton(
|
||||
label: 'Continue'.tr,
|
||||
enabled: controller.isAgreeTerms,
|
||||
onPressed: controller.isAgreeTerms
|
||||
? () => controller.saveAgreementTerms()
|
||||
: () {},
|
||||
isDark: isDark,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// EMAIL / PASSWORD FORM
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
Widget buildEmailPasswordForm(
|
||||
BuildContext context, LoginController controller) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
|
||||
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
_buildBackground(context),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Logo / badge
|
||||
Center(child: _iconBadge(Icons.lock_outline_rounded, isDark)),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Welcome Back'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: textMain,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Sign in to continue'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
AppStyle.title.copyWith(color: textSub, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
_glassCard(
|
||||
isDark: isDark,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Email field
|
||||
_buildTextField(
|
||||
controller: controller.emailController,
|
||||
label: 'Email'.tr,
|
||||
hint: 'Your email address'.tr,
|
||||
icon: Icons.email_outlined,
|
||||
isDark: isDark,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) => value == null ||
|
||||
value.isEmpty ||
|
||||
!value.contains('@') ||
|
||||
!value.contains('.')
|
||||
? 'Enter a valid email'.tr
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Password field
|
||||
_buildTextField(
|
||||
controller: controller.passwordController,
|
||||
label: 'Password'.tr,
|
||||
hint: 'Your password'.tr,
|
||||
icon: Icons.lock_outline,
|
||||
isDark: isDark,
|
||||
obscureText: true,
|
||||
validator: (value) => value == null || value.isEmpty
|
||||
? 'Enter your password'.tr
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
|
||||
GetBuilder<LoginController>(
|
||||
builder: (controller) => controller.isloading
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColor.primaryColor,
|
||||
strokeWidth: 2.5,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _buildPrimaryButton(
|
||||
label: 'Submit'.tr,
|
||||
enabled: true,
|
||||
isDark: isDark,
|
||||
onPressed: () {
|
||||
if (controller.formKey.currentState!
|
||||
.validate()) {
|
||||
controller.login();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// LOCATION PERMISSION PAGE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
Widget _buildLocationPermissionDialog(
|
||||
BuildContext context, LoginController controller) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
|
||||
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
_buildBackground(context),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(flex: 2),
|
||||
|
||||
// Animated-look stacked circles around icon
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Outer glow ring
|
||||
Container(
|
||||
width: 140,
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.primaryColor.withOpacity(0.08),
|
||||
border: Border.all(
|
||||
color: AppColor.primaryColor.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Mid ring
|
||||
Container(
|
||||
width: 108,
|
||||
height: 108,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.primaryColor.withOpacity(0.12),
|
||||
border: Border.all(
|
||||
color: AppColor.primaryColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Core badge
|
||||
_iconBadge(Icons.location_on_outlined, isDark),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 36),
|
||||
|
||||
Text(
|
||||
'Enable Location Access'.tr,
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: textMain,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'We need your location to find nearby drivers for pickups and drop-offs.'
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: textSub,
|
||||
fontSize: 14.5,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Feature chips row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_featureChip(
|
||||
Icons.speed_outlined, 'Fast matching'.tr, isDark),
|
||||
const SizedBox(width: 10),
|
||||
_featureChip(Icons.shield_outlined, 'Secure'.tr, isDark),
|
||||
const SizedBox(width: 10),
|
||||
_featureChip(Icons.near_me_outlined, 'Nearby'.tr, isDark),
|
||||
],
|
||||
),
|
||||
|
||||
const Spacer(flex: 3),
|
||||
|
||||
_buildPrimaryButton(
|
||||
label: 'Next'.tr,
|
||||
enabled: true,
|
||||
isDark: isDark,
|
||||
onPressed: () async =>
|
||||
await controller.getLocationPermission(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// SHARED SMALL WIDGETS
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Reusable styled text field.
|
||||
Widget _buildTextField({
|
||||
required TextEditingController controller,
|
||||
required String label,
|
||||
required String hint,
|
||||
required IconData icon,
|
||||
required bool isDark,
|
||||
bool obscureText = false,
|
||||
TextInputType? keyboardType,
|
||||
String? Function(String?)? validator,
|
||||
}) {
|
||||
final fill = isDark ? Colors.white.withOpacity(0.05) : Colors.white;
|
||||
final border =
|
||||
isDark ? Colors.white.withOpacity(0.1) : const Color(0xFFE5E7EB);
|
||||
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
style: TextStyle(
|
||||
color: isDark ? Colors.white : const Color(0xFF1A1A2E),
|
||||
fontSize: 14.5,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
hintText: hint,
|
||||
prefixIcon: Icon(icon,
|
||||
size: 20, color: isDark ? Colors.white38 : const Color(0xFF9CA3AF)),
|
||||
labelStyle: TextStyle(
|
||||
color: isDark ? Colors.white38 : const Color(0xFF9CA3AF),
|
||||
fontSize: 13.5,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: isDark ? Colors.white24 : const Color(0xFFD1D5DB),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: fill,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide(color: border, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide(color: AppColor.primaryColor, width: 1.5),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1.5),
|
||||
),
|
||||
),
|
||||
validator: validator,
|
||||
);
|
||||
}
|
||||
|
||||
/// Full-width gradient primary button.
|
||||
Widget _buildPrimaryButton({
|
||||
required String label,
|
||||
required bool enabled,
|
||||
required VoidCallback onPressed,
|
||||
required bool isDark,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 54,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: enabled
|
||||
? LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
AppColor.primaryColor,
|
||||
AppColor.primaryColor.withOpacity(0.8),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
color: enabled
|
||||
? null
|
||||
: (isDark ? Colors.white12 : const Color(0xFFE5E7EB)),
|
||||
boxShadow: enabled
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColor.primaryColor.withOpacity(0.32),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 6),
|
||||
)
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: enabled
|
||||
? Colors.white
|
||||
: (isDark ? Colors.white38 : const Color(0xFF9CA3AF)),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationPermissionDialog(LoginController controller) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
/// Small chip used on location page.
|
||||
Widget _featureChip(IconData icon, String text, bool isDark) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: isDark
|
||||
? Colors.white.withOpacity(0.06)
|
||||
: Colors.white.withOpacity(0.8),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withOpacity(0.1)
|
||||
: Colors.black.withOpacity(0.07),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.location_on, size: 60, color: AppColor.primaryColor),
|
||||
const SizedBox(height: 20),
|
||||
Icon(icon, size: 14, color: AppColor.primaryColor),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
'Enable Location Access'.tr,
|
||||
style: AppStyle.headTitle2,
|
||||
textAlign: TextAlign.center,
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isDark ? Colors.white70 : const Color(0xFF374151),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'We need your location to find nearby drivers for pickups and drop-offs.'
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () async => await controller.getLocationPermission(),
|
||||
child: Text('Next'.tr),
|
||||
// child: Text('Allow Location Access'.tr),
|
||||
),
|
||||
// TextButton(
|
||||
// onPressed: () => openAppSettings(),
|
||||
// child: Text('Open Settings'.tr),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user