Files
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

96 lines
3.0 KiB
Dart
Raw Permalink 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';
/// ENGAGE button — activates heading-hold at the current vessel heading.
///
/// Shown enabled (green, glowing) only when autopilot is in STANDBY.
/// When already engaged, [enabled] is false and the button dims.
///
/// Minimum touch target: 60×60 px (critical control, see design invariant §6).
class EngageButton extends StatefulWidget {
const EngageButton({
super.key,
required this.onPressed,
this.enabled = true,
});
final VoidCallback? onPressed;
final bool enabled;
@override
State<EngageButton> createState() => _EngageButtonState();
}
class _EngageButtonState extends State<EngageButton> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
final theme = context.watch<AutopilotThemeProvider>().current;
final enabled = widget.enabled && widget.onPressed != null;
return GestureDetector(
onTapDown: enabled ? (_) => setState(() => _pressed = true) : null,
onTapUp: enabled
? (_) {
setState(() => _pressed = false);
widget.onPressed!();
}
: null,
onTapCancel: () => setState(() => _pressed = false),
child: AnimatedContainer(
duration: const Duration(milliseconds: 120),
constraints: const BoxConstraints(minWidth: 60, minHeight: 60),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
gradient: enabled
? LinearGradient(
colors: [
theme.okColor.withValues(alpha: 0.65),
theme.okColor.withValues(alpha: 0.35),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: LinearGradient(
colors: [theme.panelBorder, theme.panelBorder],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: enabled ? theme.okColor : theme.panelBorder,
width: 1.5,
),
boxShadow: enabled
? [
BoxShadow(
color: theme.okColor
.withValues(alpha: _pressed ? 0.2 : 0.45),
blurRadius: _pressed ? 4 : 14,
spreadRadius: _pressed ? 0 : 2,
),
]
: [],
),
child: Text(
'ENGAGE',
textAlign: TextAlign.center,
style: TextStyle(
color: enabled ? Colors.white : theme.textDisabled,
fontSize: 13,
fontWeight: FontWeight.w800,
letterSpacing: 1.2,
shadows: enabled
? [Shadow(color: theme.okColor, blurRadius: 4)]
: null,
),
),
),
);
}
}