fix(ui,voice): five Voice Bar / settings UX bugs

1. Hard-mute now informs the server (setInputMuted) in addition to
   clamping the local TransmitGate. Without the server-side flag,
   other clients keep seeing us un-muted; without the local clamp
   a beat of in-flight audio leaks through. Drive both together so
   the mic icon and the actual silence land at the same time.

2. Split the badge's Configure affordance from the Voice Bar's
   'Voice settings' gear. The gear opens the mode + release-tail
   dialog (onConfigure); the badge's configure opens the bind-key
   capture flow directly (new onBindKey). Previously both routed
   to the settings dialog, so 'Voice settings' and the badge's
   'Configure' were the same screen — useless duplication.

3. Bind-key label is now PTT-only. The mode-badge row no longer
   prints 'PTT: Space' when Continuous / Voice Activity is
   selected. A new PTT-only secondary line carries the bound key
   plus the release-tail value together, hidden entirely for
   non-PTT modes.

4. Release-tail row is now PTT-only in BOTH the Voice Bar and the
   Voice settings dialog. The dialog previously kept the slider
   visible across all modes; switching to Continuous left the
   user staring at a control that did nothing.

5. PTT capability badge is now PTT-only. In Continuous and Voice
   Activity modes there is no key binding to surface a capability
   for, so the 'L0Focused (focused)' line + its info sheet and
   the Configure button disappear from the Voice Bar when the
   user isn't in PTT mode.

All five fixes are pure UI; no Rust changes needed. flutter analyze
remains clean (6 pre-existing Radio.groupValue deprecation infos).
This commit is contained in:
EdisonJwa
2026-05-15 23:34:53 +08:00
parent 7f4874f4c0
commit 6a41a0b4db
3 changed files with 85 additions and 51 deletions
+13
View File
@@ -356,7 +356,19 @@ class _BetaHomeState extends State<_BetaHome> {
Future<void> _onToggleHardMute() async {
final next = !_hardMute;
try {
// Hard-mute is two coordinated effects:
// * setHardMute — local TransmitGate clamp; we stop sending
// Opus frames the instant this returns.
// * setInputMuted — server-side ClientMuted flag so other
// clients see the mic-off icon next to our name and the
// server stops relaying any in-flight frames.
// Sending only one of them is user-confusing; clients see
// silence but no icon, or icon but a beat of audio leaks
// through. Drive them together.
await rust.setHardMute(muted: next);
await rust.setInputMuted(muted: next);
if (!mounted) return;
setState(() => _inputMuted = next);
} catch (e) {
if (!mounted) return;
setState(() => _error = e.toString());
@@ -822,6 +834,7 @@ class _BetaHomeState extends State<_BetaHome> {
pttBoundKeyLabel: _pttBoundKeyLabel,
onToggleMute: _onToggleHardMute,
onConfigure: _onOpenVoiceSettings,
onBindKey: () => _onConfigurePtt(context),
onLeave: _onLeaveVoice,
),
const SizedBox(height: 12),