import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../core/core.dart'; import 'voice_session_controller.dart'; class VoiceModeOverlay extends StatefulWidget { final CharacterModel character; final VoiceSessionController controller; final VoidCallback onClose; const VoiceModeOverlay({ super.key, required this.character, required this.controller, required this.onClose, }); @override State createState() => _VoiceModeOverlayState(); } class _VoiceModeOverlayState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; String get _avatarUrl => CharacterRepository.getAvatarUrl(widget.character.avatarPath); @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2)) ..repeat(reverse: true); // Listen to controller changes to update UI widget.controller.addListener(_onStateChange); } void _onStateChange() { if (mounted) setState(() {}); } @override void dispose() { widget.controller.removeListener(_onStateChange); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Positioned.fill( child: Material( type: MaterialType.transparency, child: Container( color: const Color(0xFF2E1065), child: Stack( children: [ // Background Image with Blur Positioned.fill( child: Image.network( _avatarUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container(color: const Color(0xFF2E1065)); }, ), ), Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( color: const Color(0xFF2E1065).withOpacity(0.8), ), ), ), // Main Content SafeArea( child: Column( children: [ // Header Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: widget.onClose, icon: const Icon(LucideIcons.chevronLeft, color: Colors.white70), style: IconButton.styleFrom( backgroundColor: Colors.white.withOpacity(0.1), ), ), ], ), ), const Spacer(), // Character Info & Status Column( children: [ Text( widget.character.name, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( _getStatusText(), style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 12, letterSpacing: 2, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ], ), const SizedBox(height: 48), // Avatar pulsing animation SizedBox( width: 200, height: 200, child: Stack( alignment: Alignment.center, children: [ // Show animation only when listening or speaking if (widget.controller.state == VoiceState.listening || widget.controller.state == VoiceState.speaking) AnimatedBuilder( animation: _controller, builder: (context, child) { double scale = 1.0; if (widget.controller.state == VoiceState.speaking) { // Faster pulse when AI is speaking scale = 0.8 + 0.3 * _controller.value; } else { // Slower pulse when listening scale = 0.8 + 0.1 * _controller.value; } return Container( width: 200 * scale, height: 200 * scale, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white.withOpacity(0.2 * (1 - _controller.value)), width: 1, ), ), ); }, ), Container( width: 160, height: 160, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.3), width: 2), boxShadow: [ BoxShadow( color: const Color(0xFFC084FC).withOpacity(0.5), blurRadius: 30, spreadRadius: 0 ) ], image: DecorationImage( image: NetworkImage(_avatarUrl), fit: BoxFit.cover, ), ), ), ], ), ), const Spacer(), // Waveform (Simulated) SizedBox( height: 32, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: List.generate(5, (index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2.0), child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Container( width: 4, height: widget.controller.state == VoiceState.speaking ? 10 + (30 * (index % 2 == 0 ? _controller.value : 1 - _controller.value)) // Active wave : widget.controller.state == VoiceState.processing ? 8 + (5 * (index % 2 == 0 ? _controller.value : 1 - _controller.value)) // Thinking wave : 4, // Idle decoration: BoxDecoration( color: Colors.white.withOpacity(widget.controller.state != VoiceState.listening ? 0.8 : 0.4), borderRadius: BorderRadius.circular(2), ), ); } ), ); }), ), ), const SizedBox(height: 48), // Bottom Controls Padding( padding: const EdgeInsets.symmetric(horizontal: 48.0, vertical: 32.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Mic Toggle IconButton( onPressed: widget.controller.toggleMic, icon: Icon(widget.controller.isMicMuted ? LucideIcons.micOff : LucideIcons.mic), iconSize: 24, style: IconButton.styleFrom( backgroundColor: widget.controller.isMicMuted ? Colors.white : Colors.white.withOpacity(0.1), foregroundColor: widget.controller.isMicMuted ? const Color(0xFF2E1065) : Colors.white, padding: const EdgeInsets.all(16), minimumSize: const Size(64, 64), ), ), // End Call IconButton( onPressed: widget.onClose, icon: const Icon(LucideIcons.phoneOff), iconSize: 32, style: IconButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.all(20), minimumSize: const Size(80, 80), ), ), // Speaker Toggle - Placeholder for now IconButton( onPressed: () {}, // TODO: Implement speaker toggle in controller icon: const Icon(LucideIcons.volume2), iconSize: 24, style: IconButton.styleFrom( backgroundColor: Colors.white.withOpacity(0.1), foregroundColor: Colors.white, padding: const EdgeInsets.all(16), minimumSize: const Size(64, 64), ), ), ], ), ), ], ), ), ], ), ), ), ); } String _getStatusText() { if (widget.controller.isMicMuted) return 'Microphone Muted'; switch (widget.controller.state) { case VoiceState.listening: if (widget.controller.recognizedText.isNotEmpty) { // Show last few words of what user said String text = widget.controller.recognizedText; if (text.length > 20) text = '...${text.substring(text.length - 20)}'; return text; } return 'Listening...'; case VoiceState.processing: return 'Thinking...'; case VoiceState.speaking: return 'Speaking...'; case VoiceState.error: return 'Error'; } } }