import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:math' as math; import 'package:intl/intl.dart'; import '../../database/database.dart'; import 'package:drift/drift.dart' as drift; import '../../core/responsive/responsive_layout.dart'; import '../../core/theme/theme_extensions.dart'; import '../garage/garage_providers.dart'; import 'ride_detail_screen.dart'; class HistoryScreen extends ConsumerStatefulWidget { final bool isEmbedded; const HistoryScreen({ super.key, this.isEmbedded = false, }); @override ConsumerState createState() => _HistoryScreenState(); } class _HistoryScreenState extends ConsumerState { int? _selectedMotorcycleId; // null = All Motorcycles, -1 = Current, else specific ID @override Widget build(BuildContext context) { final database = ref.watch(databaseProvider); final garageBikesAsync = ref.watch(garageMotorcyclesStreamProvider); final defaultBike = ref.watch(defaultMotorcycleProvider); final colors = context.motorColors; return Scaffold( appBar: widget.isEmbedded ? null : AppBar( title: const Text( 'Ride History', style: TextStyle(fontWeight: FontWeight.bold), ), ), body: Column( children: [ _buildFilterBar(context, garageBikesAsync.value ?? [], defaultBike), Expanded( child: StreamBuilder>( stream: (database.select(database.rides) ..orderBy([(t) => drift.OrderingTerm(expression: t.id, mode: drift.OrderingMode.desc)])) .watch(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } var rides = List.from(snapshot.data ?? []); // Normalize legacy milliseconds timestamps vs Drift seconds timestamps DateTime normalizeEpoch(DateTime dt) { if (dt.year > 3000) { return DateTime.fromMillisecondsSinceEpoch( dt.millisecondsSinceEpoch ~/ 1000, isUtc: true, ); } return dt.toUtc(); } // Strict descending chronological sort (newest rides first, tie-break on id) rides.sort((a, b) { final timeA = normalizeEpoch(a.startTime); final timeB = normalizeEpoch(b.startTime); final cmp = timeB.compareTo(timeA); if (cmp != 0) return cmp; return b.id.compareTo(a.id); }); // Filter logic: // null = All Motorcycles (show all rides, including rides with no motorcycle) // -1 = Current default motorcycle // > 0 = Specific motorcycle ID if (_selectedMotorcycleId == -1 && defaultBike != null) { rides = rides .where((r) => r.motorcycleId == defaultBike.id) .toList(); } else if (_selectedMotorcycleId != null && _selectedMotorcycleId! > 0) { rides = rides .where((r) => r.motorcycleId == _selectedMotorcycleId) .toList(); } if (rides.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.history, size: 64, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.3), ), const SizedBox(height: 16), Text( 'No recorded rides matching filter.', style: TextStyle( fontSize: 16, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), ], ), ); } return ResponsiveLayout( builder: (context, responsive) { final isMultiColumn = responsive.isExpanded || (responsive.isMedium && responsive.isLandscape); if (isMultiColumn) { return GridView.builder( padding: const EdgeInsets.all(12), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 600, mainAxisExtent: 196, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: rides.length, itemBuilder: (context, index) { return _buildRideCard(context, rides[index], colors, isGrid: true); }, ); } return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), itemCount: rides.length, itemBuilder: (context, index) { return _buildRideCard(context, rides[index], colors); }, ); }, ); }, ), ), ], ), ); } Widget _buildRideCard( BuildContext context, Ride ride, MotorSignalThemeColors colors, { bool isGrid = false, }) { final bikeName = (ride.motorcycleYear != null && ride.motorcycleMake != null && ride.motorcycleModel != null) ? '${ride.motorcycleYear} ${ride.motorcycleMake} ${ride.motorcycleModel}' : null; final theme = Theme.of(context); final normalizedDate = ride.startTime.year > 3000 ? DateTime.fromMillisecondsSinceEpoch(ride.startTime.millisecondsSinceEpoch ~/ 1000) : ride.startTime; final dateFormatted = DateFormat('yyyy-MM-dd HH:mm').format(normalizedDate.toLocal()); return Card( elevation: 2, margin: isGrid ? EdgeInsets.zero : const EdgeInsets.symmetric(vertical: 6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: colors.outlineVariant), ), color: colors.surfaceContainer, child: InkWell( key: ValueKey('history_ride_card_${ride.id}'), borderRadius: BorderRadius.circular(12), onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => RideDetailScreen(ride: ride), ), ); }, child: Padding( padding: const EdgeInsets.all(14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 'Ride ${ride.id}', style: TextStyle( fontWeight: FontWeight.w900, fontSize: 16, fontFamily: 'Sora', color: theme.colorScheme.onSurface, ), ), if (bikeName != null) ...[ const SizedBox(width: 6), Expanded( child: Container( padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 2, ), decoration: BoxDecoration( color: colors.cyberCyan.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), border: Border.all( color: colors.cyberCyan.withValues(alpha: 0.4), ), ), child: Text( bikeName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 10.5, fontWeight: FontWeight.bold, color: colors.cyberCyan, ), ), ), ), ] else ...[ const Spacer(), ], const SizedBox(width: 6), Flexible( child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerRight, child: Text( dateFormatted, style: TextStyle( color: theme.colorScheme.onSurface.withValues(alpha: 0.5), fontSize: 10.5, fontFamily: 'JetBrains Mono', ), ), ), ), ], ), const SizedBox(height: 8), Divider(height: 1, color: colors.outlineVariant), const SizedBox(height: 10), Row( children: [ Expanded(child: _buildStatCol(context, 'TRIP', '${ride.distanceMiles.toStringAsFixed(1)} mi')), Expanded(child: _buildStatCol(context, 'TIME', '${(ride.movingTime / 60).toStringAsFixed(0)} min')), Expanded(child: _buildStatCol(context, 'TOP SPD', '${ride.topSpeedMph.toStringAsFixed(0)} mph')), ], ), const SizedBox(height: 8), Row( children: [ Expanded( child: _buildStatCol( context, 'MAX LEAN', '${math.max(ride.maxLeanLeft, ride.maxLeanRight).toStringAsFixed(0)}°', valueColor: colors.cyberCyan, ), ), Expanded( child: _buildStatCol( context, 'HARD BRK', '${ride.maxBrakingG.toStringAsFixed(2)}G', valueColor: colors.gForceNegative, ), ), Expanded( child: _buildStatCol( context, 'ELEV GAIN', '${ride.elevationGain.toStringAsFixed(0)}m', ), ), ], ), ], ), ), ), ); } Widget _buildFilterBar( BuildContext context, List bikes, GarageMotorcycle? defaultBike, ) { if (bikes.isEmpty) return const SizedBox.shrink(); final colors = context.motorColors; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row( children: [ Icon(Icons.filter_alt, color: colors.cyberCyan, size: 20), const SizedBox(width: 8), Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), border: Border.all(color: colors.outlineVariant), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: _selectedMotorcycleId, isExpanded: true, dropdownColor: colors.surfaceContainerHigh, hint: const Text('Filter by Motorcycle'), items: [ const DropdownMenuItem( value: null, child: Text('All Motorcycles', overflow: TextOverflow.ellipsis), ), if (defaultBike != null) DropdownMenuItem( value: -1, child: Text('Current (${defaultBike.modelName})', overflow: TextOverflow.ellipsis), ), ...bikes.map( (b) => DropdownMenuItem( value: b.id, child: Text('${b.year} ${b.makeName} ${b.modelName}', overflow: TextOverflow.ellipsis), ), ), ], onChanged: (val) { setState(() { _selectedMotorcycleId = val; }); }, ), ), ), ), ], ), ); } Widget _buildStatCol(BuildContext context, String label, String val, {Color? valueColor}) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 9.5, fontWeight: FontWeight.w700, letterSpacing: 0.5, color: theme.colorScheme.onSurface.withValues(alpha: 0.5), ), ), const SizedBox(height: 2), FittedBox( fit: BoxFit.scaleDown, child: Text( val, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, fontFamily: 'JetBrains Mono', color: valueColor ?? theme.colorScheme.onSurface, ), ), ), ], ); } }