createTrackingMiddleware function

List<Middleware<AppState>> createTrackingMiddleware(
  1. TrackingDeps deps
)

Implementation

List<Middleware<AppState>> createTrackingMiddleware(TrackingDeps deps) {
  StreamSubscription<Position>? posSub;
  BatchingUploader? uploader;
  SampleBuilder? builder;
  LiveSubscriber? live;

  Future<String> ensurePermission(Store<AppState> store) async {
    bool enabled = await Geolocator.isLocationServiceEnabled();
    if (!enabled) {
      store.dispatch(StatusChanged('Location services are disabled.'));
      return 'denied';
    }
    var perm = await Geolocator.checkPermission();
    if (perm == LocationPermission.denied) {
      perm = await Geolocator.requestPermission();
    }
    if (perm == LocationPermission.denied) {
      store.dispatch(PermissionChanged('denied'));
      return 'denied';
    }
    if (perm == LocationPermission.deniedForever) {
      store.dispatch(PermissionChanged('foreverDenied'));
      return 'foreverDenied';
    }
    store.dispatch(PermissionChanged('granted'));
    return 'granted';
  }

  Future<void> start(Store<AppState> store, StartTrackingAction action) async {
    final perm = await ensurePermission(store);
    if (perm != 'granted') return;

    // Build gRPC client + uploader
    final factory = GrpcClientFactory(
      host: deps.host,
      port: deps.port,
      userTls: deps.useTls,
      tokenProvider: deps.tokenProvider,
    );
    uploader = BatchingUploader(factory, maxBatch: 50, flushEvery: const Duration(seconds: 5));
    await uploader!.start();

    final did = await DeviceIdentity.getOrCreate();
    final sid = DateTime.now().millisecondsSinceEpoch.toString();

    builder = SampleBuilder(deviceId: did, employeeId: action.employeeId, sessionId: sid);

    // Position stream
    const settings = LocationSettings(
      accuracy: LocationAccuracy.bestForNavigation,
      distanceFilter: 10,
    );

    posSub = Geolocator.getPositionStream(locationSettings: settings).listen(
          (pos) async {
        final sample = await builder!.fromPosition(pos);
        uploader?.enqueue(sample);
        store.dispatch(LastSampleUpdated(sample));
      },
      onError: (e) => store.dispatch(StatusChanged('Location error: $e')),
      cancelOnError: false,
    );

    store.dispatch(TrackingStarted(did, sid, action.employeeId));
  }

  Future<void> stop(Store<AppState> store) async {
    await posSub?.cancel();
    posSub = null;
    if (uploader != null) {
      await uploader!.stop();
      uploader = null;
    }
    builder = null;
    store.dispatch(TrackingStopped());
  }

  Future<void> subscribeLive(Store<AppState> store, SubscribeLiveAction a) async {
    // Optional live stream of processed server updates (for supervisor UI etc.)
    final factory = GrpcClientFactory(
      host: deps.host,
      port: deps.port,
      userTls: deps.useTls,
      tokenProvider: deps.tokenProvider,
    );
    live = LiveSubscriber(factory);
    await live!.start(
      deviceIds: a.deviceIds,
      employeeIds: a.employeeIds,
      onUpdate: (u) => store.dispatch(LiveUpdateReceived(u)),
    );
  }

  Future<void> unsubscribeLive() async {
    await live?.stop();
    live = null;
  }

  return [
    TypedMiddleware<AppState, StartTrackingAction>((store, action, next) async {
      next(action);
      await start(store, action);
    }).call,
    TypedMiddleware<AppState, StopTrackingAction>((store, action, next) async {
      next(action);
      await stop(store);
    }).call,
    TypedMiddleware<AppState, SubscribeLiveAction>((store, action, next) async {
      next(action);
      await subscribeLive(store, action);
    }).call,
    TypedMiddleware<AppState, UnsubscribeLiveAction>((store, action, next) async {
      next(action);
      await unsubscribeLive();
    }).call,
  ];
}