fix(android): clear permission-owned hard mute
This commit is contained in:
@@ -251,6 +251,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
rust.BridgeTransmitMode _transmitMode = rust.BridgeTransmitMode.ptt;
|
rust.BridgeTransmitMode _transmitMode = rust.BridgeTransmitMode.ptt;
|
||||||
bool _hardMute = false;
|
bool _hardMute = false;
|
||||||
bool _hardMuteByPermission = false;
|
bool _hardMuteByPermission = false;
|
||||||
|
bool _permissionHardMuteClearInFlight = false;
|
||||||
int _releaseTailMs = 200;
|
int _releaseTailMs = 200;
|
||||||
BigInt? _currentVoiceChannelId;
|
BigInt? _currentVoiceChannelId;
|
||||||
BigInt? _pendingVoiceChannelId;
|
BigInt? _pendingVoiceChannelId;
|
||||||
@@ -310,10 +311,38 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
// events as early as possible so the listen-only banner reflects
|
// events as early as possible so the listen-only banner reflects
|
||||||
// the system state on first frame.
|
// the system state on first frame.
|
||||||
_androidPermissions.start();
|
_androidPermissions.start();
|
||||||
|
_androidPermissions.recordAudioState.addListener(
|
||||||
|
_onRecordAudioPermissionChanged,
|
||||||
|
);
|
||||||
unawaited(_reloadBookmarks());
|
unawaited(_reloadBookmarks());
|
||||||
unawaited(_hydratePttBinding());
|
unawaited(_hydratePttBinding());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onRecordAudioPermissionChanged() {
|
||||||
|
if (_androidPermissions.recordAudioState.value ==
|
||||||
|
AndroidRecordAudioPermissionState.granted) {
|
||||||
|
unawaited(_clearPermissionHardMute());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _clearPermissionHardMute() async {
|
||||||
|
if (!_hardMuteByPermission || _permissionHardMuteClearInFlight) return;
|
||||||
|
_permissionHardMuteClearInFlight = true;
|
||||||
|
try {
|
||||||
|
await rust.setHardMute(muted: false);
|
||||||
|
if (!mounted || !_hardMuteByPermission) return;
|
||||||
|
setState(() {
|
||||||
|
_hardMute = false;
|
||||||
|
_hardMuteByPermission = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _error = e.toString());
|
||||||
|
} finally {
|
||||||
|
_permissionHardMuteClearInFlight = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool _handleFocusedPttKey(KeyEvent event) {
|
bool _handleFocusedPttKey(KeyEvent event) {
|
||||||
final label = _pttDisplayLabelForKey(event.logicalKey);
|
final label = _pttDisplayLabelForKey(event.logicalKey);
|
||||||
final isBoundKey =
|
final isBoundKey =
|
||||||
@@ -492,16 +521,17 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
// SDD-106 §5/§6 / SRS-209: defensive observer of the
|
// SDD-106 §5/§6 / SRS-209: defensive observer of the
|
||||||
// authoritative Rust-side permission stream. The transmit
|
// authoritative Rust-side permission stream. The transmit
|
||||||
// clamp is already applied inside `chanora_bridge` before
|
// clamp is already applied inside `chanora_bridge` before
|
||||||
// this event is broadcast; here we merely surface the
|
// this event is broadcast. When permission later becomes
|
||||||
// event so the UI stays consistent if the MethodChannel
|
// granted, release only the permission-owned hard-mute; do
|
||||||
// path is ever delayed. The existing `AndroidPermissionsService`
|
// not touch server-side input mute, which may be user-owned.
|
||||||
// remains the canonical Dart-side state holder (driven by
|
|
||||||
// the MethodChannel); a future revision may expose a
|
|
||||||
// setter so both paths converge on a single ValueNotifier.
|
|
||||||
case rust.BridgeEvent_PermissionState(:final permission, :final state):
|
case rust.BridgeEvent_PermissionState(:final permission, :final state):
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'bridge permission_state: permission=$permission state=$state',
|
'bridge permission_state: permission=$permission state=$state',
|
||||||
);
|
);
|
||||||
|
if (permission == 'android.permission.RECORD_AUDIO' &&
|
||||||
|
state == rust.PermissionStateKind.granted) {
|
||||||
|
unawaited(_clearPermissionHardMute());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +563,9 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
_hostCtl.dispose();
|
_hostCtl.dispose();
|
||||||
_nickCtl.dispose();
|
_nickCtl.dispose();
|
||||||
_passwordCtl.dispose();
|
_passwordCtl.dispose();
|
||||||
|
_androidPermissions.recordAudioState.removeListener(
|
||||||
|
_onRecordAudioPermissionChanged,
|
||||||
|
);
|
||||||
// SDD-106: detach the Kotlin -> Dart MethodChannel handler so a
|
// SDD-106: detach the Kotlin -> Dart MethodChannel handler so a
|
||||||
// late invokeMethod from the platform side cannot land on this
|
// late invokeMethod from the platform side cannot land on this
|
||||||
// disposed state.
|
// disposed state.
|
||||||
|
|||||||
Reference in New Issue
Block a user