Files
doormile_milderapp/lib/controllers/logcontroller.dart
Thiru-tenext d7348e253f Miler rider app: surface system, visible design language, backend lifecycle
Design system
- MilerSurface ladder (canvas → working → raised → floating) with MilerPanel
  as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1.
- Visible vocabulary applied across Home, Deliveries, Activity, Account and
  the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x),
  canvas wells for anything that opens, small filled tags for shelf labels,
  demoted placeholders. Recorded in DESIGN_SYSTEM.md §6.
- One icon family: 222 Material glyphs migrated to Lucide; none left outside
  lib/xpress.
- Colour semantics corrected: amber only for what is genuinely owed, brand red
  reserved for the live stop, disabled primaries go neutral rather than pale.

Data and lifecycle
- lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart
  makes admin sequence the single ordering authority; service_day.dart, and
  stop_area.dart rewritten against live Coimbatore addresses (digit-token
  stripping, city stoplist, street suffixes, stammer collapse).
- countLabel states the load once, in bags.

Testing
- 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity,
  sheets and verify, with test/failures/ now gitignored (diff debris).
- New pins: home_gutter_test, stop_area_test, plus updated structural bounds.

Note: this commit also carries pre-existing working-tree deletions that were
present before this work (API_SPEC.md, README.md, demo test fixtures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 05:40:35 +05:30

181 lines
5.8 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miler/providers/pickuplog/pickuplog_provider.dart';
import 'package:miler/views/helpers/constants/apiconstants.dart';
import 'package:miler/background/foreground_service.dart' as fg;
import 'package:geolocator/geolocator.dart';
/// Controller for managing active pickup logs
/// Now delegates to the background service for actual logging
class LogController extends GetxController {
final CreatePickupLogProvider _logProvider = CreatePickupLogProvider();
static const String _offlineLogKey = 'offline_pickup_logs';
bool _isFlushing = false;
/// Start the pickup log streaming service (via foreground service)
Future<void> startLogging() async {
debugPrint('[ACTIVE_COLLECTION_LOG] Requesting start logging...');
// Attempt to flush offline logs on start
flushOfflineLogs();
// Only show foreground notification when rider is actually on duty
try {
final prefs = await SharedPreferences.getInstance();
final int onduty = prefs.getInt('onduty') ?? 0;
if (onduty != 1) {
debugPrint(
'[ACTIVE_COLLECTION_LOG] Skipping startLogging because onduty=$onduty',
);
return;
}
} catch (_) {
// If prefs fail, continue with best-effort start
}
if (Platform.isAndroid) {
if (await FlutterForegroundTask.isRunningService) {
debugPrint(
'[ACTIVE_COLLECTION_LOG] Foreground service already running',
);
return;
}
debugPrint(
'[ACTIVE_COLLECTION_LOG] Starting foreground service for collection logs',
);
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'doormile_bg_service',
channelName: 'Background Service',
channelDescription:
'Keeps Miler online updates running in background.',
channelImportance: NotificationChannelImportance.LOW,
priority: NotificationPriority.LOW,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: true,
playSound: false,
),
foregroundTaskOptions: ForegroundTaskOptions(
interval: 30000, // 30 seconds
isOnceEvent: false,
autoRunOnBoot: false,
allowWakeLock: true,
allowWifiLock: true,
),
);
// Check permissions before starting service to prevent Android 14 crash
final permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
debugPrint(
'[ACTIVE_COLLECTION_LOG] Location permission missing, skipping service start',
);
return;
}
try {
await FlutterForegroundTask.startService(
notificationTitle: 'Miler is running',
notificationText: 'You are Currently on Duty !',
callback: fg.riderLogCallback,
);
} catch (e) {
debugPrint('[ACTIVE_COLLECTION_LOG] Failed to start service: $e');
}
} else {
debugPrint(
'[ACTIVE_COLLECTION_LOG] iOS/Web not fully supported for background service yet',
);
}
}
/// Stop the pickup log streaming service
/// Note: This might stop rider logs too if they share the service.
/// Usually we only stop if the user goes off-duty or logs out.
void stopLogging() {
debugPrint(
'[ACTIVE_COLLECTION_LOG] Stop logging requested (no-op to preserve rider logs)',
);
// We do not stop the service here because it might be running for Rider Logs.
// The service should be stopped by RiderLogController when going off-duty.
}
// ---------------- Offline Queue Logic (Foreground Helper) ----------------
/// Call this on app start or network restoration
Future<void> flushOfflineLogs() async {
if (_isFlushing) return;
_isFlushing = true;
try {
final prefs = await SharedPreferences.getInstance();
final List<String> queue = prefs.getStringList(_offlineLogKey) ?? [];
if (queue.isEmpty) return;
debugPrint(
'[ACTIVE_COLLECTION_LOG][OFFLINE] Flushing ${queue.length} offline logs...',
);
final List<String> remaining = [];
bool anySuccess = false;
for (final itemStr in queue) {
try {
final Map<String, dynamic> item = jsonDecode(itemStr);
final String orderId = item['orderId'] ?? '';
final Map<String, dynamic> payload = Map<String, dynamic>.from(
item['payload'] ?? {},
);
if (payload.isEmpty) continue;
debugPrint(
'[ACTIVE_COLLECTION_LOG][OFFLINE] Retrying for orderId: $orderId',
);
final result = await _logProvider
.createPickupLog(payload)
.timeout(const Duration(seconds: 8));
if (result != null) {
debugPrint(
'[ACTIVE_COLLECTION_LOG][OFFLINE] Success for orderId: $orderId',
);
anySuccess = true;
} else {
remaining.add(itemStr);
}
} catch (e) {
debugPrint(
'[ACTIVE_COLLECTION_LOG][OFFLINE] Error processing item: $e',
);
remaining.add(itemStr);
}
}
if (anySuccess || remaining.length != queue.length) {
await prefs.setStringList(_offlineLogKey, remaining);
debugPrint(
'[ACTIVE_COLLECTION_LOG][OFFLINE] Flush complete. Remaining: ${remaining.length}',
);
}
} catch (e) {
debugPrint('[ACTIVE_COLLECTION_LOG][OFFLINE] Flush error: $e');
} finally {
_isFlushing = false;
}
}
}