fix(audio,storage,ui): allow PTT binding before audio is running
Save-binding before joining a voice channel used to return BridgeError.invalidCommand(audio not started) because the PttController only exists after start_audio runs and set_ptt_binding required a live controller. Users naturally want to bind their PTT key once on first launch, not every time they join a channel — fix: * chanora_storage::IdentityFileStore::set_ptt_binding / get_ptt_binding persist the privacy-safe binding triple (input_class, platform_key, key_label) into audio_meta.json next to transmit_mode and release_tail_ms. * ChanoraSession holds pending_binding: Arc<Mutex<Option<PttBinding>>>. set_ptt_binding now (1) persists to storage best-effort, (2) stashes into pending_binding, (3) forwards live to the controller only if one exists. No more AudioNotStarted. * init_storage loads the persisted binding into pending_binding so it survives app restarts. * start_audio applies pending_binding immediately after constructing the PttController so the first key-press after join already works. * supervisor_loop carries pending_binding and re-applies it after any reconnect-driven audio engine restart, so reconnects don't silently drop the hotkey. * New bridge call get_ptt_binding() -> (input_class, key_label) plus a matching Flutter _hydratePttBinding() in initState lets the Voice Bar show the user's saved hotkey label on launch (e.g. 'PTT: Space') before any voice channel is joined. cargo test --workspace --lib: 72 passed / 0 failed / 1 ignored. flutter analyze: clean (6 pre-existing Radio.groupValue infos). FRB bindings regenerated.
This commit is contained in:
@@ -145,6 +145,27 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
super.initState();
|
||||
_eventsSub = rust.eventsStream().listen(_onEvent);
|
||||
unawaited(_reloadBookmarks());
|
||||
unawaited(_hydratePttBinding());
|
||||
}
|
||||
|
||||
/// Hydrate the bound-key display state from the bridge so the
|
||||
/// Voice Bar shows the user's persisted hotkey label immediately
|
||||
/// at launch — without having to wait for them to open the
|
||||
/// binding dialog. The bridge returns empty strings when no
|
||||
/// binding has ever been saved.
|
||||
Future<void> _hydratePttBinding() async {
|
||||
try {
|
||||
final (inputClass, keyLabel) = await rust.getPttBinding();
|
||||
if (!mounted) return;
|
||||
if (keyLabel.isNotEmpty || inputClass.isNotEmpty) {
|
||||
setState(() {
|
||||
_pttBoundKeyLabel = keyLabel;
|
||||
_pttBoundInputClass = inputClass;
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
// No persisted binding or storage not yet wired; not an error.
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reloadBookmarks() async {
|
||||
|
||||
Reference in New Issue
Block a user