fix: improve voice and chat controls
This commit is contained in:
@@ -174,6 +174,7 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
bool _hardMute = false;
|
||||
bool _hardMuteByPermission = false;
|
||||
bool _hardMuteByTalkPower = false;
|
||||
bool _hardMuteByOutputMute = false;
|
||||
bool _permissionHardMuteClearInFlight = false;
|
||||
int _releaseTailMs = 200;
|
||||
BigInt? _currentVoiceChannelId;
|
||||
@@ -212,6 +213,7 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
final ValueNotifier<int> _chatFeedRevision = ValueNotifier(0);
|
||||
int _chatUnread = 0;
|
||||
bool _chatOpen = false;
|
||||
bool _chatPreviewSnackBarVisible = false;
|
||||
final ValueNotifier<List<_ReceivedPoke>> _pokeSnackBarPokes = ValueNotifier(
|
||||
const [],
|
||||
);
|
||||
@@ -484,8 +486,10 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
await rust.setHardMute(muted: false);
|
||||
if (!mounted || !_hardMuteByPermission) return;
|
||||
setState(() {
|
||||
_hardMute = false;
|
||||
_hardMuteByPermission = false;
|
||||
if (!_inputMuted && !_hardMuteByTalkPower && !_hardMuteByOutputMute) {
|
||||
_hardMute = false;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -1010,6 +1014,14 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_outputMuted = next;
|
||||
_hardMuteByOutputMute = next;
|
||||
if (next) {
|
||||
_hardMute = true;
|
||||
} else if (!_inputMuted &&
|
||||
!_hardMuteByPermission &&
|
||||
!_hardMuteByTalkPower) {
|
||||
_hardMute = false;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -1104,8 +1116,9 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
Future<void> _onToggleHardMute() async {
|
||||
// Don't allow manual unmute when talk-power-muted.
|
||||
if (_hardMuteByTalkPower && _hardMute) {
|
||||
// Don't allow manual unmute while another policy owns the local
|
||||
// transmit clamp.
|
||||
if ((_hardMuteByTalkPower || _hardMuteByOutputMute) && _hardMute) {
|
||||
return;
|
||||
}
|
||||
final next = !_hardMute;
|
||||
@@ -1200,6 +1213,8 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
pttBoundInputClass: _pttBoundInputClass,
|
||||
isTouchOnly: isTouchOnlyPttHost,
|
||||
initialAudioConfig: audioConfig,
|
||||
onnxRuntimeAvailable:
|
||||
StartupDependencyScope.maybeOf(context)?.hasOnnxRuntime ?? true,
|
||||
onModeChanged: (mode) async {
|
||||
try {
|
||||
await rust.setTransmitMode(mode: mode);
|
||||
@@ -1241,6 +1256,8 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
initialMode: _transmitMode,
|
||||
initialReleaseTailMs: _releaseTailMs,
|
||||
initialAudioConfig: audioConfig,
|
||||
onnxRuntimeAvailable:
|
||||
StartupDependencyScope.maybeOf(ctx)?.hasOnnxRuntime ?? true,
|
||||
pttLevel: _pttLevel,
|
||||
pttBackendId: _pttBackendId,
|
||||
pttBoundInputClass: _pttBoundInputClass,
|
||||
@@ -1399,6 +1416,10 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
_audioStats = null;
|
||||
_inputMuted = false;
|
||||
_outputMuted = false;
|
||||
_hardMute = false;
|
||||
_hardMuteByPermission = false;
|
||||
_hardMuteByTalkPower = false;
|
||||
_hardMuteByOutputMute = false;
|
||||
_inChannel = false;
|
||||
_currentVoiceChannelId = null;
|
||||
_pendingVoiceChannelId = null;
|
||||
@@ -1418,6 +1439,7 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
String clientName = '',
|
||||
}) async {
|
||||
final initialSnapshot = _snapshot!;
|
||||
_dismissChatPreviewSnackBar();
|
||||
setState(() {
|
||||
_chatUnread = 0;
|
||||
_chatOpen = true;
|
||||
@@ -1500,36 +1522,50 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
if (_pokeSnackBarPokes.value.isNotEmpty) return;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger.hideCurrentSnackBar();
|
||||
messenger.showSnackBar(
|
||||
_chatPreviewSnackBarVisible = true;
|
||||
void openChatFromPreview() {
|
||||
_dismissChatPreviewSnackBar();
|
||||
unawaited(
|
||||
_onOpenChat(
|
||||
target: target,
|
||||
clientName: _chatClientNameForTarget(target, senderName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final controller = messenger.showSnackBar(
|
||||
SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: _chatSnackBarMargin(),
|
||||
duration: const Duration(seconds: 7),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$senderName (${_chatTargetLabel(target)})',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(message, maxLines: 3, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Open',
|
||||
onPressed: () => unawaited(
|
||||
_onOpenChat(
|
||||
target: target,
|
||||
clientName: _chatClientNameForTarget(target, senderName),
|
||||
),
|
||||
duration: const Duration(seconds: 4),
|
||||
content: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: openChatFromPreview,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$senderName (${_chatTargetLabel(target)})',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(message, maxLines: 3, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
),
|
||||
action: SnackBarAction(label: 'Open', onPressed: openChatFromPreview),
|
||||
),
|
||||
);
|
||||
controller.closed.then((_) => _chatPreviewSnackBarVisible = false);
|
||||
}
|
||||
|
||||
void _dismissChatPreviewSnackBar() {
|
||||
if (!_chatPreviewSnackBarVisible || !mounted) return;
|
||||
_chatPreviewSnackBarVisible = false;
|
||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry _chatSnackBarMargin() {
|
||||
@@ -1589,6 +1625,14 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
if (own == null) return;
|
||||
_inputMuted = own.inputMuted;
|
||||
_outputMuted = own.outputMuted;
|
||||
_hardMuteByOutputMute = own.outputMuted;
|
||||
if (own.outputMuted) {
|
||||
_hardMute = true;
|
||||
} else if (!_inputMuted &&
|
||||
!_hardMuteByPermission &&
|
||||
!_hardMuteByTalkPower) {
|
||||
_hardMute = false;
|
||||
}
|
||||
if (!_voiceStateInitialized || _currentVoiceChannelId == null) {
|
||||
_currentVoiceChannelId = own.channelId;
|
||||
_pendingVoiceChannelId = null;
|
||||
@@ -1607,7 +1651,7 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
rust.setInputMuted(muted: true);
|
||||
} else if (own.talkPowerOk && _hardMuteByTalkPower) {
|
||||
_hardMuteByTalkPower = false;
|
||||
if (!_hardMuteByPermission) {
|
||||
if (!_hardMuteByPermission && !_hardMuteByOutputMute && !_inputMuted) {
|
||||
_hardMute = false;
|
||||
rust.setHardMute(muted: false);
|
||||
rust.setInputMuted(muted: false);
|
||||
@@ -1622,12 +1666,12 @@ class _BetaHomeState extends State<_BetaHome> with WidgetsBindingObserver {
|
||||
|
||||
Future<void> _onShowDiagnostics(BuildContext context) async {
|
||||
final l10n = AppL10n.of(context);
|
||||
final rustText = rust.exportDiagnostics();
|
||||
final rustText = await rust.exportDiagnostics();
|
||||
if (!context.mounted) return;
|
||||
final uiText = _uiDiagnostics.isEmpty
|
||||
? 'UI diagnostics: none'
|
||||
: ['UI diagnostics:', ..._uiDiagnostics].join('\n');
|
||||
final text = '$uiText\n\nRust diagnostics:\n$rustText';
|
||||
if (!mounted) return;
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
|
||||
Reference in New Issue
Block a user