first commit
This commit is contained in:
104
receiver_app_new/lib/services/api_service.dart
Normal file
104
receiver_app_new/lib/services/api_service.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ApiService {
|
||||
static const String _baseUrl = 'https://otp.intaleqapp.com/api/';
|
||||
static const String _appKey =
|
||||
'f3a9e7c1b8d5f2a4c6e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1';
|
||||
|
||||
/// Request an OTP to be sent to the given phone number
|
||||
/// Returns a map with {success, expires_in, method}
|
||||
static Future<Map<String, dynamic>> requestOtp(
|
||||
String phone,
|
||||
String deviceType,
|
||||
) async {
|
||||
try {
|
||||
final response = await http
|
||||
.post(
|
||||
Uri.parse('${_baseUrl}request-otp.php'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'phone': phone,
|
||||
'device_type': deviceType,
|
||||
'app_key': _appKey,
|
||||
}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return {
|
||||
'success': data['success'] ?? false,
|
||||
'expires_in': data['expires_in'] ?? 120,
|
||||
'method': data['method'] ?? 'flash_call',
|
||||
};
|
||||
} else {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return {
|
||||
'success': false,
|
||||
'expires_in': 0,
|
||||
'method': 'flash_call',
|
||||
'message': data['message'] ?? 'Server error: ${response.statusCode}',
|
||||
};
|
||||
}
|
||||
} on http.ClientException catch (e) {
|
||||
return {
|
||||
'success': false,
|
||||
'expires_in': 0,
|
||||
'method': 'flash_call',
|
||||
'message': 'Network error: ${e.message}',
|
||||
};
|
||||
} on FormatException {
|
||||
return {
|
||||
'success': false,
|
||||
'expires_in': 0,
|
||||
'method': 'flash_call',
|
||||
'message': 'Invalid server response',
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
'success': false,
|
||||
'expires_in': 0,
|
||||
'method': 'flash_call',
|
||||
'message': 'Connection error: $e',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify an OTP code for the given phone number
|
||||
/// Returns a map with {success, message}
|
||||
static Future<Map<String, dynamic>> verifyOtp(
|
||||
String phone,
|
||||
String otp,
|
||||
) async {
|
||||
try {
|
||||
final response = await http
|
||||
.post(
|
||||
Uri.parse('${_baseUrl}verify-otp.php'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'phone': phone, 'otp': otp, 'app_key': _appKey}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return {
|
||||
'success': data['success'] ?? false,
|
||||
'message': data['message'] ?? 'Verification completed',
|
||||
};
|
||||
} else {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return {
|
||||
'success': false,
|
||||
'message': data['message'] ?? 'Server error: ${response.statusCode}',
|
||||
};
|
||||
}
|
||||
} on http.ClientException catch (e) {
|
||||
return {'success': false, 'message': 'Network error: ${e.message}'};
|
||||
} on FormatException {
|
||||
return {'success': false, 'message': 'Invalid server response'};
|
||||
} catch (e) {
|
||||
return {'success': false, 'message': 'Connection error: $e'};
|
||||
}
|
||||
}
|
||||
}
|
||||
100
receiver_app_new/lib/services/otp_controller.dart
Normal file
100
receiver_app_new/lib/services/otp_controller.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:call_log/call_log.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class OtpController {
|
||||
DateTime? _startTime;
|
||||
Timer? _pollTimer;
|
||||
String? _detectedOtp;
|
||||
bool _isPolling = false;
|
||||
|
||||
// Callbacks
|
||||
Function(String status)? onStatusChanged;
|
||||
Function(String otp)? onOtpDetected;
|
||||
Function()? onTimeout;
|
||||
|
||||
/// Start polling call log for missed calls
|
||||
Future<bool> startWaiting(String phone) async {
|
||||
if (Platform.isIOS) {
|
||||
// iOS: cannot read call log, skip to manual entry
|
||||
onStatusChanged?.call('manual_ios');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request phone permissions (usually covers call log on many Android versions)
|
||||
var status = await Permission.phone.request();
|
||||
|
||||
if (!status.isGranted) {
|
||||
onStatusChanged?.call('permission_denied');
|
||||
return false;
|
||||
}
|
||||
|
||||
_startTime = DateTime.now();
|
||||
_isPolling = true;
|
||||
onStatusChanged?.call('waiting');
|
||||
|
||||
// Poll every 1.5 seconds
|
||||
_pollTimer = Timer.periodic(const Duration(milliseconds: 1500), (_) {
|
||||
_checkCallLog(phone);
|
||||
});
|
||||
|
||||
// Timeout after 120 seconds
|
||||
Future.delayed(const Duration(seconds: 120), () {
|
||||
if (_isPolling && _detectedOtp == null) {
|
||||
stopWaiting();
|
||||
onTimeout?.call();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Check call log for missed calls since _startTime
|
||||
Future<void> _checkCallLog(String phone) async {
|
||||
try {
|
||||
final Iterable<CallLogEntry> entries = await CallLog.query(
|
||||
dateFrom: _startTime!.millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
for (var entry in entries) {
|
||||
if (entry.callType == CallType.missed && entry.number != null) {
|
||||
final otp = extractOTP(entry.number!);
|
||||
if (otp.length == 4) {
|
||||
_detectedOtp = otp;
|
||||
stopWaiting();
|
||||
onStatusChanged?.call('call_detected');
|
||||
onOtpDetected?.call(otp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently continue polling
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract OTP from caller number (last 4 digits)
|
||||
String extractOTP(String callerNumber) {
|
||||
final digitsOnly = callerNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
if (digitsOnly.length >= 4) {
|
||||
return digitsOnly.substring(digitsOnly.length - 4);
|
||||
}
|
||||
return digitsOnly;
|
||||
}
|
||||
|
||||
/// Stop polling
|
||||
void stopWaiting() {
|
||||
_isPolling = false;
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = null;
|
||||
}
|
||||
|
||||
/// Dispose resources
|
||||
void dispose() {
|
||||
stopWaiting();
|
||||
onStatusChanged = null;
|
||||
onOtpDetected = null;
|
||||
onTimeout = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user