import 'package:flutter/material.dart'; import '../../../fly/fly_prefs.dart'; import '../../../fly/modes/intelligent_modes.dart'; import '../../../fly/product_profile.dart'; import '../../../theme.dart'; import '../../pv_icons.dart'; import '../hud_kit.dart'; /// The Intelligent Flight Modes picker (plan P7): a grid of the connected /// product's modes, grouped as GO 4 does. Resolves to the chosen mode once /// its first-use terms are accepted, or null. Future showModePicker(BuildContext context, {required ProductProfile profile}) async { final IntelligentMode? picked = await showGeneralDialog( context: context, barrierDismissible: true, barrierLabel: 'Intelligent Flight Modes', barrierColor: const Color(0x88050A14), transitionDuration: const Duration(milliseconds: 180), pageBuilder: (BuildContext context, _, __) => Center(child: ModePickerPanel(profile: profile)), transitionBuilder: (BuildContext context, Animation a, _, Widget child) => FadeTransition( opacity: a, child: ScaleTransition(scale: Tween(begin: 0.96, end: 1).animate(a), child: child), ), ); if (picked == null || !context.mounted) return null; final bool ok = await ensureModeTerms(context, ModeCatalog.of(picked)); return ok ? picked : null; } /// Shows the mode's terms the first time it is used; true once accepted. Future ensureModeTerms(BuildContext context, ModeInfo info) async { if (FlyPrefs.acceptedModeTerms.value.contains(info.mode.name)) return true; final bool? agreed = await showDialog( context: context, barrierColor: const Color(0x99050A14), builder: (BuildContext context) => ModeTermsDialog(info: info), ); if (agreed != true) return false; await FlyPrefs.acceptModeTerms(info.mode.name); return true; } class ModePickerPanel extends StatelessWidget { const ModePickerPanel({super.key, required this.profile}); final ProductProfile profile; @override Widget build(BuildContext context) { final List modes = ModeCatalog.forProfile(profile); final Size screen = MediaQuery.sizeOf(context); return Material( color: const Color(0xF20B1426), borderRadius: BorderRadius.circular(16), clipBehavior: Clip.antiAlias, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 620, maxHeight: screen.height - 32), child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.fromLTRB(18, 10, 6, 6), child: Row(children: [ const PVIcon('master', size: 16, color: Glass.ink), const SizedBox(width: 8), Expanded(child: Text('Intelligent Flight Modes', style: HudText.sans.copyWith(fontSize: 14, fontWeight: FontWeight.w700))), IconButton( key: const ValueKey('mode-picker-close'), onPressed: () => Navigator.of(context).pop(), icon: const PVIcon('close', size: 18, color: HudText.dim), ), ]), ), const Divider(height: 1, color: Glass.hairline), Flexible( child: modes.isEmpty ? Padding( padding: const EdgeInsets.all(24), child: Text('This aircraft offers no intelligent flight modes through the SDK.', style: HudText.sans.copyWith(color: HudText.dim, fontWeight: FontWeight.w400)), ) : ListView( shrinkWrap: true, padding: const EdgeInsets.fromLTRB(16, 10, 16, 16), children: [ for (final String g in ModeCatalog.groups) if (modes.any((ModeInfo m) => m.group == g)) ...[ Padding( padding: const EdgeInsets.only(top: 6, bottom: 8), child: Text(g.toUpperCase(), style: HudText.label), ), Wrap(spacing: 10, runSpacing: 10, children: [ for (final ModeInfo m in modes) if (m.group == g) _ModeTile(info: m), ]), ], ], ), ), ]), ), ); } } class _ModeTile extends StatelessWidget { const _ModeTile({required this.info}); final ModeInfo info; @override Widget build(BuildContext context) { final bool on = info.built; return GestureDetector( key: ValueKey('mode-${info.mode.name}'), behavior: HitTestBehavior.opaque, onTap: on ? () => Navigator.of(context).pop(info.mode) : null, child: Opacity( opacity: on ? 1 : 0.5, child: Container( width: 136, height: 92, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: on ? const Color(0x263D7BF0) : const Color(0x14FFFFFF), borderRadius: BorderRadius.circular(12), border: Border.all(color: on ? const Color(0x663D7BF0) : Glass.hairline), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ PVIcon(info.icon, size: 18, color: Glass.ink), const Spacer(), if (!on) Text('SOON', style: HudText.label.copyWith(fontSize: 7.5)), ]), const SizedBox(height: 6), Text(info.label, style: HudText.sans.copyWith(fontSize: 12.5, fontWeight: FontWeight.w700)), const SizedBox(height: 3), Expanded( child: Text(info.blurb, maxLines: 2, overflow: TextOverflow.ellipsis, style: HudText.sans.copyWith(fontSize: 10, fontWeight: FontWeight.w400, color: HudText.dim)), ), ]), ), ), ); } } /// GO 4's "Intelligent Flight Mode Terms": Agree / Disagree, once per mode. class ModeTermsDialog extends StatelessWidget { const ModeTermsDialog({super.key, required this.info}); final ModeInfo info; @override Widget build(BuildContext context) { return Dialog( backgroundColor: const Color(0xF20B1426), insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 12), child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row(children: [ PVIcon(info.icon, size: 18, color: Glass.ink), const SizedBox(width: 8), Expanded( child: Text('${info.label}: before you fly', style: HudText.sans.copyWith(fontSize: 14, fontWeight: FontWeight.w700))), ]), const SizedBox(height: 10), Flexible( child: SingleChildScrollView( child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ for (final String t in info.terms) Padding( padding: const EdgeInsets.only(bottom: 8), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 5, height: 5, margin: const EdgeInsets.only(top: 5), decoration: const BoxDecoration(color: HudText.dim, shape: BoxShape.circle), ), const SizedBox(width: 8), Expanded(child: Text(t, style: HudText.sans.copyWith(fontSize: 11.5, fontWeight: FontWeight.w400, height: 1.35))), ]), ), ]), ), ), const SizedBox(height: 6), Row(mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( key: const ValueKey('terms-disagree'), onPressed: () => Navigator.of(context).pop(false), child: Text('Disagree', style: HudText.sans.copyWith(color: HudText.dim)), ), const SizedBox(width: 8), FilledButton( key: const ValueKey('terms-agree'), style: FilledButton.styleFrom(backgroundColor: PV.accent), onPressed: () => Navigator.of(context).pop(true), child: Text('Agree', style: HudText.sans.copyWith(color: Colors.white)), ), ]), ]), ), ), ); } }