flagged bugs fix
This commit is contained in:
@@ -47,7 +47,11 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig = signingConfigs.getByName("release")
|
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||||
|
signingConfigs.getByName("release")
|
||||||
|
} else {
|
||||||
|
signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536m -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2560m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8
|
||||||
org.gradle.parallel=false
|
org.gradle.parallel=false
|
||||||
org.gradle.workers.max=1
|
org.gradle.workers.max=1
|
||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
||||||
|
|||||||
67
integration_test/otp_send_test.dart
Normal file
67
integration_test/otp_send_test.dart
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Confirms Firebase Phone Auth actually dispatches an OTP SMS.
|
||||||
|
//
|
||||||
|
// This must run on a real device or emulator with Google Play services —
|
||||||
|
// `flutter_test`'s headless VM has no platform channels, so FirebaseAuth
|
||||||
|
// would throw MissingPluginException there. Each run sends one real, billed
|
||||||
|
// SMS (Blaze plan) to the hardcoded number below.
|
||||||
|
//
|
||||||
|
// Run with a device attached:
|
||||||
|
// flutter test integration_test/otp_send_test.dart -d <device-id>
|
||||||
|
// (list device ids with `flutter devices`)
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
testWidgets('Firebase dispatches OTP SMS to a real phone number',
|
||||||
|
(tester) async {
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
|
||||||
|
const phoneNumber = '+916374946729';
|
||||||
|
final completer = Completer<String>();
|
||||||
|
|
||||||
|
await FirebaseAuth.instance.verifyPhoneNumber(
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
|
timeout: const Duration(seconds: 60),
|
||||||
|
verificationCompleted: (PhoneAuthCredential credential) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete('verificationCompleted (device auto-verified)');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verificationFailed: (FirebaseAuthException e) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError('verificationFailed: ${e.code} — ${e.message}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
codeSent: (String verificationId, int? resendToken) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete('codeSent (verificationId: $verificationId)');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
codeAutoRetrievalTimeout: (String verificationId) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(
|
||||||
|
'codeAutoRetrievalTimeout (verificationId: $verificationId) — request was accepted, auto-retrieval just timed out');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await completer.future.timeout(
|
||||||
|
const Duration(seconds: 45),
|
||||||
|
onTimeout: () => throw TimeoutException(
|
||||||
|
'No callback fired within 45s — check phone provider is enabled, '
|
||||||
|
'Blaze billing is active, and SHA-1/SHA-256 fingerprints are '
|
||||||
|
'registered in Firebase console.'),
|
||||||
|
);
|
||||||
|
|
||||||
|
debugPrint('OTP SEND RESULT: $result');
|
||||||
|
expect(result, isNotEmpty);
|
||||||
|
expect(result, isNot(contains('verificationFailed')));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -8,10 +8,6 @@ class ApiConstants {
|
|||||||
static const String resetPin = '$baseUrl/customer/reset-pin';
|
static const String resetPin = '$baseUrl/customer/reset-pin';
|
||||||
static const String logout = '$baseUrl/customer/logout';
|
static const String logout = '$baseUrl/customer/logout';
|
||||||
|
|
||||||
// ---- Email OTP (Replace Firebase) ----
|
|
||||||
static const String sendEmailOtp = '$baseUrl/customer/send-email-otp';
|
|
||||||
static const String verifyEmailOtp = '$baseUrl/customer/verify-email-otp';
|
|
||||||
|
|
||||||
// ---- Profile ----
|
// ---- Profile ----
|
||||||
static const String profile = '$baseUrl/customer/profile';
|
static const String profile = '$baseUrl/customer/profile';
|
||||||
|
|
||||||
|
|||||||
@@ -34,32 +34,12 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
await state.initiateLogin(_phone.text);
|
await state.initiateLogin(_phone.text);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
state.isLoginFlow = true;
|
// Backend already gates login behind the PIN check, so no separate
|
||||||
|
// OTP possession-of-phone step is needed for returning users.
|
||||||
// Bypass OTP for Easter Egg
|
setState(() => _loading = false);
|
||||||
if (_phone.text.replaceAll(' ', '') == '9876543210') {
|
Navigator.pushNamed(context, Routes.enterPin);
|
||||||
setState(() => _loading = false);
|
|
||||||
Navigator.pushNamed(context, Routes.enterPin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await state.sendOtp(
|
|
||||||
_phone.text,
|
|
||||||
onSuccess: () {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() => _loading = false);
|
|
||||||
Navigator.pushNamed(context, Routes.otp);
|
|
||||||
},
|
|
||||||
onError: (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
state.isLoginFlow = false;
|
|
||||||
setState(() => _loading = false);
|
|
||||||
SnackbarUtils.showError(context, err);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
state.isLoginFlow = false;
|
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
SnackbarUtils.showError(context, e.toString());
|
SnackbarUtils.showError(context, e.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:sms_autofill/sms_autofill.dart';
|
||||||
|
|
||||||
import 'package:doormile/main.dart';
|
import 'package:doormile/main.dart';
|
||||||
import 'package:doormile/shared/state/app_state.dart';
|
import 'package:doormile/shared/state/app_state.dart';
|
||||||
@@ -18,7 +21,7 @@ class OtpScreen extends StatefulWidget {
|
|||||||
State<OtpScreen> createState() => _OtpScreenState();
|
State<OtpScreen> createState() => _OtpScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OtpScreenState extends State<OtpScreen> {
|
class _OtpScreenState extends State<OtpScreen> with CodeAutoFill {
|
||||||
final List<TextEditingController> _controllers =
|
final List<TextEditingController> _controllers =
|
||||||
List.generate(6, (_) => TextEditingController());
|
List.generate(6, (_) => TextEditingController());
|
||||||
final List<FocusNode> _nodes = List.generate(6, (_) => FocusNode());
|
final List<FocusNode> _nodes = List.generate(6, (_) => FocusNode());
|
||||||
@@ -30,9 +33,40 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_startTimer();
|
_startTimer();
|
||||||
|
listenForCode();
|
||||||
|
_prefillDebugBypass();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _nodes[0].requestFocus());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _nodes[0].requestFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Debug-only tester shortcut, see [AppState.verifyOtp]. Compiled out of
|
||||||
|
/// release builds via kDebugMode.
|
||||||
|
Future<void> _prefillDebugBypass() async {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final provider = prefs.getInt('smsDefaultProvider') ?? 0;
|
||||||
|
final passKey = prefs.getInt('smsPassKey');
|
||||||
|
if (provider != 1 || passKey == null) return;
|
||||||
|
if (!mounted || _controllers.any((c) => c.text.isNotEmpty)) return;
|
||||||
|
|
||||||
|
final passKeyStr = passKey.toString().padLeft(6, '0');
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
_controllers[i].text = passKeyStr[i];
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void codeUpdated() {
|
||||||
|
if (!mounted) return;
|
||||||
|
final digits = (code ?? '').replaceAll(RegExp(r'[^0-9]'), '');
|
||||||
|
if (digits.length < 6) return;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
_controllers[i].text = digits[i];
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
_verify();
|
||||||
|
}
|
||||||
|
|
||||||
void _startTimer() {
|
void _startTimer() {
|
||||||
_seconds = 28;
|
_seconds = 28;
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
@@ -59,8 +93,6 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
final state = context.read<AppState>();
|
final state = context.read<AppState>();
|
||||||
if (state.isResettingPin) {
|
if (state.isResettingPin) {
|
||||||
Navigator.pushReplacementNamed(context, Routes.resetPin);
|
Navigator.pushReplacementNamed(context, Routes.resetPin);
|
||||||
} else if (state.isLoginFlow) {
|
|
||||||
Navigator.pushReplacementNamed(context, Routes.enterPin);
|
|
||||||
} else {
|
} else {
|
||||||
Navigator.pushReplacementNamed(context, Routes.setPin);
|
Navigator.pushReplacementNamed(context, Routes.setPin);
|
||||||
}
|
}
|
||||||
@@ -115,6 +147,7 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
cancel();
|
||||||
for (final c in _controllers) {
|
for (final c in _controllers) {
|
||||||
c.dispose();
|
c.dispose();
|
||||||
}
|
}
|
||||||
@@ -137,8 +170,10 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: AppCard(
|
child: ConstrainedBox(
|
||||||
padding: const EdgeInsets.all(28),
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: AppCard(
|
||||||
|
padding: const EdgeInsets.all(28),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -147,10 +182,10 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
height: 88,
|
height: 88,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: AppColors.primaryFixed, shape: BoxShape.circle),
|
color: AppColors.primaryFixed, shape: BoxShape.circle),
|
||||||
child: const Icon(Icons.mark_email_read_rounded, size: 40, color: AppColors.primary),
|
child: const Icon(Icons.sms_rounded, size: 40, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text('Verify your email',
|
Text('Verify your number',
|
||||||
textAlign: TextAlign.center, style: AppTheme.headlineLg),
|
textAlign: TextAlign.center, style: AppTheme.headlineLg),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
@@ -159,7 +194,7 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const TextSpan(text: 'We sent a 6-digit code to '),
|
const TextSpan(text: 'We sent a 6-digit code to '),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: context.read<AppState>().pendingEmail ?? 'your email',
|
text: '+91 $phone',
|
||||||
style: AppTheme.labelMd,
|
style: AppTheme.labelMd,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -168,12 +203,19 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: List.generate(
|
||||||
children: List.generate(6, (i) => _OtpBox(
|
6,
|
||||||
controller: _controllers[i],
|
(i) => Expanded(
|
||||||
node: _nodes[i],
|
child: Padding(
|
||||||
onChanged: (v) => _onChanged(i, v),
|
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||||
)),
|
child: _OtpBox(
|
||||||
|
controller: _controllers[i],
|
||||||
|
node: _nodes[i],
|
||||||
|
onChanged: (v) => _onChanged(i, v),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
if (_seconds > 0)
|
if (_seconds > 0)
|
||||||
@@ -198,6 +240,7 @@ class _OtpScreenState extends State<OtpScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -213,7 +256,6 @@ class _OtpBox extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final filled = controller.text.isNotEmpty;
|
final filled = controller.text.isNotEmpty;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 46,
|
|
||||||
height: 56,
|
height: 56,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ class _BookingPickupScreenState extends State<BookingPickupScreen> {
|
|||||||
if (match != null) draft.pickupPincode = match.group(0)!;
|
if (match != null) draft.pickupPincode = match.group(0)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (draft.pickupPincode.isEmpty) {
|
||||||
|
_showValidationError('Missing Pickup Pincode',
|
||||||
|
'We couldn\'t detect a pincode for your pickup address. Please use "Use GPS current location", pick a saved address, or include the 6-digit pincode in the address.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
await appState.fetchPriceEstimate();
|
await appState.fetchPriceEstimate();
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:doormile/main.dart';
|
import 'package:doormile/main.dart';
|
||||||
import 'package:doormile/shared/state/app_state.dart';
|
import 'package:doormile/shared/state/app_state.dart';
|
||||||
|
import 'package:doormile/core/utils/snackbar_utils.dart';
|
||||||
import 'package:doormile/core/theme/app_colors.dart';
|
import 'package:doormile/core/theme/app_colors.dart';
|
||||||
import 'package:doormile/core/theme/app_theme.dart';
|
import 'package:doormile/core/theme/app_theme.dart';
|
||||||
import 'package:doormile/core/widgets/common.dart';
|
import 'package:doormile/core/widgets/common.dart';
|
||||||
@@ -51,13 +52,20 @@ class _PaymentSheetState extends State<_PaymentSheet> {
|
|||||||
state.draft.paymentMethod = _methods[_selected].name;
|
state.draft.paymentMethod = _methods[_selected].name;
|
||||||
await Future.delayed(const Duration(milliseconds: 1400));
|
await Future.delayed(const Duration(milliseconds: 1400));
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
state.placeOrder();
|
try {
|
||||||
Navigator.pop(context); // close sheet
|
await state.placeOrder();
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
if (!mounted) return;
|
||||||
context,
|
Navigator.pop(context); // close sheet
|
||||||
Routes.bookingSuccess,
|
Navigator.pushNamedAndRemoveUntil(
|
||||||
(r) => r.settings.name == Routes.home,
|
context,
|
||||||
);
|
Routes.bookingSuccess,
|
||||||
|
(r) => r.settings.name == Routes.home,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _processing = false);
|
||||||
|
SnackbarUtils.showError(context, e.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ class AppState extends ChangeNotifier {
|
|||||||
String? pendingEmail;
|
String? pendingEmail;
|
||||||
String? authToken;
|
String? authToken;
|
||||||
bool isResettingPin = false;
|
bool isResettingPin = false;
|
||||||
bool isLoginFlow = false;
|
|
||||||
String? _deviceToken;
|
String? _deviceToken;
|
||||||
|
|
||||||
AppState() {
|
AppState() {
|
||||||
@@ -127,52 +126,103 @@ class AppState extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a 6-digit OTP to [phoneNumber] via Firebase Phone Auth.
|
||||||
Future<void> sendOtp(String phoneNumber, {String? firstName, String? email, bool resetPin = false, required Function onSuccess, required Function(String) onError}) async {
|
Future<void> sendOtp(String phoneNumber, {String? firstName, String? email, bool resetPin = false, required Function onSuccess, required Function(String) onError}) async {
|
||||||
final fullPhone = '+91${phoneNumber.replaceAll(' ', '')}';
|
|
||||||
setPhone(phoneNumber);
|
setPhone(phoneNumber);
|
||||||
pendingFirstName = firstName;
|
pendingFirstName = firstName ?? pendingFirstName;
|
||||||
pendingEmail = email;
|
pendingEmail = email ?? pendingEmail;
|
||||||
isResettingPin = resetPin;
|
isResettingPin = resetPin;
|
||||||
|
|
||||||
if (email == null || email.isEmpty) {
|
final rawPhone = phoneNumber.replaceAll(' ', '');
|
||||||
onError('Email address is required for verification.');
|
if (rawPhone.length != 10) {
|
||||||
|
onError('A valid mobile number is required for verification.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final fullPhone = '+91$rawPhone';
|
||||||
|
|
||||||
|
if (kIsWeb) {
|
||||||
|
debugPrint('[OTP Service] Web build: bypassing Firebase OTP (mocking success)');
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
verificationId = 'mock_verification_id_$fullPhone';
|
||||||
|
onSuccess();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final res = await http.post(
|
debugPrint('[OTP Service] Initiating Firebase phone verification for $fullPhone');
|
||||||
Uri.parse(ApiConstants.sendEmailOtp),
|
await FirebaseAuth.instance.verifyPhoneNumber(
|
||||||
headers: {'Content-Type': 'application/json'},
|
phoneNumber: fullPhone,
|
||||||
body: jsonEncode({"email": email, "phone": fullPhone}),
|
verificationCompleted: (PhoneAuthCredential credential) async {
|
||||||
|
debugPrint('[OTP Service] Verification completed automatically');
|
||||||
|
try {
|
||||||
|
await FirebaseAuth.instance.signInWithCredential(credential);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[OTP Service] Error signing in: $e');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verificationFailed: (FirebaseAuthException e) {
|
||||||
|
debugPrint('[OTP Service] Verification failed: ${e.code} - ${e.message}');
|
||||||
|
onError(e.message ?? 'Failed to send verification code.');
|
||||||
|
},
|
||||||
|
codeSent: (String vId, int? resendToken) {
|
||||||
|
debugPrint('[OTP Service] Code sent. Verification ID: $vId');
|
||||||
|
verificationId = vId;
|
||||||
|
onSuccess();
|
||||||
|
},
|
||||||
|
codeAutoRetrievalTimeout: (String vId) {
|
||||||
|
debugPrint('[OTP Service] Auto retrieval timeout. Verification ID: $vId');
|
||||||
|
verificationId = vId;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
||||||
onSuccess();
|
|
||||||
} else {
|
|
||||||
final body = jsonDecode(res.body);
|
|
||||||
onError(body['message'] ?? 'Failed to send verification code.');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onError('Error: $e');
|
debugPrint('[OTP Service] Exception during OTP request: $e');
|
||||||
|
onError('Error sending verification code: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> verifyOtp(String smsCode, {required Function onSuccess, required Function(String) onError}) async {
|
/// Debug-only tester shortcut: if `smsDefaultProvider`/`smsPassKey` are set
|
||||||
try {
|
/// in SharedPreferences, that fixed passkey is accepted instead of the real
|
||||||
final res = await http.post(
|
/// OTP. `kDebugMode` means this branch is compiled out of release builds
|
||||||
Uri.parse(ApiConstants.verifyEmailOtp),
|
/// entirely, so it can never ship as a bypass.
|
||||||
headers: {'Content-Type': 'application/json'},
|
Future<bool> _checkDebugBypass(String smsCode) async {
|
||||||
body: jsonEncode({"email": pendingEmail, "otp": smsCode}),
|
if (!kDebugMode) return false;
|
||||||
);
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final provider = prefs.getInt('smsDefaultProvider') ?? 0;
|
||||||
|
final passKey = prefs.getInt('smsPassKey');
|
||||||
|
return provider == 1 && passKey != null && smsCode == passKey.toString().padLeft(6, '0');
|
||||||
|
}
|
||||||
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
Future<void> verifyOtp(String smsCode, {required Function onSuccess, required Function(String) onError}) async {
|
||||||
|
if (await _checkDebugBypass(smsCode)) {
|
||||||
|
onSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verificationId == null) {
|
||||||
|
onError('Verification ID is missing. Please resend OTP.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kIsWeb) {
|
||||||
|
debugPrint('[OTP Service] Web build: bypassing Firebase OTP verification (mocking success)');
|
||||||
|
if (smsCode.length == 6) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} else {
|
} else {
|
||||||
final body = jsonDecode(res.body);
|
onError('Invalid mock OTP code (must be 6 digits).');
|
||||||
onError(body['message'] ?? 'Invalid verification code.');
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final credential = PhoneAuthProvider.credential(
|
||||||
|
verificationId: verificationId!,
|
||||||
|
smsCode: smsCode,
|
||||||
|
);
|
||||||
|
await FirebaseAuth.instance.signInWithCredential(credential);
|
||||||
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onError('Error: $e');
|
onError('Invalid verification code.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +367,6 @@ class AppState extends ChangeNotifier {
|
|||||||
|
|
||||||
void login() {
|
void login() {
|
||||||
isLoggedIn = true;
|
isLoggedIn = true;
|
||||||
isLoginFlow = false;
|
|
||||||
orders.clear();
|
orders.clear();
|
||||||
savedAddresses.clear();
|
savedAddresses.clear();
|
||||||
_saveSession();
|
_saveSession();
|
||||||
@@ -333,7 +382,6 @@ class AppState extends ChangeNotifier {
|
|||||||
verificationId = null;
|
verificationId = null;
|
||||||
pendingFirstName = null;
|
pendingFirstName = null;
|
||||||
pendingEmail = null;
|
pendingEmail = null;
|
||||||
isLoginFlow = false;
|
|
||||||
orders.clear();
|
orders.clear();
|
||||||
savedAddresses.clear();
|
savedAddresses.clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -517,6 +565,8 @@ class AppState extends ChangeNotifier {
|
|||||||
"deliverypincode": draft.dropPincode,
|
"deliverypincode": draft.dropPincode,
|
||||||
"deliveryaddress": draft.dropAddress,
|
"deliveryaddress": draft.dropAddress,
|
||||||
"serviceoption": draft.service,
|
"serviceoption": draft.service,
|
||||||
|
"contactname": draft.contactName,
|
||||||
|
"contactmobile": draft.contactMobile,
|
||||||
"parcels": [
|
"parcels": [
|
||||||
{
|
{
|
||||||
"itemcategory": backendCategory,
|
"itemcategory": backendCategory,
|
||||||
|
|||||||
75
pubspec.lock
75
pubspec.lock
@@ -53,10 +53,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -238,6 +238,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_driver:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -280,6 +285,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fuchsia_remote_debug_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
geoclue:
|
geoclue:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -464,6 +474,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
|
integration_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -508,26 +523,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.18.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -640,6 +655,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.2"
|
version: "7.0.2"
|
||||||
|
pin_input_text_field:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pin_input_text_field
|
||||||
|
sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -664,6 +687,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
version: "6.5.0"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.5"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -741,6 +772,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
sms_autofill:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sms_autofill
|
||||||
|
sha256: "5c6c5569a310fce12117eaefab5be6e29cc676b635e43be8b70b1946ec21ae91"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -781,6 +820,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
sync_http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sync_http
|
||||||
|
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -793,10 +840,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.11"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -925,6 +972,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
webdriver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webdriver
|
||||||
|
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: doormile
|
name: doormile
|
||||||
description: "Doormile — EV-first parcel delivery app."
|
description: "Doormile — EV-first parcel delivery app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.0.0+1
|
version: 1.0.3+5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
@@ -22,10 +22,13 @@ dependencies:
|
|||||||
google_maps_flutter: ^2.9.0
|
google_maps_flutter: ^2.9.0
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
firebase_messaging: 16.3.0
|
firebase_messaging: 16.3.0
|
||||||
|
sms_autofill: ^2.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
integration_test:
|
||||||
|
sdk: flutter
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^4.0.0
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: ^0.14.4
|
||||||
flutter_native_splash: ^2.4.7
|
flutter_native_splash: ^2.4.7
|
||||||
|
|||||||
Reference in New Issue
Block a user