updated the sheet
This commit is contained in:
@@ -3,7 +3,8 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:miler/data/api_config.dart';
|
||||
import 'package:miler/data/miler_api.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'dart:math' as math;
|
||||
@@ -20,7 +21,6 @@ class BackgroundDeliveryLog {
|
||||
|
||||
static final CreateDeliveryLogProvider _logProvider =
|
||||
CreateDeliveryLogProvider();
|
||||
static final http.Client _httpClient = http.Client();
|
||||
static final FlutterTts _tts = FlutterTts();
|
||||
static final AudioPlayer _proximityPlayer = AudioPlayer();
|
||||
|
||||
@@ -60,38 +60,37 @@ class BackgroundDeliveryLog {
|
||||
final now = DateTime.now();
|
||||
final today = '${now.year}-${_pad(now.month)}-${_pad(now.day)}';
|
||||
|
||||
// Build API URL using v1 endpoint (getdeliveries)
|
||||
final bool isLive = ApiConstants.mainRoute == 'live';
|
||||
final baseUrl = isLive
|
||||
? ApiConstants.currentDeliveryLive
|
||||
: ApiConstants.currentDeliveryDev;
|
||||
|
||||
final uri = Uri.parse(baseUrl).replace(
|
||||
queryParameters: {
|
||||
'userid': userId.toString(),
|
||||
'fromdate': today,
|
||||
'todate': today,
|
||||
't': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint('[ACTIVE_DELIVERY_LOG][BG] Fetching deliveries: $uri');
|
||||
|
||||
// Fetch deliveries from API using v1 endpoint
|
||||
final deliveries = await _fetchDeliveriesFromApi(uri);
|
||||
// ── The delivery twin of the pickup fetch, with the same two faults ──
|
||||
//
|
||||
// It built a `deliveries/getdeliveries` URL by hand and fetched it with a
|
||||
// bare `_httpClient.get` — **no bearer token** — and put `userid` in the
|
||||
// query string. The URL itself already resolved to a v1 route through the
|
||||
// [DeliveryApi] facade, so the call could only ever be answered 401.
|
||||
//
|
||||
// And the filter looked for `orderstatus == 'active'`, which is not a
|
||||
// value this app's vocabulary contains: [ApiConfig.legacyStatusFromNew]
|
||||
// maps the v1 statuses to `assigned` / `accepted` / `arrived` / `picked`,
|
||||
// never to `active`. So it returned nothing on every tick and fell
|
||||
// through to the shift-end check, which is what the device log shows.
|
||||
//
|
||||
// `GET /miler/bookings` through [MilerApi], asked with the session, about
|
||||
// the rider the session names.
|
||||
final res = await MilerApi.bookings(date: today);
|
||||
final deliveries = res.list;
|
||||
|
||||
debugPrint(
|
||||
'[ACTIVE_DELIVERY_LOG][BG] API returned ${deliveries.length} items',
|
||||
'[ACTIVE_DELIVERY_LOG][BG] /miler/bookings -> ${res.status}, '
|
||||
'${deliveries.length} items',
|
||||
);
|
||||
|
||||
// Filter for active orders only
|
||||
const inProgress = {'accepted', 'arrived', 'picked'};
|
||||
final activeOrders = deliveries.whereType<Map<String, dynamic>>().where((
|
||||
order,
|
||||
) {
|
||||
final status = (order['orderstatus']?.toString().toLowerCase() ?? '')
|
||||
.trim();
|
||||
final isActive = status == 'active';
|
||||
return isActive;
|
||||
final status = ApiConfig.legacyStatusFromNew(
|
||||
(order['status'] ?? order['orderstatus'])?.toString(),
|
||||
);
|
||||
return inProgress.contains(status);
|
||||
}).toList();
|
||||
|
||||
if (activeOrders.isEmpty) {
|
||||
@@ -202,11 +201,8 @@ class BackgroundDeliveryLog {
|
||||
'logstatus': await _resolveLogStatus(orderId, coords),
|
||||
};
|
||||
|
||||
// Determine API endpoint
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createDeliveryLogLive
|
||||
: ApiConstants.createDeliveryLogDev;
|
||||
|
||||
// v1: `/miler/consignments/logs`, via the DeliveryApi facade.
|
||||
final url = ApiConstants.createDeliveryLogLive;
|
||||
// Post the log
|
||||
debugPrint('[ACTIVE_DELIVERY_LOG][BG] Posting log for $orderId');
|
||||
|
||||
@@ -434,33 +430,6 @@ class BackgroundDeliveryLog {
|
||||
}
|
||||
|
||||
/// Fetch deliveries from API using v1 endpoint
|
||||
static Future<List<dynamic>> _fetchDeliveriesFromApi(Uri uri) async {
|
||||
try {
|
||||
final res = await _httpClient
|
||||
.get(uri)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
final decoded = json.decode(res.body);
|
||||
|
||||
final data = decoded is Map<String, dynamic>
|
||||
? (decoded['details'] ?? decoded['data'] ?? decoded)
|
||||
: decoded;
|
||||
|
||||
if (data is List) {
|
||||
return data;
|
||||
}
|
||||
if (data is Map && data['items'] is List) {
|
||||
return data['items'] as List;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to pad numbers with leading zero
|
||||
static String _pad(int n) => n.toString().padLeft(2, '0');
|
||||
@@ -540,10 +509,6 @@ class BackgroundDeliveryLog {
|
||||
final List<String> remaining = [];
|
||||
bool anySuccess = false;
|
||||
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createDeliveryLogLive
|
||||
: ApiConstants.createDeliveryLogDev;
|
||||
|
||||
for (final itemStr in queue) {
|
||||
try {
|
||||
final Map<String, dynamic> item = jsonDecode(itemStr);
|
||||
@@ -554,6 +519,8 @@ class BackgroundDeliveryLog {
|
||||
|
||||
if (payload.isEmpty) continue;
|
||||
|
||||
// v1: `/miler/consignments/logs`, via the DeliveryApi facade.
|
||||
final url = ApiConstants.createDeliveryLogLive;
|
||||
debugPrint(
|
||||
'[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Retrying for orderId: $orderId',
|
||||
);
|
||||
@@ -770,14 +737,18 @@ class BackgroundDeliveryLog {
|
||||
'[AUTO_SHIFT_END] 🚀 Initiating auto-break and offline sequence...',
|
||||
);
|
||||
|
||||
// 1. Get Required IDs
|
||||
// ── The four ids and the fix are no longer this function's business ──
|
||||
//
|
||||
// `userid`, `partnerid`, `shiftid`, `logid` and a position were assembled
|
||||
// here to fill a legacy break-log body. On v1 the break and the duty end
|
||||
// are both keyed on the **token**: `POST /miler/breaks/start` opens a
|
||||
// break for whoever the session names, and `PUT /miler/duty/end` closes
|
||||
// whichever duty log is open for them. Sending an id would not make it
|
||||
// more correct; it is what let the old call be made unauthenticated.
|
||||
//
|
||||
// `userid` is still read, as the one guard worth keeping: no signed-in
|
||||
// rider, nothing to end.
|
||||
final int userid = prefs.getInt('userid') ?? prefs.getInt('userId') ?? 0;
|
||||
final int partnerid =
|
||||
prefs.getInt('partnerid') ?? prefs.getInt('partnerId') ?? 0;
|
||||
final int shiftid =
|
||||
prefs.getInt('shiftid') ?? prefs.getInt('shiftId') ?? 0;
|
||||
final int logid = prefs.getInt('logid') ?? prefs.getInt('logId') ?? 0;
|
||||
|
||||
if (userid == 0) {
|
||||
debugPrint(
|
||||
'[AUTO_SHIFT_END] ❌ Missing userid, cannot create break log',
|
||||
@@ -785,61 +756,28 @@ class BackgroundDeliveryLog {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Get Location
|
||||
final Map<String, String>? coords = await _getCoordinatesWithFallback();
|
||||
final String lat = coords?['lat'] ?? '0';
|
||||
final String lng = coords?['lng'] ?? '0';
|
||||
|
||||
// 3. Prepare Break Log Payload
|
||||
final now = DateTime.now();
|
||||
final int localBreakId =
|
||||
(DateTime.now().millisecondsSinceEpoch % 900) + 100; // Random-ish ID
|
||||
final String breakdate =
|
||||
'${now.year}-${_pad(now.month)}-${_pad(now.day)} ${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';
|
||||
final String breakstart =
|
||||
'${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';
|
||||
|
||||
final payload = <String, dynamic>{
|
||||
"breakid": localBreakId,
|
||||
"logid": logid,
|
||||
"breakdate": breakdate,
|
||||
"userid": userid,
|
||||
"partnerid": partnerid,
|
||||
"shiftid": shiftid,
|
||||
"breakstart": breakstart,
|
||||
"breakend": "",
|
||||
"breakhours": 0.0,
|
||||
"latitude": lat,
|
||||
"longitude": lng,
|
||||
};
|
||||
|
||||
// 4. Call API to Create Break
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createBreakRiderLogLive
|
||||
: ApiConstants.createBreakRiderLogDev;
|
||||
|
||||
debugPrint('[AUTO_SHIFT_END] Creating break log: $url');
|
||||
|
||||
// We use a separate provider instance or http call if needed,
|
||||
// but _logProvider is for delivery logs. We need a generic post or use http directly.
|
||||
// Since we don't have BreakRiderLogProvider here, we'll use http directly for simplicity and isolation.
|
||||
// 4. Close the shift with a break, on v1.
|
||||
//
|
||||
// This hand-posted the retired backend's `partners/createbreaklog`
|
||||
// with a bare `_httpClient.post` and no bearer
|
||||
// token — an unauthenticated write keyed on a `userid` in the
|
||||
// body. `POST /miler/breaks/start` is the v1 endpoint, and
|
||||
// [MilerApi] attaches the session.
|
||||
debugPrint('[AUTO_SHIFT_END] Creating break log (v1)');
|
||||
|
||||
try {
|
||||
final response = await _httpClient
|
||||
.post(
|
||||
Uri.parse(url),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(payload),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
final res = await MilerApi.startBreak('Shift_End');
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
if (res.ok) {
|
||||
debugPrint('[AUTO_SHIFT_END] ✅ Break log created successfully');
|
||||
|
||||
// Parse response to get server break ID if needed, but mainly we just need to go offline
|
||||
final body = jsonDecode(response.body);
|
||||
final det = (body['details'] is Map) ? body['details'] : body;
|
||||
final serverBreakId = det['breakid'];
|
||||
final serverBreakId = res.map['breaklogid'] ?? res.map['breakid'];
|
||||
|
||||
if (serverBreakId != null) {
|
||||
await prefs.setInt(
|
||||
@@ -851,7 +789,7 @@ class BackgroundDeliveryLog {
|
||||
await prefs.setInt('break_start_epoch', now.millisecondsSinceEpoch);
|
||||
} else {
|
||||
debugPrint(
|
||||
'[AUTO_SHIFT_END] ⚠️ Failed to create break log: ${response.statusCode}',
|
||||
'[AUTO_SHIFT_END] ⚠️ Failed to create break log: ${res.status}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -863,27 +801,15 @@ class BackgroundDeliveryLog {
|
||||
await prefs.setInt('onduty', 0);
|
||||
await prefs.setBool('online', false);
|
||||
|
||||
// 6. Update Rider Log (Set Duty = 0)
|
||||
// We should also update the main rider log to say onduty=0
|
||||
final updateUrl = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.updateRiderLogLive
|
||||
: ApiConstants.updateRiderLogDev;
|
||||
|
||||
final updatePayload = {
|
||||
"userid": userid,
|
||||
"onduty": 0,
|
||||
"latitude": lat,
|
||||
"longitude": lng,
|
||||
};
|
||||
// 6. End duty, on v1.
|
||||
//
|
||||
// Was an unauthenticated POST of `{userid, onduty: 0}` to the
|
||||
// legacy `updateriderlog`. `PUT /miler/duty/end` closes whichever
|
||||
// duty log is open for the token's rider, which is the same fact
|
||||
// without the id or the open door.
|
||||
|
||||
try {
|
||||
await _httpClient
|
||||
.post(
|
||||
Uri.parse(updateUrl),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(updatePayload),
|
||||
)
|
||||
.timeout(const Duration(seconds: 5));
|
||||
await MilerApi.endDuty();
|
||||
debugPrint('[AUTO_SHIFT_END] ✅ Rider status updated to Offline');
|
||||
} catch (_) {}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:miler/xpress/views/helpers/constants/apiconstants.dart';
|
||||
import 'package:miler/xpress/providers/Riderlog/riderlog_provider.dart';
|
||||
import 'package:miler/xpress/background/backgroundservice.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
@@ -354,12 +353,9 @@ class _BackgroundRiderLog {
|
||||
payload['lastname'] = lastName.trim();
|
||||
}
|
||||
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createRiderLogLive
|
||||
: ApiConstants.createRiderLogDev;
|
||||
|
||||
final provider = CreateRiderLogProvider();
|
||||
final resp = await provider.createRiderLog(base, payload);
|
||||
final resp = await provider.createRiderLog(payload);
|
||||
|
||||
if (resp == null || resp.isEmpty) return;
|
||||
final det = (resp['details'] is Map<String, dynamic>)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:miler/xpress/views/helpers/constants/apiconstants.dart';
|
||||
import 'package:miler/xpress/Models/riders/riders_models.dart';
|
||||
import 'package:miler/xpress/providers/Riderlog/riderlog_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -15,6 +14,7 @@ import 'package:miler/xpress/controllers/logcontroller.dart';
|
||||
import 'package:miler/xpress/background/foreground_service.dart' as fg;
|
||||
import 'package:miler/xpress/utils/kalman_filter.dart';
|
||||
import 'package:miler/xpress/utils/mqtt_service.dart';
|
||||
import 'package:miler/data/heartbeat.dart';
|
||||
import 'package:miler/xpress/views/helpers/constants/mqtt_constants.dart';
|
||||
import 'package:battery_plus/battery_plus.dart';
|
||||
|
||||
@@ -40,11 +40,9 @@ class RiderLogController extends GetxController {
|
||||
Future<void> fetchRiderLogs({required String userId}) async {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.getRiderLogLive
|
||||
: ApiConstants.getRiderLogDev;
|
||||
final url = "$base?userid=$userId";
|
||||
final response = await _getProvider.getRiderLog(url);
|
||||
// v1 answers duty for the *token's* rider — `userid` is neither sent
|
||||
// nor needed. See [GetRiderLogProvider].
|
||||
final response = await _getProvider.getRiderLog();
|
||||
|
||||
// Handle either {details: {...}} or {data: [...]} shapes
|
||||
if (response == null) {
|
||||
@@ -122,11 +120,7 @@ class RiderLogController extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> fetchRiderCount({required String userId}) async {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.getRiderCountLive
|
||||
: ApiConstants.getRiderCountDev;
|
||||
final url = "$base?userid=$userId";
|
||||
final response = await _getProvider.getRiderCount(url);
|
||||
final response = await _getProvider.getRiderCount();
|
||||
final dynamic cnt = response != null ? response['count'] : null;
|
||||
riderCount.value = cnt is num
|
||||
? cnt.toInt()
|
||||
@@ -134,46 +128,30 @@ class RiderLogController extends GetxController {
|
||||
}
|
||||
|
||||
Future<bool> createLogin(RiderLogin login) async {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createRiderLogLive
|
||||
: ApiConstants.createRiderLogDev;
|
||||
final ok = await _createProvider.createRiderLog(
|
||||
base,
|
||||
_CreateRiderLogRequestCompat.fromRiderLogin(login),
|
||||
);
|
||||
return ok != null && ok.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<bool> updateLog(RiderUpdate update) async {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.updateRiderLogLive
|
||||
: ApiConstants.updateRiderLogDev;
|
||||
final ok = await _updateProvider.updateRiderLog(
|
||||
base,
|
||||
_UpdateRiderLogRequestCompat.fromRiderUpdate(update),
|
||||
);
|
||||
return ok != null && ok.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<bool> createBreak(RiderBreak brk) async {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createBreakRiderLogLive
|
||||
: ApiConstants.createBreakRiderLogDev;
|
||||
final ok = await _breakProvider.createBreakRiderLog(
|
||||
base,
|
||||
_BreakLogRequestCompat.fromRiderBreak(brk),
|
||||
);
|
||||
return ok != null && ok.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<bool> updateBreak(RiderBreak brk) async {
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.updateBreakRiderLogLive
|
||||
: ApiConstants.updateBreakRiderLogDev;
|
||||
final ok = await _breakProvider.updateBreakRiderLog(
|
||||
base,
|
||||
_BreakLogRequestCompat.fromRiderBreak(brk),
|
||||
);
|
||||
// `PUT /miler/breaks/end` takes no body and closes whichever break is
|
||||
// open, which the server knows and the app was only guessing at.
|
||||
final ok = await _breakProvider.updateBreakRiderLog();
|
||||
return ok != null && ok.isNotEmpty;
|
||||
}
|
||||
|
||||
@@ -190,10 +168,6 @@ class RiderLogController extends GetxController {
|
||||
required String latitude,
|
||||
required String longitude,
|
||||
}) async {
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.updateBreakRiderLogLive
|
||||
: ApiConstants.updateBreakRiderLogDev;
|
||||
|
||||
final payload = {
|
||||
"breakid": breakid,
|
||||
"logid": logid,
|
||||
@@ -209,11 +183,9 @@ class RiderLogController extends GetxController {
|
||||
};
|
||||
|
||||
try {
|
||||
// Debug: show exact URL and JSON body
|
||||
debugPrint('[BREAK][UPDATE] URL: $url');
|
||||
debugPrint('[BREAK][UPDATE] Body: $payload');
|
||||
|
||||
final response = await _breakProvider.updateBreakRiderLog(url, payload);
|
||||
final response = await _breakProvider.updateBreakRiderLog();
|
||||
|
||||
// Debug: show raw response
|
||||
debugPrint(
|
||||
@@ -375,13 +347,9 @@ class RiderLogController extends GetxController {
|
||||
payload.remove('lastname');
|
||||
}
|
||||
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createRiderLogLive
|
||||
: ApiConstants.createRiderLogDev;
|
||||
debugPrint('[RIDERLOG][CREATE LOGIN NOW] URL: $base');
|
||||
debugPrint('[RIDERLOG][CREATE LOGIN NOW] Payload: $payload');
|
||||
|
||||
final resp = await _createProvider.createRiderLog(base, payload);
|
||||
final resp = await _createProvider.createRiderLog(payload);
|
||||
debugPrint(
|
||||
'[RIDERLOG][CREATE LOGIN NOW] Response: ${resp == null ? 'null' : resp.toString()}',
|
||||
);
|
||||
@@ -391,7 +359,7 @@ class RiderLogController extends GetxController {
|
||||
debugPrint(
|
||||
'[RIDERLOG][CREATE LOGIN NOW] Failed, saving to offline queue',
|
||||
);
|
||||
await _saveToOfflineQueue(base, payload);
|
||||
await _saveToOfflineQueue('/miler/logs', payload);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -500,11 +468,7 @@ class RiderLogController extends GetxController {
|
||||
payload.remove('lastname');
|
||||
}
|
||||
|
||||
final base = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createRiderLogLive
|
||||
: ApiConstants.createRiderLogDev;
|
||||
|
||||
await _saveToOfflineQueue(base, payload);
|
||||
await _saveToOfflineQueue('/miler/logs', payload);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[RIDERLOG] Error saving offline log: $e');
|
||||
@@ -532,10 +496,15 @@ class RiderLogController extends GetxController {
|
||||
await prefs.setInt('onduty', on ? 1 : 0);
|
||||
if (on) {
|
||||
await createLoginNowV2();
|
||||
final int interval = prefs.getInt('logseconds') ?? 0;
|
||||
if (interval > 0) {
|
||||
startAutoCreateLoginLoop(seconds: interval);
|
||||
}
|
||||
// ── Going on duty always starts the heartbeat ──
|
||||
//
|
||||
// Gated on `interval > 0`, this read a stored zero as a decision not
|
||||
// to beat. The v1 login has no `logseconds` to store, so the gate was
|
||||
// shut on every rider: duty went on, one log was posted by the call
|
||||
// above, and nothing followed it. Mirrors the logistics controller —
|
||||
// see [resolveLogSeconds].
|
||||
final int interval = resolveLogSeconds(prefs.getInt('logseconds'));
|
||||
startAutoCreateLoginLoop(seconds: interval);
|
||||
|
||||
// Ensure foreground logging notification is started when going on-duty
|
||||
try {
|
||||
@@ -604,16 +573,43 @@ class RiderLogController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the periodic heartbeat is currently running on the stream path.
|
||||
///
|
||||
/// ── Why a test needs to be able to see this ──
|
||||
///
|
||||
/// The `logseconds` bug was invisible from outside the class. Duty went on,
|
||||
/// the API accepted it, one log was posted by the immediate call — and the
|
||||
/// loop that was supposed to follow it simply never started. Nothing threw,
|
||||
/// nothing logged an error, and the only symptom was a console panel that had
|
||||
/// been blank for so long it read as a backend problem.
|
||||
///
|
||||
/// Android normally runs the heartbeat as a foreground service instead, and
|
||||
/// leaves this null; the test host is never Android, so the stream path is
|
||||
/// the one under test. See [startAutoCreateLoginLoop].
|
||||
@visibleForTesting
|
||||
bool get autoLoopRunning => _autoLoginSubscription != null;
|
||||
|
||||
// Start periodic createRiderLog calls based on seconds (or prefs 'logseconds')
|
||||
// ✅ CRITICAL: When there are active deliveries, use 30 seconds (same as delivery logs)
|
||||
// Otherwise, use the configured logseconds interval
|
||||
void startAutoCreateLoginLoop({int? seconds}) async {
|
||||
/// `Future<void>`, not `void`. It was fire-and-forget, which meant no caller
|
||||
/// could wait for the loop to actually be up — and the one place that most
|
||||
/// wanted to, [setOnDuty], had no way to tell whether the heartbeat it had
|
||||
/// just asked for existed. Every existing call site ignores the future and is
|
||||
/// unchanged by this.
|
||||
Future<void> startAutoCreateLoginLoop({int? seconds}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// ✅ Check if there are active deliveries - if yes, use 30 seconds (same as delivery logs)
|
||||
final bool hasActiveDeliveries =
|
||||
prefs.getBool('has_live_deliveries') ?? false;
|
||||
final int baseInterval = seconds ?? (prefs.getInt('logseconds') ?? 0);
|
||||
// The configured cadence, or the floor under it. `?? 0` here was the
|
||||
// second half of the same bug as the one in [setOnDuty]: a caller passing
|
||||
// no `seconds` fell through to a stored zero and returned at the
|
||||
// `interval <= 0` guard below. See [resolveLogSeconds].
|
||||
final int baseInterval = resolveLogSeconds(
|
||||
seconds ?? prefs.getInt('logseconds'),
|
||||
);
|
||||
|
||||
// When there are active deliveries, post rider logs every 30 seconds (matching delivery logs)
|
||||
// Otherwise, use the configured interval
|
||||
@@ -625,12 +621,18 @@ class RiderLogController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
await stopAutoCreateLoginLoop(); // Cancel existing subscription
|
||||
// Stop before start, which is what keeps this method safe to call from
|
||||
// every place that does. Whichever call arrives last wins, and there is
|
||||
// never a second loop: the subscription is cancelled and the foreground
|
||||
// service stopped before either is created again.
|
||||
await stopAutoCreateLoginLoop();
|
||||
|
||||
// Attempt to flush offline logs on loop start
|
||||
flushOfflineLogs();
|
||||
|
||||
if (interval <= 0) return;
|
||||
// No `interval <= 0` guard. [resolveLogSeconds] cannot return one, and the
|
||||
// guard that used to be here is what turned a missing `logseconds` into a
|
||||
// rider who never reported.
|
||||
final int onduty = prefs.getInt('onduty') ?? 0;
|
||||
if (onduty != 1) {
|
||||
debugPrint('[RIDERLOG][AUTO LOOP] Not starting - onduty=$onduty');
|
||||
@@ -921,10 +923,6 @@ class RiderLogController extends GetxController {
|
||||
final breakdate = _formatDateTimeFull(now); // e.g. 2025-10-16 16:36:16
|
||||
final breakstart = _formatTime(now); // e.g. 16:36:16
|
||||
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createBreakRiderLogLive
|
||||
: ApiConstants.createBreakRiderLogDev;
|
||||
|
||||
// Build payload with all required fields in the exact format the API expects
|
||||
final payload = <String, dynamic>{
|
||||
"breakid": localBreakId,
|
||||
@@ -941,13 +939,12 @@ class RiderLogController extends GetxController {
|
||||
};
|
||||
|
||||
// Debug logs for terminal visibility
|
||||
debugPrint('[BREAK][CREATE] URL: $url');
|
||||
debugPrint('[BREAK][CREATE] Payload: $payload');
|
||||
|
||||
// Persist local break id immediately to ensure update can reference it
|
||||
await prefs.setInt('breakId', localBreakId);
|
||||
|
||||
final resp = await _breakProvider.createBreakRiderLog(url, payload);
|
||||
final resp = await _breakProvider.createBreakRiderLog(payload);
|
||||
|
||||
debugPrint(
|
||||
'[BREAK][CREATE] Response: ${resp == null ? 'null' : resp.toString()}',
|
||||
@@ -1045,9 +1042,6 @@ class RiderLogController extends GetxController {
|
||||
final breakhoursDouble = duration.inSeconds / 3600.0;
|
||||
|
||||
// Debug logs for terminal visibility
|
||||
debugPrint(
|
||||
'[BREAK][UPDATE] URL: ${ApiConstants.mainRoute == 'live' ? ApiConstants.updateBreakRiderLogLive : ApiConstants.updateBreakRiderLogDev}',
|
||||
);
|
||||
debugPrint(
|
||||
'[BREAK][UPDATE] Fields: breakid=$breakid, logid=$logid, userid=$userid, partnerid=$partnerid, shiftid=$shiftid, breakend=$breakend, breakhours=$breakhoursDouble, lat=$latitude, lng=$longitude',
|
||||
);
|
||||
@@ -1101,10 +1095,6 @@ class RiderLogController extends GetxController {
|
||||
final iso = _formatDateTimeFull(now);
|
||||
final loginTime = _formatTime(now);
|
||||
|
||||
final url = ApiConstants.mainRoute == 'live'
|
||||
? ApiConstants.createRiderLogLive
|
||||
: ApiConstants.createRiderLogDev;
|
||||
|
||||
// ✅ Check if there are active deliveries to set status
|
||||
final bool hasActiveDeliveries =
|
||||
prefs.getBool('has_live_deliveries') ?? false;
|
||||
@@ -1141,10 +1131,9 @@ class RiderLogController extends GetxController {
|
||||
userfcmtoken: userfcmtoken ?? '',
|
||||
).toJson();
|
||||
|
||||
debugPrint('[RIDERLOG][CREATE LOGIN] URL: $url');
|
||||
debugPrint('[RIDERLOG][CREATE LOGIN] Payload: $payload');
|
||||
|
||||
final resp = await _createProvider.createRiderLog(url, payload);
|
||||
final resp = await _createProvider.createRiderLog(payload);
|
||||
debugPrint(
|
||||
'[RIDERLOG][CREATE LOGIN] Response: ${resp == null ? 'null' : resp.toString()}',
|
||||
);
|
||||
@@ -1205,7 +1194,7 @@ class RiderLogController extends GetxController {
|
||||
// We can use _createProvider generic call
|
||||
|
||||
debugPrint('[RIDERLOG][OFFLINE] Retrying: $payload');
|
||||
final resp = await _createProvider.createRiderLog(url, payload);
|
||||
final resp = await _createProvider.createRiderLog(payload);
|
||||
|
||||
if (resp != null && resp.isNotEmpty) {
|
||||
debugPrint('[RIDERLOG][OFFLINE] Success!');
|
||||
|
||||
@@ -107,7 +107,7 @@ class DeliveryApi {
|
||||
/// **ABSENT** — the generic status write the ported flow uses for every
|
||||
/// transition (accepted → active → arrived → delivered → skipped).
|
||||
///
|
||||
/// Was `queue.workolik.com/live/api/v1/deliveries/updatedelivery`. There is no
|
||||
/// Was `deliveries/updatedelivery` on the retired backend. There is no
|
||||
/// single status-write route on Doormile; the transitions above are separate
|
||||
/// endpoints. Call sites that still post a bare status change land here and
|
||||
/// will 404 until either they are split or the route is built.
|
||||
@@ -117,7 +117,7 @@ class DeliveryApi {
|
||||
|
||||
/// **LIVE** — periodic location/telemetry log.
|
||||
///
|
||||
/// Was `queue.workolik.com/live/api/v2/partners/createriderlog`.
|
||||
/// Was `partners/createriderlog` on the retired backend.
|
||||
static String get createRiderLog => url('/miler/logs');
|
||||
|
||||
/// **LIVE** — read them back.
|
||||
@@ -142,7 +142,7 @@ class DeliveryApi {
|
||||
|
||||
/// **ABSENT** — per-delivery event log.
|
||||
///
|
||||
/// Was `queue.workolik.com/live/api/v2/deliveries/createdeliverylog`. The
|
||||
/// Was `deliveries/createdeliverylog` on the retired backend. The
|
||||
/// closest real thing is consignment logs, which key on a consignmentid the
|
||||
/// delivery flow does not always hold.
|
||||
static String get createDeliveryLog => url('/miler/consignments/logs');
|
||||
|
||||
@@ -1,260 +1,46 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// Combined Riderlog providers:
|
||||
|
||||
/// Hardcoded known-good IPs for hosts where carrier DNS returns broken nodes.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// THE DELIVERY LINE'S RIDER LOG IS THE PARCEL LINE'S RIDER LOG
|
||||
///
|
||||
/// **Empty since the delivery line moved to Doormile.** The one entry here
|
||||
/// pinned `queue.workolik.com` to 66.116.225.226, because some Indian carriers'
|
||||
/// DNS resolved that host to a CDN node that answered 404. Nothing calls that
|
||||
/// host any more — every delivery-line write goes to `api.doormile.com` via
|
||||
/// [DeliveryApi] — so the pin was routing advice about a server this app no
|
||||
/// longer talks to.
|
||||
/// ── What this file used to be ──
|
||||
///
|
||||
/// It is deliberately not re-pointed at the Doormile host: that IP belongs to
|
||||
/// the old backend, and pinning it for `api.doormile.com` would send every
|
||||
/// request to the wrong server with the right SNI. If Doormile ever needs the
|
||||
/// same workaround, add it here with its own verified address.
|
||||
const _knownGoodIPs = <String, String>{};
|
||||
/// A second, complete implementation of duty, breaks and the location
|
||||
/// heartbeat, posting to the two retired pre-v1 hosts over raw `http`. It survived the migration only because
|
||||
/// nothing in `lib/` imported it: the delivery shell reaches it through
|
||||
/// [riderShell], which is production for any tenant on the delivery line.
|
||||
///
|
||||
/// It carried two things that had already been deleted from the parcel line:
|
||||
///
|
||||
/// • **`_buildSslBypassClient()`** — an `IOClient` whose
|
||||
/// `badCertificateCallback` returned `true` for *every* certificate, with a
|
||||
/// hand-rolled `SecureSocket` upgrade and a hardcoded IP pin. Every request
|
||||
/// it carried — a rider's GPS track, his duty state, his break times — could
|
||||
/// be read and rewritten by anything on the path. The parcel line removed
|
||||
/// this; the delivery line kept it.
|
||||
/// • **The legacy hosts themselves**, which the v1 migration left behind.
|
||||
///
|
||||
/// ── Why this is an export and not a rewrite ──
|
||||
///
|
||||
/// `lib/providers/Riderlog/riderlog_provider.dart` already does exactly this
|
||||
/// job against `api.doormile.com`: same four class names, same legacy
|
||||
/// `{status, details}` envelopes, mapped onto `POST /miler/duty/start`,
|
||||
/// `PUT /miler/location`, `POST /miler/logs` and the break endpoints. The
|
||||
/// delivery line's controller calls the same four classes by the same names.
|
||||
///
|
||||
/// So there is nothing to port. Re-exporting means one implementation, one duty
|
||||
/// gate, one telemetry payload and one place where a contract change lands —
|
||||
/// which is the whole point of having migrated the parcel line at all. The two
|
||||
/// lines cannot drift, because there is only one of them.
|
||||
///
|
||||
/// The `String urldata` first parameter is gone from every method; callers that
|
||||
/// were building a legacy URL to pass in have had that construction deleted
|
||||
/// rather than made to compile.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
library;
|
||||
|
||||
/// Creates an IOClient that:
|
||||
/// 1. Bypasses SSL certificate errors
|
||||
/// 2. Forces known-good IPs for hosts where carrier DNS returns broken CDN nodes
|
||||
/// 3. Manually does TLS upgrade with correct SNI (hostname, not IP)
|
||||
IOClient _buildSslBypassClient() {
|
||||
final httpClient = HttpClient()
|
||||
..badCertificateCallback = (X509Certificate cert, String host, int port) =>
|
||||
true;
|
||||
|
||||
httpClient
|
||||
.connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) async {
|
||||
final host = uri.host;
|
||||
final port = uri.port;
|
||||
|
||||
// Use known-good IP if available, else resolve normally (prefer IPv4)
|
||||
InternetAddress? target;
|
||||
final knownIP = _knownGoodIPs[host];
|
||||
if (knownIP != null) {
|
||||
target = InternetAddress(knownIP);
|
||||
debugPrint('[SSL_CLIENT] Using known-good IP: $knownIP for $host');
|
||||
} else {
|
||||
try {
|
||||
final addresses = await InternetAddress.lookup(
|
||||
host,
|
||||
type: InternetAddressType.IPv4,
|
||||
);
|
||||
if (addresses.isNotEmpty) target = addresses.first;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (uri.scheme == 'https') {
|
||||
final socketFuture = Socket.connect(target ?? InternetAddress(host), port)
|
||||
.then(
|
||||
(plain) => SecureSocket.secure(
|
||||
plain,
|
||||
host: host, // SNI = original hostname for Nginx routing
|
||||
onBadCertificate: (_) => true,
|
||||
supportedProtocols: ['http/1.1'],
|
||||
),
|
||||
)
|
||||
.then((s) => s as Socket);
|
||||
return ConnectionTask.fromSocket<Socket>(socketFuture, () {});
|
||||
}
|
||||
|
||||
return Socket.startConnect(target ?? InternetAddress(host), port);
|
||||
};
|
||||
|
||||
return IOClient(httpClient);
|
||||
}
|
||||
|
||||
class CreateRiderLogProvider {
|
||||
Future<Map<String, dynamic>?> createRiderLog(
|
||||
String urldata,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
const maxAttempts = 3;
|
||||
try {
|
||||
debugPrint('createRiderLog payload ${json.encode(data)}');
|
||||
} catch (_) {}
|
||||
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
final client = _buildSslBypassClient();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client.post(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
debugPrint('createRiderLog url $urldata (attempt $attempt)');
|
||||
debugPrint('createRiderLog status ${response.statusCode}');
|
||||
debugPrint('createRiderLog response ${response.body}');
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return json.decode(response.body.toString()) as Map<String, dynamic>;
|
||||
} else {
|
||||
debugPrint(
|
||||
'createRiderLog failed: HTTP ${response.statusCode} (attempt $attempt/$maxAttempts)',
|
||||
);
|
||||
// On 404/5xx, wait and retry to potentially hit a different CDN node
|
||||
if (attempt < maxAttempts) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('createRiderLog exception (attempt $attempt): $e');
|
||||
if (attempt < maxAttempts) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('createRiderLog failed after $maxAttempts attempts');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateRiderLogProvider {
|
||||
Future<Map<String, dynamic>?> updateRiderLog(
|
||||
String urldata,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint('updateRiderLog url: $urldata');
|
||||
debugPrint('updateRiderLog response: ${response.body}');
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
final decoded = json.decode(response.body);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
return decoded;
|
||||
} else {
|
||||
debugPrint(
|
||||
'⚠️ updateRiderLog: Expected Map but got ${decoded.runtimeType}',
|
||||
);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
debugPrint('❌ updateRiderLog failed with code ${response.statusCode}');
|
||||
return {};
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Exception in updateRiderLog: $e');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetRiderLogProvider {
|
||||
Future<Map<String, dynamic>?> getRiderLog(String urldata) async {
|
||||
Map<String, dynamic>? getRiderLogResponse;
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await get(
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
debugPrint('getRiderLog response ${response.body}');
|
||||
debugPrint('getRiderLog url ${urldata.toString()}');
|
||||
getRiderLogResponse =
|
||||
json.decode(response.body.toString()) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
return getRiderLogResponse;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getRiderCount(String urldata) async {
|
||||
Map<String, dynamic>? getRiderCountResponse;
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await get(
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
debugPrint('getRiderCount response ${response.body}');
|
||||
debugPrint('getRiderCount url ${urldata.toString()}');
|
||||
getRiderCountResponse =
|
||||
json.decode(response.body.toString()) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
return getRiderCountResponse;
|
||||
}
|
||||
}
|
||||
|
||||
class BreakRiderLogProvider {
|
||||
Future<Map<String, dynamic>?> createBreakRiderLog(
|
||||
String urldata,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
Map<String, dynamic>? breakLogResponse;
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await post(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
debugPrint('createBreakRiderLog url $urldata');
|
||||
debugPrint('createBreakRiderLog response ${response.body}');
|
||||
breakLogResponse =
|
||||
json.decode(response.body.toString()) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
return breakLogResponse;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> updateBreakRiderLog(
|
||||
String urldata,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
Map<String, dynamic>? breakLogResponse;
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
debugPrint('updateBreakRiderLog url $urldata');
|
||||
debugPrint('updateBreakRiderLog response ${response.body}');
|
||||
breakLogResponse =
|
||||
json.decode(response.body.toString()) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
return breakLogResponse;
|
||||
}
|
||||
}
|
||||
export 'package:miler/providers/Riderlog/riderlog_provider.dart'
|
||||
show
|
||||
BreakRiderLogProvider,
|
||||
CreateRiderLogProvider,
|
||||
GetRiderLogProvider,
|
||||
UpdateRiderLogProvider,
|
||||
unawaited;
|
||||
|
||||
@@ -1,67 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:miler/data/api_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// Combined Deliverylog providers:
|
||||
|
||||
/// Hardcoded known-good IPs for hosts where carrier DNS returns broken CDN nodes.
|
||||
/// Confirmed: 66.116.225.226 = 200 OK, 125.21.240.67 = 404/405.
|
||||
/// Empty since the delivery line moved to Doormile — see the note on the
|
||||
/// matching map in `providers/Riderlog/riderlog_provider.dart`. The old entry
|
||||
/// pinned `queue.workolik.com`, a host nothing calls now, and must not be
|
||||
/// re-pointed at `api.doormile.com`.
|
||||
const _knownGoodIPs = <String, String>{};
|
||||
|
||||
/// Creates an IOClient that:
|
||||
/// 1. Bypasses SSL certificate errors
|
||||
/// 2. Forces known-good IPs to avoid broken CDN nodes from carrier DNS
|
||||
/// 3. Manually does TLS upgrade with correct SNI (hostname, not IP)
|
||||
IOClient _buildSslBypassClient() {
|
||||
final httpClient = HttpClient()
|
||||
..badCertificateCallback = (X509Certificate cert, String host, int port) =>
|
||||
true;
|
||||
|
||||
httpClient
|
||||
.connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) async {
|
||||
final host = uri.host;
|
||||
final port = uri.port;
|
||||
|
||||
InternetAddress? target;
|
||||
final knownIP = _knownGoodIPs[host];
|
||||
if (knownIP != null) {
|
||||
target = InternetAddress(knownIP);
|
||||
} else {
|
||||
try {
|
||||
final addresses = await InternetAddress.lookup(
|
||||
host,
|
||||
type: InternetAddressType.IPv4,
|
||||
);
|
||||
if (addresses.isNotEmpty) target = addresses.first;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (uri.scheme == 'https') {
|
||||
final socketFuture = Socket.connect(target ?? InternetAddress(host), port)
|
||||
.then(
|
||||
(plain) => SecureSocket.secure(
|
||||
plain,
|
||||
host: host,
|
||||
onBadCertificate: (_) => true,
|
||||
supportedProtocols: ['http/1.1'],
|
||||
),
|
||||
)
|
||||
.then((s) => s as Socket);
|
||||
return ConnectionTask.fromSocket<Socket>(socketFuture, () {});
|
||||
}
|
||||
|
||||
return Socket.startConnect(target ?? InternetAddress(host), port);
|
||||
};
|
||||
|
||||
return IOClient(httpClient);
|
||||
}
|
||||
/// ── The SSL bypass is gone ──
|
||||
///
|
||||
/// This file built its own `IOClient` with `onBadCertificate: (_) => true` and
|
||||
/// a hand-rolled `SecureSocket` upgrade, so **every certificate was accepted**
|
||||
/// — a delivery rider's position, his order ids and his session were readable
|
||||
/// and rewritable by anything on the path. The parcel line deleted the same
|
||||
/// code (see `providers/Riderlog/riderlog_provider.dart`); the delivery line
|
||||
/// kept it because nothing in `lib/` imported this file directly.
|
||||
///
|
||||
/// It also sent **no `Authorization` header**. The URLs it posts to are v1
|
||||
/// routes on `api.doormile.com` — resolved through the [DeliveryApi] facade —
|
||||
/// so every one of these calls could only ever have been answered with a 401.
|
||||
/// A plain client plus [ApiConfig.authHeaders] fixes both at once.
|
||||
Future<Map<String, String>> _headers() => ApiConfig.authHeaders();
|
||||
|
||||
class CreateDeliveryLogProvider {
|
||||
Future<Map<String, dynamic>?> createDeliveryLog(
|
||||
@@ -70,19 +28,12 @@ class CreateDeliveryLogProvider {
|
||||
bool wrapInArray = true,
|
||||
}) async {
|
||||
Map<String, dynamic>? result;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final body = json.encode(wrapInArray ? [data] : data);
|
||||
final response = await client
|
||||
.post(
|
||||
url,
|
||||
body: body,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
)
|
||||
.post(url, body: body, headers: await _headers())
|
||||
.timeout(const Duration(seconds: 10));
|
||||
debugPrint('createDeliveryLog url $urldata');
|
||||
debugPrint(body);
|
||||
@@ -110,18 +61,11 @@ class UpdateDeliveryProvider {
|
||||
String urldata,
|
||||
) async {
|
||||
Map<String, dynamic>? updateDeliveryResponse;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client
|
||||
.put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
)
|
||||
.put(url, body: json.encode(data), headers: await _headers())
|
||||
.timeout(const Duration(seconds: 10));
|
||||
debugPrint('updateDelivery url $urldata');
|
||||
debugPrint('updateDelivery status ${response.statusCode}');
|
||||
@@ -148,18 +92,11 @@ class UpdateDeliveryProvider {
|
||||
String urldata,
|
||||
) async {
|
||||
Map<String, dynamic>? updateDeliveryResponse;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client
|
||||
.put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
)
|
||||
.put(url, body: json.encode(data), headers: await _headers())
|
||||
.timeout(const Duration(seconds: 10));
|
||||
debugPrint('updateArrived url $urldata');
|
||||
debugPrint('updateArrived status ${response.statusCode}');
|
||||
@@ -185,18 +122,11 @@ class UpdateDeliveryProvider {
|
||||
String urldata,
|
||||
) async {
|
||||
Map<String, dynamic>? updateDeliveryResponse;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client
|
||||
.put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
)
|
||||
.put(url, body: json.encode(data), headers: await _headers())
|
||||
.timeout(const Duration(seconds: 10));
|
||||
debugPrint('updatePicked url $urldata');
|
||||
debugPrint('updatePicked status ${response.statusCode}');
|
||||
@@ -222,18 +152,11 @@ class UpdateDeliveryProvider {
|
||||
String urldata,
|
||||
) async {
|
||||
Map<String, dynamic>? updateDeliveryResponse;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client
|
||||
.put(
|
||||
url,
|
||||
body: json.encode(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
)
|
||||
.put(url, body: json.encode(data), headers: await _headers())
|
||||
.timeout(const Duration(seconds: 10));
|
||||
debugPrint('updateActive url $urldata');
|
||||
debugPrint('updateActive status ${response.statusCode}');
|
||||
@@ -258,7 +181,7 @@ class UpdateDeliveryProvider {
|
||||
class GetDeliveryLogProvider {
|
||||
Future<Map<String, dynamic>?> getDeliveryLog(String urldata) async {
|
||||
Map<String, dynamic>? result;
|
||||
final client = _buildSslBypassClient();
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(urldata);
|
||||
final response = await client.get(
|
||||
|
||||
@@ -34,6 +34,7 @@ import 'package:miler/xpress/views/Dashboard/deliveries/deliveries.dart'
|
||||
as deliveries;
|
||||
import 'package:miler/xpress/background/live_tracking_service.dart';
|
||||
import 'package:miler/xpress/data/delivery_api.dart';
|
||||
import 'package:miler/data/heartbeat.dart';
|
||||
|
||||
/// Helper function to launch phone dialer - works in both debug and release builds
|
||||
/// In release builds, canLaunchUrl may fail due to R8/ProGuard, so we always try to launch
|
||||
@@ -320,11 +321,13 @@ class _HomepageState extends State<Homepage>
|
||||
Future<Duration> _getLogInterval() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final secs = prefs.getInt('logseconds');
|
||||
final int interval = (secs != null && secs > 0) ? secs : 30;
|
||||
// Already correct — routed through the shared resolver so the 30 lives in
|
||||
// one place. See [kDefaultLogSeconds] for what a zero here used to cost
|
||||
// the rider-log loop, which read the same pref and did not guard it.
|
||||
final int interval = resolveLogSeconds(prefs.getInt('logseconds'));
|
||||
return Duration(seconds: interval);
|
||||
} catch (_) {
|
||||
return const Duration(seconds: 30);
|
||||
return const Duration(seconds: kDefaultLogSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user