diff --git a/apps/chanora_flutter/lib/l10n/app_en.arb b/apps/chanora_flutter/lib/l10n/app_en.arb index db1ac3d..a19047d 100644 --- a/apps/chanora_flutter/lib/l10n/app_en.arb +++ b/apps/chanora_flutter/lib/l10n/app_en.arb @@ -123,7 +123,8 @@ "voiceReleaseTailLabel": "Release tail", "voiceReleaseTailHint": "ms", "voiceLeaveAction": "Leave voice", - "voiceHardMuteLabel": "Mute", + "voiceHardMuteLabel": "Mute microphone", + "voiceOutputMuteLabel": "Mute speakers", "voiceSettingsTitle": "Voice settings", "voiceBindKeyAction": "Bind PTT key" } diff --git a/apps/chanora_flutter/lib/l10n/app_zh.arb b/apps/chanora_flutter/lib/l10n/app_zh.arb index d6a082e..b041d91 100644 --- a/apps/chanora_flutter/lib/l10n/app_zh.arb +++ b/apps/chanora_flutter/lib/l10n/app_zh.arb @@ -80,7 +80,8 @@ "voiceReleaseTailLabel": "释放延迟", "voiceReleaseTailHint": "毫秒", "voiceLeaveAction": "离开语音", - "voiceHardMuteLabel": "静音", + "voiceHardMuteLabel": "麦克风静音", + "voiceOutputMuteLabel": "扬声器静音", "voiceSettingsTitle": "语音设置", "voiceBindKeyAction": "绑定 PTT 按键" } diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart index 5638062..2b01368 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart @@ -526,9 +526,15 @@ abstract class AppL10n { /// No description provided for @voiceHardMuteLabel. /// /// In en, this message translates to: - /// **'Mute'** + /// **'Mute microphone'** String get voiceHardMuteLabel; + /// No description provided for @voiceOutputMuteLabel. + /// + /// In en, this message translates to: + /// **'Mute speakers'** + String get voiceOutputMuteLabel; + /// No description provided for @voiceSettingsTitle. /// /// In en, this message translates to: diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart index 389c1a9..9e6f221 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart @@ -255,7 +255,10 @@ class AppL10nEn extends AppL10n { String get voiceLeaveAction => 'Leave voice'; @override - String get voiceHardMuteLabel => 'Mute'; + String get voiceHardMuteLabel => 'Mute microphone'; + + @override + String get voiceOutputMuteLabel => 'Mute speakers'; @override String get voiceSettingsTitle => 'Voice settings'; diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart index 4960a23..440b02d 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart @@ -249,7 +249,10 @@ class AppL10nZh extends AppL10n { String get voiceLeaveAction => '离开语音'; @override - String get voiceHardMuteLabel => '静音'; + String get voiceHardMuteLabel => '麦克风静音'; + + @override + String get voiceOutputMuteLabel => '扬声器静音'; @override String get voiceSettingsTitle => '语音设置'; diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index b592931..a7f848d 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -318,7 +318,6 @@ class _BetaHomeState extends State<_BetaHome> { } } - // ignore: unused_element Future _toggleOutputMute() async { final next = !_outputMuted; try { @@ -846,6 +845,7 @@ class _BetaHomeState extends State<_BetaHome> { inChannel: _inChannel, transmitMode: _transmitMode, hardMute: _hardMute, + outputMuted: _outputMuted, releaseTailMs: _releaseTailMs, channelName: _currentVoiceChannelName(), audioStats: _audioStats, @@ -854,6 +854,7 @@ class _BetaHomeState extends State<_BetaHome> { pttBoundInputClass: _pttBoundInputClass, pttBoundKeyLabel: _pttBoundKeyLabel, onToggleMute: _onToggleHardMute, + onToggleOutputMute: _toggleOutputMute, onConfigure: _onOpenVoiceSettings, onLeave: _onLeaveVoice, ), diff --git a/apps/chanora_flutter/lib/widgets/voice_bar.dart b/apps/chanora_flutter/lib/widgets/voice_bar.dart index 414f70e..8e173ab 100644 --- a/apps/chanora_flutter/lib/widgets/voice_bar.dart +++ b/apps/chanora_flutter/lib/widgets/voice_bar.dart @@ -18,6 +18,7 @@ class VoiceBar extends StatelessWidget { required this.inChannel, required this.transmitMode, required this.hardMute, + required this.outputMuted, required this.releaseTailMs, required this.channelName, required this.audioStats, @@ -26,6 +27,7 @@ class VoiceBar extends StatelessWidget { required this.pttBoundInputClass, required this.pttBoundKeyLabel, required this.onToggleMute, + required this.onToggleOutputMute, required this.onConfigure, required this.onLeave, }); @@ -39,6 +41,13 @@ class VoiceBar extends StatelessWidget { /// Hard-mute clamp state. final bool hardMute; + /// Speaker (output) mute state. Mirrors the server-broadcast + /// `ClientOutputMuted` flag plus the engine's local output + /// silencer — toggling this hushes incoming voice immediately + /// AND tells the server so other clients see the headphone-off + /// icon next to our name. + final bool outputMuted; + /// Configured release-tail in milliseconds (0..=500). Surfaced as /// a hint underneath the mode badge. final int releaseTailMs; @@ -67,6 +76,9 @@ class VoiceBar extends StatelessWidget { /// Toggle the hard-mute clamp. final VoidCallback onToggleMute; + /// Toggle speaker (output) mute. + final VoidCallback onToggleOutputMute; + /// Open the voice settings dialog. This is the SINGLE entry point /// for transmit-mode selection, PTT key binding, and release-tail /// configuration. The capability badge below is information-only @@ -136,6 +148,15 @@ class VoiceBar extends StatelessWidget { ), ], const Spacer(), + IconButton( + tooltip: l10n.voiceOutputMuteLabel, + icon: Icon( + outputMuted ? Icons.headset_off : Icons.headset, + ), + isSelected: outputMuted, + selectedIcon: const Icon(Icons.headset_off), + onPressed: onToggleOutputMute, + ), IconButton( tooltip: l10n.voiceHardMuteLabel, icon: Icon(hardMute ? Icons.mic_off : Icons.mic),