import 'package:flutter/material.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/Services/meta_glasses_service.dart'; import 'package:mymuseum_visitapp/Services/voice_controller.dart'; import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; /// Bottom sheet de sélection et gestion du mode vocal. /// /// Accessible depuis GlassesStatusWidget (icône top-right) ou depuis /// les settings. Affiche : /// - État de connexion des lunettes (avec bouton connect/disconnect) /// - Tuiles de sélection de mode (vocal seul / lunettes) /// - Bouton de désactivation si un mode est actif class VoiceModeSheet extends StatelessWidget { final VisitAppContext visitAppContext; const VoiceModeSheet({super.key, required this.visitAppContext}); static Future show(BuildContext context, {required VisitAppContext visitAppContext}) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => VoiceModeSheet(visitAppContext: visitAppContext), ); } @override Widget build(BuildContext context) { return Consumer( builder: (context, controller, _) { return Container( decoration: const BoxDecoration( color: Color(0xFF1E1E2E), borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), padding: const EdgeInsets.fromLTRB(20, 12, 20, 32), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Handle Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 20), // Titre const Text( 'Assistant vocal', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), // Section lunettes — toujours visible _GlassesConnectionTile(), const SizedBox(height: 20), // Modes — uniquement si isAssistant if (controller.isFeatureAvailable(visitAppContext)) ...[ const Text( 'Mode', style: TextStyle(color: Colors.white54, fontSize: 12, letterSpacing: 1), ), const SizedBox(height: 8), _ModeTile( icon: Icons.mic, label: 'Mode vocal', subtitle: 'Micro du téléphone', targetMode: VoiceMode.voiceOnly, controller: controller, visitAppContext: visitAppContext, enabled: true, ), const SizedBox(height: 8), ValueListenableBuilder( valueListenable: MetaGlassesService.instance.state, builder: (context, glassesState, _) { final connected = glassesState == GlassesState.connected || glassesState == GlassesState.streaming; return _ModeTile( icon: Icons.remove_red_eye_outlined, label: 'Mode lunettes', subtitle: connected ? 'Ray-Ban Meta connectées' : 'Lunettes non connectées', targetMode: VoiceMode.glasses, controller: controller, visitAppContext: visitAppContext, enabled: connected, ); }, ), const SizedBox(height: 16), // Bouton désactiver if (controller.mode != VoiceMode.none) SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: controller.isActivating ? null : () => controller.deactivate(), icon: const Icon(Icons.stop_circle_outlined, color: Colors.redAccent), label: const Text('Désactiver', style: TextStyle(color: Colors.redAccent)), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.redAccent), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ), ), ] else const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( "L'assistant vocal n'est pas activé pour cette instance.", style: TextStyle(color: Colors.white54, fontSize: 13), ), ), ], ), ); }, ); } } class _GlassesConnectionTile extends StatelessWidget { @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: MetaGlassesService.instance.state, builder: (context, state, _) { final label = switch (state) { GlassesState.connected => 'Connectées', GlassesState.streaming => 'Connectées (streaming)', GlassesState.connecting => 'Connexion...', GlassesState.disconnected => 'Non connectées', }; final color = (state == GlassesState.connected || state == GlassesState.streaming) ? Colors.greenAccent : state == GlassesState.connecting ? Colors.orangeAccent : Colors.white38; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white10, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon(Icons.remove_red_eye_outlined, color: color, size: 22), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Ray-Ban Meta', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600)), Text(label, style: TextStyle(color: color, fontSize: 12)), ], ), ), if (state == GlassesState.disconnected) TextButton( onPressed: () => MetaGlassesService.instance.connect(), child: const Text('Connecter', style: TextStyle(color: kMainColor1)), ) else if (state == GlassesState.connected || state == GlassesState.streaming) TextButton( onPressed: () => MetaGlassesService.instance.disconnect(), child: const Text('Déconnecter', style: TextStyle(color: Colors.white54)), ) else const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)), ], ), ); }, ); } } class _ModeTile extends StatelessWidget { final IconData icon; final String label; final String subtitle; final VoiceMode targetMode; final VoiceController controller; final VisitAppContext visitAppContext; final bool enabled; const _ModeTile({ required this.icon, required this.label, required this.subtitle, required this.targetMode, required this.controller, required this.visitAppContext, required this.enabled, }); @override Widget build(BuildContext context) { final isActive = controller.mode == targetMode; return AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( color: isActive ? kMainColor1.withValues(alpha: 0.2) : Colors.white10, borderRadius: BorderRadius.circular(12), border: Border.all( color: isActive ? kMainColor1 : Colors.transparent, width: 1.5, ), ), child: ListTile( leading: Icon(icon, color: isActive ? kMainColor1 : enabled ? Colors.white70 : Colors.white30), title: Text( label, style: TextStyle( color: enabled ? Colors.white : Colors.white38, fontWeight: isActive ? FontWeight.bold : FontWeight.normal, ), ), subtitle: Text(subtitle, style: TextStyle(color: enabled ? Colors.white54 : Colors.white24, fontSize: 12)), trailing: isActive ? const Icon(Icons.check_circle, color: kMainColor1) : enabled ? const Icon(Icons.chevron_right, color: Colors.white38) : const Icon(Icons.lock_outline, color: Colors.white24, size: 18), onTap: (enabled && !controller.isActivating && !isActive) ? () => controller.activate(targetMode, visitAppContext) : null, ), ); } }