Files
AR-Autopilot/display/lib/widgets/themed/heading_adjust_bar.dart
T
alro65 c946d2df6d feat(display): Sprint 4 — pantalla principal del autopilot
Archivos nuevos:
  display/lib/data/autopilot_state.dart
    - ChangeNotifier con todos los datos del autopilot
    - Simulación de velero en mar (heading drift / P-controller)
    - API pública estable: engage(), disengage(), adjustSetpoint(), selectMode()
    - Sprint 7: los internos se reemplazan por Modbus RTU, la API no cambia

  display/lib/screens/cockpit/cockpit_screen.dart
    - Pantalla principal: TopBar, ModeSelector, CompassRose, DataStrip,
      HeadingAdjustBar, RudderIndicator, ENGAGE/DISENGAGE
    - Logo con triple-tap para ciclar temas (StateWidget con Timer)
    - Indicador DEMO visible cuando isConnected == false
    - Engranaje → AppearanceSettingsScreen

  display/lib/widgets/themed/engage_button.dart
    - Botón verde con glow; dimmed cuando ya está engaged

  display/lib/widgets/themed/heading_adjust_bar.dart
    - Botones << < [SET 048.0°] > >>
    - Deshabilitado cuando mode == STANDBY

  display/lib/widgets/themed/status_chip.dart
    - Indicador de punto + label para NMEA / GPS (ok / warn / off)

Modificado:
  display/lib/main.dart
    - MultiProvider: agrega AutopilotState al árbol de providers
    - Ruta inicial: CockpitScreen.routeName ('/') en lugar de Appearance

AR_electronics — AR-Autopilot Project
2026-05-23 11:53:50 -04:00

189 lines
5.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../theme/autopilot_theme.dart';
import '../../theme/theme_provider.dart';
/// Four heading-adjust buttons around a central setpoint readout.
///
/// Layout (left → right):
/// [ << 10° ] [ < 1° ] SET 048.0° [ 1° > ] [ 10° >> ]
///
/// All buttons are disabled when [enabled] is false (autopilot not engaged).
/// The setpoint display dims when disabled to reinforce the inactive state.
///
/// [onAdjust] receives the signed delta in degrees (+1, 1, +10, 10).
class HeadingAdjustBar extends StatelessWidget {
const HeadingAdjustBar({
super.key,
required this.setpointDeg,
required this.enabled,
required this.onAdjust,
});
final double setpointDeg;
final bool enabled;
final ValueChanged<double> onAdjust;
@override
Widget build(BuildContext context) {
final theme = context.watch<AutopilotThemeProvider>().current;
return Row(
children: [
_AdjustButton(
theme: theme, label: '<<\n10°', delta: -10,
enabled: enabled, onAdjust: onAdjust),
const SizedBox(width: 6),
_AdjustButton(
theme: theme, label: '<\n1°', delta: -1,
enabled: enabled, onAdjust: onAdjust),
const SizedBox(width: 10),
Expanded(
child: _SetpointDisplay(
theme: theme,
setpointDeg: setpointDeg,
enabled: enabled,
),
),
const SizedBox(width: 10),
_AdjustButton(
theme: theme, label: '1°\n>', delta: 1,
enabled: enabled, onAdjust: onAdjust),
const SizedBox(width: 6),
_AdjustButton(
theme: theme, label: '10°\n>>', delta: 10,
enabled: enabled, onAdjust: onAdjust),
],
);
}
}
// ── Setpoint display ──────────────────────────────────────────────────────────
class _SetpointDisplay extends StatelessWidget {
const _SetpointDisplay({
required this.theme,
required this.setpointDeg,
required this.enabled,
});
final AutopilotTheme theme;
final double setpointDeg;
final bool enabled;
@override
Widget build(BuildContext context) {
return Container(
height: 52,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: theme.panelBackground,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: enabled
? theme.setLight.withValues(alpha: 0.5)
: theme.panelBorder,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'SET',
style: TextStyle(
color: theme.textMuted,
fontSize: 9,
letterSpacing: 1.2,
),
),
const SizedBox(height: 2),
Text(
'${setpointDeg.toStringAsFixed(1)}°',
style: TextStyle(
color: enabled ? theme.setLight : theme.textDisabled,
fontSize: 20,
fontWeight: FontWeight.w300,
fontFeatures: const [FontFeature.tabularFigures()],
shadows: enabled && theme.accentGlowRadius > 0
? [Shadow(color: theme.setGlow, blurRadius: 8)]
: null,
),
),
],
),
);
}
}
// ── Adjust button ─────────────────────────────────────────────────────────────
class _AdjustButton extends StatefulWidget {
const _AdjustButton({
required this.theme,
required this.label,
required this.delta,
required this.enabled,
required this.onAdjust,
});
final AutopilotTheme theme;
final String label;
final double delta;
final bool enabled;
final ValueChanged<double> onAdjust;
@override
State<_AdjustButton> createState() => _AdjustButtonState();
}
class _AdjustButtonState extends State<_AdjustButton> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
final t = widget.theme;
final enabled = widget.enabled;
return GestureDetector(
onTapDown: enabled ? (_) => setState(() => _pressed = true) : null,
onTapUp: enabled
? (_) {
setState(() => _pressed = false);
widget.onAdjust(widget.delta);
}
: null,
onTapCancel: () => setState(() => _pressed = false),
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
constraints: const BoxConstraints(minWidth: 48, minHeight: 52),
padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: enabled ? t.actionButtonBackground : null,
color: enabled ? null : t.backgroundDeep,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: enabled && !_pressed
? t.actionButtonBorder
: t.panelBorder,
),
boxShadow: enabled && !_pressed
? t.glowShadow(t.actionButtonGlow, t.accentGlowRadius / 2)
: [],
),
child: Text(
widget.label,
textAlign: TextAlign.center,
style: TextStyle(
color: enabled ? t.actionButtonText : t.textDisabled,
fontSize: 11,
fontWeight: FontWeight.w600,
height: 1.35,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
),
);
}
}