import 'package:flutter/material.dart'; import '../flight_model.dart'; import '../theme.dart'; import '../uploader.dart'; /// Portrait launch screen — mirrors the ui_kit/fly "Go Fly · launch" mockup: /// brand header, aircraft connection card, big GO FLY, and a 2×2 tile grid. class GoFlyPage extends StatelessWidget { const GoFlyPage({ super.key, required this.model, required this.onGoFly, required this.onOpenAlbum, required this.onSettings, required this.onTile, }); final FlightModel model; final VoidCallback onGoFly; final VoidCallback onOpenAlbum; final VoidCallback onSettings; final void Function(String tile) onTile; @override Widget build(BuildContext context) { final PVScheme s = PVScheme.of(context); return Scaffold( backgroundColor: s.bgApp, body: SafeArea( child: AnimatedBuilder( animation: model, builder: (BuildContext context, _) { return Column( children: [ _header(s), Expanded( child: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: _connectionCard(s), ), ), ), Padding( // No bottom gap here: the design pins GO FLY directly above the // tiles' 18px top pad, which centers the card 4px lower to match. padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), child: _goFlyButton(s), ), Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 28), child: _tiles(s), ), ], ); }, ), ), ); } Widget _header(PVScheme s) { return Padding( padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), child: Row( children: [ const PvBrandMark(size: 22), const SizedBox(width: 8), Text.rich( TextSpan(children: [ TextSpan( text: 'Pilot', style: TextStyle(fontFamily: PV.fontSans, fontSize: 19, fontWeight: FontWeight.w500, letterSpacing: -0.38, color: s.textSecondary), ), TextSpan( text: 'Vault', style: TextStyle(fontFamily: PV.fontSans, fontSize: 19, fontWeight: FontWeight.w700, letterSpacing: -0.38, color: s.textPrimary), ), TextSpan( text: ' Fly', style: TextStyle(fontFamily: PV.fontSans, fontSize: 19, fontWeight: FontWeight.w500, letterSpacing: -0.38, color: s.accent), ), ]), ), const Spacer(), GestureDetector( onTap: onSettings, child: Container( width: 32, height: 32, decoration: BoxDecoration(color: s.surfaceInset, shape: BoxShape.circle), child: Icon(Icons.person_outline, size: 17, color: s.textSecondary), ), ), ], ), ); } Widget _connectionCard(PVScheme s) { final bool connected = model.connected; final Color dot = connected ? s.success : s.textTertiary; final Color statusFg = connected ? s.successFg : s.textTertiary; final String statusText = connected ? 'CONNECTED' : 'DISCONNECTED'; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: s.surface, borderRadius: BorderRadius.circular(18), border: Border.all(color: s.border), boxShadow: s.shadowSm, ), child: Column( mainAxisSize: MainAxisSize.min, // size to content; Center handles vertical placement crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container(width: 7, height: 7, decoration: BoxDecoration(color: dot, shape: BoxShape.circle)), const SizedBox(width: 6), Text( statusText, style: TextStyle(fontFamily: PV.fontMono, fontSize: 11, letterSpacing: 1.1, fontWeight: FontWeight.w700, color: statusFg), ), ], ), const SizedBox(height: 14), Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration(color: s.accentSoft, borderRadius: BorderRadius.circular(14)), child: Icon(Icons.flight, size: 28, color: s.accent), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( connected ? (model.model ?? 'Aircraft') : 'No aircraft', style: TextStyle(fontFamily: PV.fontSans, fontSize: 18, fontWeight: FontWeight.w700, color: s.textPrimary), ), const SizedBox(height: 2), Text( 'MSDK · ${model.sdkVersion}', style: TextStyle(fontFamily: PV.fontMono, fontSize: 12, color: s.textSecondary), ), ], ), ), ], ), const SizedBox(height: 14), Row( children: [ _chip(s, Icons.battery_full, model.batteryPercent == null ? '—' : '${model.batteryPercent}%'), const SizedBox(width: 8), _chip(s, Icons.satellite_alt, model.satellites == null ? '— sats' : '${model.satellites} sats'), const SizedBox(width: 8), _chip(s, Icons.link, _linkLabel(model.upload, connected)), ], ), ], ), ); } String _linkLabel(UploadStatus u, bool connected) { switch (u) { case UploadStatus.connected: return 'Streaming'; case UploadStatus.connecting: return 'Linking…'; case UploadStatus.error: return 'Retrying'; case UploadStatus.disabled: return connected ? 'Linked' : 'Off'; } } Widget _chip(PVScheme s, IconData icon, String value) { return Expanded( child: Container( height: 34, decoration: BoxDecoration(color: s.surfaceInset, borderRadius: BorderRadius.circular(9)), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 14, color: s.textTertiary), const SizedBox(width: 6), Flexible( child: Text( value, overflow: TextOverflow.ellipsis, style: TextStyle(fontFamily: PV.fontMono, fontSize: 11.5, color: s.textSecondary), ), ), ], ), ), ); } Widget _goFlyButton(PVScheme s) { return SizedBox( height: 58, child: FilledButton( onPressed: onGoFly, style: FilledButton.styleFrom( backgroundColor: s.accent, foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon(Icons.play_arrow_rounded, size: 22), SizedBox(width: 10), Text('GO FLY', style: TextStyle(fontFamily: PV.fontSans, fontSize: 18, fontWeight: FontWeight.w700, letterSpacing: 0.4)), ], ), ), ); } Widget _tiles(PVScheme s) { const List<(IconData, String)> tiles = <(IconData, String)>[ (Icons.photo_library_outlined, 'Album'), (Icons.school_outlined, 'Academy'), (Icons.route_outlined, 'Routes'), (Icons.speed, 'Flight logs'), ]; return Column( children: [ Row(children: [ Expanded(child: _tile(s, tiles[0])), const SizedBox(width: 12), Expanded(child: _tile(s, tiles[1])), ]), const SizedBox(height: 12), Row(children: [ Expanded(child: _tile(s, tiles[2])), const SizedBox(width: 12), Expanded(child: _tile(s, tiles[3])), ]), ], ); } Widget _tile(PVScheme s, (IconData, String) t) { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: () => t.$2 == 'Album' ? onOpenAlbum() : onTile(t.$2), child: Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( color: s.surface, borderRadius: BorderRadius.circular(14), border: Border.all(color: s.border), boxShadow: s.shadowXs, ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration(color: s.accentSoft, borderRadius: BorderRadius.circular(9)), child: Icon(t.$1, size: 17, color: s.accentSoftFg), ), const SizedBox(width: 10), Flexible( child: Text( t.$2, overflow: TextOverflow.ellipsis, style: TextStyle(fontFamily: PV.fontSans, fontSize: 14, fontWeight: FontWeight.w600, color: s.textPrimary), ), ), ], ), ), ), ); } }