import 'dart:async'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../services/call_session.dart'; import '../widgets/status_indicator.dart'; import 'call_screen.dart'; import 'settings_screen.dart'; /// Home screen — pre-call setup and call initiation. /// /// Responsibilities: /// 1. Request RECORD_AUDIO permission on first launch. /// 2. Show current key status (fingerprint loaded / none). /// 3. Allow user to navigate to Settings to enter passphrase. /// 4. Start / end the secure call. class HomeScreen extends StatefulWidget { final CallSession session; const HomeScreen({super.key, required this.session}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { StreamSubscription? _stateSub; SessionState _state = SessionState.noKey; String? _fingerprint; bool _permGranted = false; @override void initState() { super.initState(); _state = widget.session.state; _stateSub = widget.session.onStateChange .listen((s) { if (mounted) setState(() => _state = s); }); _init(); } Future _init() async { await _checkPermission(); await _loadFingerprint(); } Future _checkPermission() async { final status = await Permission.microphone.status; if (status.isGranted) { if (mounted) setState(() => _permGranted = true); return; } final result = await Permission.microphone.request(); if (mounted) setState(() => _permGranted = result.isGranted); } Future _loadFingerprint() async { final fp = await widget.session.getSavedFingerprint(); if (mounted) setState(() => _fingerprint = fp); } Future _openSettings() async { await Navigator.of(context).push( MaterialPageRoute( builder: (_) => SettingsScreen(session: widget.session), ), ); // Refresh fingerprint after returning from settings. await _loadFingerprint(); } Future _startCall() async { if (!_permGranted) { await _checkPermission(); if (!_permGranted) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Microphone permission is required'), backgroundColor: Colors.redAccent, )); } return; } } if (!widget.session.hasKey) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Set a passphrase in Settings first'), backgroundColor: Colors.amber, )); } return; } await widget.session.startCall(); if (!mounted) return; await Navigator.of(context).push( MaterialPageRoute( builder: (_) => CallScreen(session: widget.session), ), ); } @override void dispose() { _stateSub?.cancel(); super.dispose(); } // ── Build ────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { final canCall = _permGranted && (_state == SessionState.keyLoaded || _state == SessionState.inCall); return Scaffold( backgroundColor: const Color(0xFF0D0D1A), appBar: AppBar( backgroundColor: const Color(0xFF0D0D1A), title: const Text('SecureCall', style: TextStyle(color: Colors.white, letterSpacing: 2, fontWeight: FontWeight.w300)), actions: [ IconButton( icon: const Icon(Icons.settings_outlined, color: Colors.white54), tooltip: 'Settings', onPressed: _openSettings, ), ], ), body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── Status bar ──────────────────────────────────────── Center(child: StatusIndicator(state: _state)), const SizedBox(height: 32), // ── Key fingerprint card ────────────────────────────── _KeyCard(fingerprint: _fingerprint, onTap: _openSettings), const SizedBox(height: 20), // ── Permission card ─────────────────────────────────── if (!_permGranted) _WarningCard( icon: Icons.mic_off, message: 'Microphone permission denied.\nTap to request again.', onTap: _checkPermission, ), const Spacer(), // ── How it works ────────────────────────────────────── _HowItWorksSection(), const SizedBox(height: 28), // ── Start call button ───────────────────────────────── ElevatedButton.icon( onPressed: canCall ? _startCall : null, icon: const Icon(Icons.lock_rounded, size: 20), label: const Text('START SECURE CALL', style: TextStyle( fontSize: 16, letterSpacing: 2, fontWeight: FontWeight.bold)), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF00C853), disabledBackgroundColor: const Color(0xFF1A3020), padding: const EdgeInsets.symmetric(vertical: 18), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), ), const SizedBox(height: 16), Center( child: Text( canCall ? 'First start a normal phone call, then press the button' : _fingerprint == null ? 'Set a passphrase in ⚙ Settings to enable' : 'Microphone permission required', textAlign: TextAlign.center, style: const TextStyle( color: Colors.white38, fontSize: 12, height: 1.5), ), ), const SizedBox(height: 12), ], ), ), ), ); } } // ── Sub-widgets ──────────────────────────────────────────────────────── class _KeyCard extends StatelessWidget { final String? fingerprint; final VoidCallback onTap; const _KeyCard({required this.fingerprint, required this.onTap}); @override Widget build(BuildContext context) { final hasKey = fingerprint != null; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF1A1A2E), borderRadius: BorderRadius.circular(12), border: Border.all( color: hasKey ? const Color(0xFF00C853).withValues(alpha: 0.5) : Colors.white12, ), ), child: Row( children: [ Icon( hasKey ? Icons.vpn_key_rounded : Icons.vpn_key_off_outlined, color: hasKey ? const Color(0xFF00C853) : Colors.white38, size: 28, ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( hasKey ? 'Encryption key loaded' : 'No encryption key', style: TextStyle( color: hasKey ? const Color(0xFF00C853) : Colors.white54, fontWeight: FontWeight.w600, ), ), if (hasKey) ...[ const SizedBox(height: 4), Text( 'Fingerprint: $fingerprint', style: const TextStyle( color: Colors.white38, fontSize: 12, fontFamily: 'monospace', letterSpacing: 2), ), ] else ...[ const SizedBox(height: 4), const Text('Tap to open Settings and set passphrase', style: TextStyle(color: Colors.white30, fontSize: 12)), ], ], ), ), const Icon(Icons.chevron_right, color: Colors.white24), ], ), ), ); } } class _WarningCard extends StatelessWidget { final IconData icon; final String message; final VoidCallback onTap; const _WarningCard( {required this.icon, required this.message, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.amber.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.amber.withValues(alpha: 0.4)), ), child: Row( children: [ Icon(icon, color: Colors.amber, size: 22), const SizedBox(width: 12), Expanded( child: Text(message, style: const TextStyle( color: Colors.amber, fontSize: 13, height: 1.4)), ), ], ), ), ); } } class _HowItWorksSection extends StatelessWidget { @override Widget build(BuildContext context) { const steps = [ (Icons.phone_in_talk_rounded, 'Start a normal cellular voice call with your peer'), (Icons.vpn_key_rounded, 'Both devices must have the same passphrase loaded'), (Icons.mic, 'Press PTT to speak — FSK audio transmits over the call'), (Icons.hearing, 'Tap LISTEN to receive and decode incoming voice'), ]; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF0F0F1E), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white10), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('HOW TO USE', style: TextStyle( color: Colors.white38, fontSize: 11, letterSpacing: 2)), const SizedBox(height: 12), ...steps.map((s) => Padding( padding: const EdgeInsets.only(bottom: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(s.$1, color: Colors.white38, size: 16), const SizedBox(width: 10), Expanded( child: Text(s.$2, style: const TextStyle( color: Colors.white54, fontSize: 13, height: 1.4))), ], ), )), ], ), ); } }