diff --git a/CHANGES.md b/CHANGES.md index 4c323eb..1f7f3a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1392,3 +1392,21 @@ 2026-07-06 12:44 [Edit] C:\Users\nordm\pyramid - Kopie\docs\PC_TESTPLAN.md 2026-07-06 12:44 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md 2026-07-06 12:45 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md +2026-07-06 16:58 [Edit] C:\Users\nordm\pyramid - Kopie\docs\PC_TESTPLAN.md +2026-07-06 16:59 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md +2026-07-06 17:01 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\rooms\rooms_panel.dart +2026-07-06 17:01 [Edit] C:\Users\nordm\pyramid - Kopie\lib\layout\app_shell.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\settings_prefs.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 17:05 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 17:06 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings_modal.dart diff --git a/lib/core/settings_prefs.dart b/lib/core/settings_prefs.dart index 505d26f..8d09968 100644 --- a/lib/core/settings_prefs.dart +++ b/lib/core/settings_prefs.dart @@ -90,6 +90,56 @@ final privacyReadReceiptsProvider = final privacyTypingProvider = StateNotifierProvider((ref) => BoolPref('privacy_typing', true)); +// ── Voice preferences ──────────────────────────────────────────────────────── +// Geschrieben von der Settings-Voice-Sektion, gelesen von den Call-Modulen +// (call_signaling, voice_channel). Die historischen voice_*-Keys bleiben +// unverändert, damit bestehende Nutzer-Einstellungen erhalten bleiben. + +class VoicePrefs { + VoicePrefs._(); + + static const _kMic = 'voice_mic_device'; + static const _kSpeaker = 'voice_speaker_device'; + static const _kCamera = 'voice_camera_device'; + static const _kOutputVolume = 'voice_output_volume'; + + static Future loadOutputVolume() async { + final p = await SharedPreferences.getInstance(); + return p.getDouble(_kOutputVolume) ?? 1.0; + } + + static Future saveOutputVolume(double v) async { + final p = await SharedPreferences.getInstance(); + await p.setDouble(_kOutputVolume, v); + } + + /// Gewählte Geräte-IDs; null = kein Pref gesetzt (System-Standard nutzen). + static Future<({String? mic, String? speaker, String? camera})> + loadDevices() async { + final p = await SharedPreferences.getInstance(); + return ( + mic: p.getString(_kMic), + speaker: p.getString(_kSpeaker), + camera: p.getString(_kCamera), + ); + } + + static Future saveMic(String? deviceId) => _saveDevice(_kMic, deviceId); + static Future saveSpeaker(String? deviceId) => + _saveDevice(_kSpeaker, deviceId); + static Future saveCamera(String? deviceId) => + _saveDevice(_kCamera, deviceId); + + static Future _saveDevice(String key, String? deviceId) async { + final p = await SharedPreferences.getInstance(); + if (deviceId == null || deviceId.isEmpty) { + await p.remove(key); + } else { + await p.setString(key, deviceId); + } + } +} + // ── Theme persistence helpers ──────────────────────────────────────────────── Future saveThemePrefs({ diff --git a/lib/features/call_signaling/voip_manager.dart b/lib/features/call_signaling/voip_manager.dart index 22b888d..9fd5ed5 100644 --- a/lib/features/call_signaling/voip_manager.dart +++ b/lib/features/call_signaling/voip_manager.dart @@ -3,9 +3,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:webrtc_interface/webrtc_interface.dart'; import 'package:pyramid/core/ice_servers.dart'; +import 'package:pyramid/core/settings_prefs.dart'; import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; import 'package:pyramid/widgets/screen_share_picker.dart'; @@ -272,8 +272,7 @@ class PyramidVoipManager extends CallSignalingService var v = value; if (v == null) { try { - final prefs = await SharedPreferences.getInstance(); - v = prefs.getDouble('voice_output_volume') ?? 1.0; + v = await VoicePrefs.loadOutputVolume(); } catch (_) { v = 1.0; } @@ -467,10 +466,10 @@ class MediaDevicesWrapper extends MediaDevices { // In den Einstellungen gewählte Geräte (voice_*_device Prefs) anwenden — // nur wenn kein Screenshare-Source gesetzt ist (sonst nicht anfassen). try { - final prefs = await SharedPreferences.getInstance(); - final mic = prefs.getString('voice_mic_device'); - final cam = prefs.getString('voice_camera_device'); - final speaker = prefs.getString('voice_speaker_device'); + final devicePrefs = await VoicePrefs.loadDevices(); + final mic = devicePrefs.mic; + final cam = devicePrefs.camera; + final speaker = devicePrefs.speaker; if (mic != null && improvedConstraints['audio'] != null && improvedConstraints['audio'] != false) { diff --git a/lib/features/voice_channel/livekit_call_manager.dart b/lib/features/voice_channel/livekit_call_manager.dart index 60f2c6b..6dbe5d9 100644 --- a/lib/features/voice_channel/livekit_call_manager.dart +++ b/lib/features/voice_channel/livekit_call_manager.dart @@ -6,8 +6,8 @@ import 'package:livekit_client/livekit_client.dart'; import 'package:matrix/matrix.dart' hide Room; import 'package:pyramid/core/ice_servers.dart'; import 'package:pyramid/core/livekit_token.dart'; +import 'package:pyramid/core/settings_prefs.dart'; import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; -import 'package:shared_preferences/shared_preferences.dart'; const _kVoicePresenceType = 'io.pyramid.voice.presence'; @@ -260,11 +260,11 @@ class LiveKitCallManager extends VoiceChannelService { String? prefMic, prefSpeaker, prefCam; var deviceIds = {}; try { - final prefs = await SharedPreferences.getInstance(); - _outputVolume = prefs.getDouble('voice_output_volume') ?? 1.0; - prefMic = prefs.getString('voice_mic_device'); - prefSpeaker = prefs.getString('voice_speaker_device'); - prefCam = prefs.getString('voice_camera_device'); + _outputVolume = await VoicePrefs.loadOutputVolume(); + final devicePrefs = await VoicePrefs.loadDevices(); + prefMic = devicePrefs.mic; + prefSpeaker = devicePrefs.speaker; + prefCam = devicePrefs.camera; final devices = await rtc.navigator.mediaDevices.enumerateDevices(); deviceIds = devices.map((d) => d.deviceId).toSet(); } catch (e) { diff --git a/lib/widgets/settings/settings_voice.dart b/lib/widgets/settings/settings_voice.dart index 845258a..f42cab2 100644 --- a/lib/widgets/settings/settings_voice.dart +++ b/lib/widgets/settings/settings_voice.dart @@ -19,11 +19,6 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { bool _loading = true; String? _loadError; - static const _kMic = 'voice_mic_device'; - static const _kSpeaker = 'voice_speaker_device'; - static const _kCamera = 'voice_camera_device'; - static const _kOutputVolume = 'voice_output_volume'; - @override void initState() { super.initState(); @@ -31,7 +26,8 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { } Future _init() async { - final prefs = await SharedPreferences.getInstance(); + final devicePrefs = await VoicePrefs.loadDevices(); + final outputVolume = await VoicePrefs.loadOutputVolume(); List devices = []; String? loadError; try { @@ -76,10 +72,10 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { _audioInputs = devices.where((d) => d.kind?.toLowerCase() == 'audioinput').toList(); _audioOutputs = devices.where((d) => d.kind?.toLowerCase() == 'audiooutput').toList(); _videoInputs = devices.where((d) => d.kind?.toLowerCase() == 'videoinput').toList(); - _selectedMic = prefs.getString(_kMic); - _selectedSpeaker = prefs.getString(_kSpeaker); - _selectedCamera = prefs.getString(_kCamera); - _outputVolume = prefs.getDouble(_kOutputVolume) ?? 1.0; + _selectedMic = devicePrefs.mic; + _selectedSpeaker = devicePrefs.speaker; + _selectedCamera = devicePrefs.camera; + _outputVolume = outputVolume; _loadError = loadError; _loading = false; }); @@ -87,8 +83,7 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { Future _setOutputVolume(double v) async { setState(() => _outputVolume = v); - final prefs = await SharedPreferences.getInstance(); - await prefs.setDouble(_kOutputVolume, v); + await VoicePrefs.saveOutputVolume(v); // Live auf laufende Calls anwenden (über die Modul-Fassaden). ref.read(voiceChannelProvider).setOutputVolume(v).catchError((_) {}); try { @@ -96,15 +91,6 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { } catch (_) {} // Instanz existiert evtl. noch nicht } - Future _save(String key, String? value) async { - final prefs = await SharedPreferences.getInstance(); - if (value == null || value.isEmpty) { - await prefs.remove(key); - } else { - await prefs.setString(key, value); - } - } - Widget _deviceDropdown({ required List devices, required String? selected, @@ -227,7 +213,7 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { icon: Icons.mic_rounded, onChanged: (v) { setState(() => _selectedMic = v); - _save(_kMic, v); + VoicePrefs.saveMic(v); }, ), ), @@ -243,7 +229,7 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { icon: Icons.volume_up_rounded, onChanged: (v) { setState(() => _selectedSpeaker = v); - _save(_kSpeaker, v); + VoicePrefs.saveSpeaker(v); }, ), ), @@ -259,7 +245,7 @@ class _VoiceSectionState extends ConsumerState<_VoiceSection> { icon: Icons.videocam_rounded, onChanged: (v) { setState(() => _selectedCamera = v); - _save(_kCamera, v); + VoicePrefs.saveCamera(v); }, ), ), diff --git a/lib/widgets/settings_modal.dart b/lib/widgets/settings_modal.dart index 183c713..5eb4ffa 100644 --- a/lib/widgets/settings_modal.dart +++ b/lib/widgets/settings_modal.dart @@ -36,7 +36,6 @@ import 'package:pyramid/widgets/banner_crop_dialog.dart'; import 'package:pyramid/widgets/hover_region.dart'; import 'package:pyramid/widgets/mxc_image.dart'; import 'package:pyramid/widgets/pyramid_loader.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'settings/settings_shared.dart'; part 'settings/settings_profile.dart';