feat(ui,ptt): on-screen touch-and-hold PTT button for iOS / iPadOS / Android
iOS / iPadOS / Android have no hardware keyboard for the user to
bind a PTT key on. Up to now the VoiceBar showed only a
'Push to talk: bound key —' hint that didn't lead anywhere usable.
Add a touch-and-hold on-screen PTT button rendered only on
touch-only platforms (Platform.isIOS || Platform.isAndroid; web
hosts and desktop continue to use the hardware-key path
unchanged).
apps/chanora_flutter/lib/widgets/voice_bar.dart:
* New module-private `_isTouchOnlyPttHost` predicate.
* VoiceBar gains an `onPttHeldChanged: ValueChanged<bool>`
constructor param. Desktop callers wire it but never invoke it
because the button is not rendered there.
* Row 3 (the PTT-only secondary content) now branches:
- on touch-only hosts -> renders the new `_PttHoldButton` plus
a small release-tail hint underneath
- on hardware-keyboard hosts -> renders the same bound-key +
release-tail one-liner as before, unchanged.
* New `_PttHoldButton` StatefulWidget. Uses a single
GestureDetector covering onTapDown / onTapUp / onTapCancel /
onPanDown / onPanEnd / onPanCancel so the held edges fire on
finger-down and the released edge fires when the user lifts
OR drags off OR another gesture in the arena wins. Visual
feedback mirrors the level-meter active flag.
apps/chanora_flutter/lib/main.dart:
* New `_onOnscreenPttHeldChanged(bool held)` method that calls
`rust.setPtt(active: held)`. The bridge's set_ptt routes the
edge through the same release-tail timer + transmit-mode
selector that desktop hardware keys use (SDD-096 / SAD-083),
so behaviour parity is preserved.
flutter analyze: clean (6 pre-existing Radio.groupValue infos).
flutter build ios --release --no-codesign: clean (Runner.app 29.9 MB).
DEC-025: iPhone + iPad + Android in scope; this commit makes PTT
mode actually usable on those platforms. The 'Focused' capability
badge wording in ios-p0-acceptance.md / ipad-p0-acceptance.md
already documents the on-screen button as the only PTT input;
this commit makes that documentation true.
This commit is contained in:
@@ -461,6 +461,31 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Touch-only PTT (iOS / iPadOS / Android). The on-screen
|
||||
/// `_PttHoldButton` calls this with `true` on finger-down and
|
||||
/// `false` on finger-up (or cancel). The bridge's
|
||||
/// `setPtt(active:)` routes the press edge through the same
|
||||
/// release-tail timer + transmit-mode selector the desktop
|
||||
/// hardware-key paths use (SDD-096 / SAD-083), so the user-
|
||||
/// visible behaviour is identical across platforms — only the
|
||||
/// input device changes.
|
||||
///
|
||||
/// Errors are swallowed silently in the held=false branch
|
||||
/// because the timer's `key_up` is idempotent; a failed send
|
||||
/// would still let the tail expire naturally. Errors on
|
||||
/// held=true surface in the UI banner so the user knows the
|
||||
/// mic didn't open.
|
||||
Future<void> _onOnscreenPttHeldChanged(bool held) async {
|
||||
try {
|
||||
await rust.setPtt(active: held);
|
||||
} catch (e) {
|
||||
if (held) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onOpenVoiceSettings() async {
|
||||
final result = await showDialog<VoiceSettingsResult>(
|
||||
context: context,
|
||||
@@ -943,6 +968,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
onToggleMute: _onToggleHardMute,
|
||||
onToggleOutputMute: _toggleOutputMute,
|
||||
onConfigure: _onOpenVoiceSettings,
|
||||
onPttHeldChanged: _onOnscreenPttHeldChanged,
|
||||
);
|
||||
final snapshotView = _SnapshotView(
|
||||
snapshot: _snapshot!,
|
||||
|
||||
Reference in New Issue
Block a user