MotorSignal-App/lib/features/dashboard/dashboard_screen.dart

185 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../services/location_service.dart';
import '../../services/sensor_fusion_service.dart';
import '../../services/compass_service.dart';
import '../rides/ride_state_machine.dart';
class DashboardScreen extends ConsumerWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final locationAsync = ref.watch(locationStreamProvider);
final leanAngleAsync = ref.watch(leanAngleStreamProvider);
final gForceAsync = ref.watch(gForceStreamProvider);
final headingAsync = ref.watch(headingStreamProvider);
final rideSession = ref.watch(rideStateMachineProvider);
// Speed calculation
double currentSpeedMph = 0;
if (locationAsync.hasValue && locationAsync.value != null) {
currentSpeedMph = locationAsync.value!.speed * 2.23694;
// We also update the state machine telemetry here for simplicity
// In a real prod app, you might have a dedicated telemetry aggregator service
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(rideStateMachineProvider.notifier).updateTelemetry(
position: locationAsync.value,
speedMph: currentSpeedMph,
);
});
}
double leanAngle = leanAngleAsync.value ?? 0.0;
GForceData? gForce = gForceAsync.value;
double heading = headingAsync.value ?? 0.0;
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: LayoutBuilder(
builder: (context, constraints) {
final isLandscape = constraints.maxWidth > constraints.maxHeight;
if (isLandscape) {
return _buildLandscapeLayout(context, currentSpeedMph, heading, leanAngle, gForce, rideSession);
}
return _buildPortraitLayout(context, currentSpeedMph, heading, leanAngle, gForce, rideSession);
},
),
),
),
);
}
Widget _buildPortraitLayout(BuildContext context, double speed, double heading, double lean, GForceData? gForce, RideSession session) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCompassHeader(context, heading),
const Spacer(),
_buildSpeedometer(context, speed),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildDataBox(context, 'LEAN', '${lean.abs().toStringAsFixed(0)}° ${lean > 0 ? 'R' : 'L'}'),
_buildDataBox(context, 'G-FORCE', '${gForce?.longitudinal.abs().toStringAsFixed(2) ?? '0.00'} G'),
_buildDataBox(context, 'TRIP', '${session.tripDistanceMiles.toStringAsFixed(2)} mi'),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildDataBox(context, '0-60', '-- s'), // Will connect to performance provider later
_buildDataBox(context, 'TOP SPEED', '${session.topSpeedMph.toStringAsFixed(0)} MPH'),
_buildDataBox(context, 'STATUS', session.state.name.toUpperCase()),
],
),
const SizedBox(height: 32),
_buildActionButtons(context, session),
],
);
}
Widget _buildLandscapeLayout(BuildContext context, double speed, double heading, double lean, GForceData? gForce, RideSession session) {
return Row(
children: [
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildSpeedometer(context, speed),
_buildActionButtons(context, session),
],
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCompassHeader(context, heading),
_buildDataBox(context, 'LEAN', '${lean.abs().toStringAsFixed(0)}° ${lean > 0 ? 'R' : 'L'}'),
_buildDataBox(context, 'G-FORCE', '${gForce?.longitudinal.abs().toStringAsFixed(2) ?? '0.00'} G'),
_buildDataBox(context, 'TRIP', '${session.tripDistanceMiles.toStringAsFixed(2)} mi'),
],
),
),
],
);
}
Widget _buildCompassHeader(BuildContext context, double heading) {
final compassService = CompassService(); // Just using for string conversion here
final headingStr = compassService.getHeadingString(heading);
return Column(
children: [
Text(headingStr, style: Theme.of(context).textTheme.headlineLarge),
Text('${heading.toStringAsFixed(0)}°', style: Theme.of(context).textTheme.labelLarge),
],
);
}
Widget _buildSpeedometer(BuildContext context, double speed) {
return Column(
children: [
Text(
speed.toStringAsFixed(0),
style: Theme.of(context).textTheme.displayLarge,
textAlign: TextAlign.center,
),
Text(
'MPH',
style: Theme.of(context).textTheme.labelLarge,
),
],
);
}
Widget _buildDataBox(BuildContext context, String label, String value) {
return Expanded(
child: Column(
children: [
Text(label, style: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 16)),
const SizedBox(height: 4),
Text(value, style: Theme.of(context).textTheme.bodyLarge),
],
),
);
}
Widget _buildActionButtons(BuildContext context, RideSession session) {
return Consumer(
builder: (context, ref, child) {
final notifier = ref.read(rideStateMachineProvider.notifier);
if (session.state == RideState.idle || session.state == RideState.finished) {
return ElevatedButton(
onPressed: () => notifier.startRide(),
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Text('START RIDE', style: TextStyle(fontSize: 24)),
),
);
} else if (session.state == RideState.riding) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () => notifier.finishRide(),
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Text('END RIDE', style: TextStyle(fontSize: 24)),
),
);
}
return const SizedBox.shrink();
},
);
}
}