import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../database/database.dart'; import '../../models/motorcycle_models.dart'; import '../../services/motorcycle/motorcycle_repository.dart'; final motorcycleRepositoryProvider = Provider((ref) { return HybridMotorcycleRepository(); }); final garageMotorcyclesStreamProvider = StreamProvider>((ref) { final db = ref.watch(databaseProvider); return (db.select(db.garageMotorcycles) ..orderBy([(t) => OrderingTerm(expression: t.dateAdded, mode: OrderingMode.desc)])) .watch(); }); final defaultMotorcycleProvider = Provider((ref) { final bikesAsync = ref.watch(garageMotorcyclesStreamProvider); final bikes = bikesAsync.value ?? []; try { return bikes.firstWhere((b) => b.isDefault); } catch (_) { return bikes.isNotEmpty ? bikes.first : null; } }); class MotorcycleStats { final int rideCount; final double totalDistanceMiles; final double topSpeedMph; final double? best0to60Ms; final double? best0to100Ms; final double maxLean; final double maxAccelG; final double maxBrakingG; MotorcycleStats({ required this.rideCount, required this.totalDistanceMiles, required this.topSpeedMph, this.best0to60Ms, this.best0to100Ms, required this.maxLean, required this.maxAccelG, required this.maxBrakingG, }); } final motorcyclePerformanceStatsProvider = FutureProvider.family((ref, motorcycleId) async { final db = ref.watch(databaseProvider); final rides = await (db.select(db.rides)..where((t) => t.motorcycleId.equals(motorcycleId))).get(); if (rides.isEmpty) { return MotorcycleStats( rideCount: 0, totalDistanceMiles: 0.0, topSpeedMph: 0.0, maxLean: 0.0, maxAccelG: 0.0, maxBrakingG: 0.0, ); } int count = rides.length; double totalDistance = 0.0; double topSpeed = 0.0; double? best0to60; double? best0to100; double maxLean = 0.0; double maxAccel = 0.0; double maxBraking = 0.0; for (var r in rides) { totalDistance += r.distanceMiles; if (r.topSpeedMph > topSpeed) topSpeed = r.topSpeedMph; final lean = r.maxLeanLeft > r.maxLeanRight ? r.maxLeanLeft : r.maxLeanRight; if (lean > maxLean) maxLean = lean; if (r.maxAccelG > maxAccel) maxAccel = r.maxAccelG; if (r.maxBrakingG > maxBraking) maxBraking = r.maxBrakingG; // Legacy fields ignored; we rely on the authoritative PerformanceRuns table below } final rideIds = rides.map((r) => r.id).toList(); if (rideIds.isNotEmpty) { final runs = await (db.select(db.performanceRuns)..where((t) => t.rideId.isIn(rideIds))).get(); for (var run in runs) { if (run.type == '0-60') { if (best0to60 == null || run.timeMs < best0to60!) best0to60 = run.timeMs; } else if (run.type == '0-100') { if (best0to100 == null || run.timeMs < best0to100!) best0to100 = run.timeMs; } } } return MotorcycleStats( rideCount: count, totalDistanceMiles: totalDistance, topSpeedMph: topSpeed, best0to60Ms: best0to60, best0to100Ms: best0to100, maxLean: maxLean, maxAccelG: maxAccel, maxBrakingG: maxBraking, ); }); class GarageController { final AppDatabase _db; final MotorcycleRepository _repository; GarageController(this._db, this._repository); Future addMotorcycle(MotorcycleSelection selection, {String? nickname}) async { final spec = await _repository.getSpecifications(selection); final specJson = spec != null ? json.encode(spec.toJson()) : null; // Check existing default count final existingBikes = await _db.select(_db.garageMotorcycles).get(); final isFirst = existingBikes.isEmpty; return await _db.into(_db.garageMotorcycles).insert( GarageMotorcyclesCompanion.insert( year: selection.year, makeId: selection.makeId, makeName: selection.makeName, modelId: selection.modelId, modelName: selection.modelName, variantId: Value(selection.variantId), variantName: Value(selection.variantName), nickname: Value(nickname), isDefault: Value(isFirst), dateAdded: DateTime.now(), lastUpdated: DateTime.now(), specificationJson: Value(specJson), ), ); } Future setDefaultMotorcycle(int id) async { await (_db.update(_db.garageMotorcycles)..where((t) => t.id.equals(id).not())) .write(const GarageMotorcyclesCompanion(isDefault: Value(false))); await (_db.update(_db.garageMotorcycles)..where((t) => t.id.equals(id))) .write(const GarageMotorcyclesCompanion(isDefault: Value(true))); } Future updateNickname(int id, String? nickname) async { await (_db.update(_db.garageMotorcycles)..where((t) => t.id.equals(id))) .write(GarageMotorcyclesCompanion(nickname: Value(nickname))); } Future deleteMotorcycle(int id) async { await (_db.delete(_db.garageMotorcycles)..where((t) => t.id.equals(id))).go(); // If default was deleted, set next bike as default if available final remaining = await _db.select(_db.garageMotorcycles).get(); if (remaining.isNotEmpty && !remaining.any((b) => b.isDefault)) { await setDefaultMotorcycle(remaining.first.id); } } } final garageControllerProvider = Provider((ref) { final db = ref.watch(databaseProvider); final repo = ref.watch(motorcycleRepositoryProvider); return GarageController(db, repo); });