import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../database/database.dart'; import 'package:drift/drift.dart' as drift; import 'package:geolocator/geolocator.dart'; enum RideState { idle, ready, riding, paused, finished } class RideSession { final int? dbId; final RideState state; final double tripDistanceMiles; final double topSpeedMph; final double maxLeanLeft; final double maxLeanRight; final double maxAccelG; final double maxBrakingG; final DateTime? startTime; RideSession({ this.dbId, this.state = RideState.idle, this.tripDistanceMiles = 0.0, this.topSpeedMph = 0.0, this.maxLeanLeft = 0.0, this.maxLeanRight = 0.0, this.maxAccelG = 0.0, this.maxBrakingG = 0.0, this.startTime, }); RideSession copyWith({ int? dbId, RideState? state, double? tripDistanceMiles, double? topSpeedMph, double? maxLeanLeft, double? maxLeanRight, double? maxAccelG, double? maxBrakingG, DateTime? startTime, }) { return RideSession( dbId: dbId ?? this.dbId, state: state ?? this.state, tripDistanceMiles: tripDistanceMiles ?? this.tripDistanceMiles, topSpeedMph: topSpeedMph ?? this.topSpeedMph, maxLeanLeft: maxLeanLeft ?? this.maxLeanLeft, maxLeanRight: maxLeanRight ?? this.maxLeanRight, maxAccelG: maxAccelG ?? this.maxAccelG, maxBrakingG: maxBrakingG ?? this.maxBrakingG, startTime: startTime ?? this.startTime, ); } } class RideStateMachine extends StateNotifier { final AppDatabase db; Position? _lastPosition; RideStateMachine(this.db) : super(RideSession()); void setReady() { state = state.copyWith(state: RideState.ready); } Future startRide() async { final id = await db.startRide(); state = state.copyWith( dbId: id, state: RideState.riding, startTime: DateTime.now(), tripDistanceMiles: 0, topSpeedMph: 0, ); _lastPosition = null; } void pauseRide() { state = state.copyWith(state: RideState.paused); } void resumeRide() { state = state.copyWith(state: RideState.riding); _lastPosition = null; // reset so we don't jump distance } Future finishRide() async { if (state.dbId != null) { await db.endRide( state.dbId!, RidesCompanion( distanceMiles: drift.Value(state.tripDistanceMiles), topSpeedMph: drift.Value(state.topSpeedMph), maxLeanLeft: drift.Value(state.maxLeanLeft), maxLeanRight: drift.Value(state.maxLeanRight), maxAccelG: drift.Value(state.maxAccelG), maxBrakingG: drift.Value(state.maxBrakingG), ), ); } state = state.copyWith(state: RideState.finished); } void reset() { state = RideSession(); } void updateTelemetry({ Position? position, double? speedMph, double? leanAngle, double? accelG, double? lateralG, }) { if (state.state != RideState.riding) return; double newTopSpeed = state.topSpeedMph; double newMaxLeanLeft = state.maxLeanLeft; double newMaxLeanRight = state.maxLeanRight; double newMaxAccelG = state.maxAccelG; double newMaxBrakingG = state.maxBrakingG; double newDistance = state.tripDistanceMiles; if (speedMph != null && speedMph > state.topSpeedMph) { newTopSpeed = speedMph; } if (leanAngle != null) { if (leanAngle < 0 && leanAngle.abs() > state.maxLeanLeft) { newMaxLeanLeft = leanAngle.abs(); } else if (leanAngle > 0 && leanAngle > state.maxLeanRight) { newMaxLeanRight = leanAngle; } } if (accelG != null) { if (accelG > 0 && accelG > state.maxAccelG) { newMaxAccelG = accelG; } else if (accelG < 0 && accelG.abs() > state.maxBrakingG) { newMaxBrakingG = accelG.abs(); } } if (position != null) { if (_lastPosition != null) { // Calculate distance in meters final distMeters = Geolocator.distanceBetween( _lastPosition!.latitude, _lastPosition!.longitude, position.latitude, position.longitude, ); newDistance += distMeters / 1609.34; // convert to miles } _lastPosition = position; } state = state.copyWith( topSpeedMph: newTopSpeed, maxLeanLeft: newMaxLeanLeft, maxLeanRight: newMaxLeanRight, maxAccelG: newMaxAccelG, maxBrakingG: newMaxBrakingG, tripDistanceMiles: newDistance, ); } } final rideStateMachineProvider = StateNotifierProvider((ref) { final db = ref.watch(databaseProvider); return RideStateMachine(db); });