Per SDD-106 §5 add BridgeEvent::PermissionState{permission, state}
with the PermissionStateKind enum (Granted, Denied, PermanentlyDenied,
Unknown). The Kotlin side publishes mid-session permission changes
through a new JNI entry point Java_app_chanora_chanora_1flutter
_MainActivity_publishPermissionState routed by the new
permission_jni.rs module; the Rust audio engine subscribes and
authoritatively clamps the transmit gate (see SDD-106 §6).
Adds crates/chanora_bridge/build.rs to emit
cargo:rustc-link-lib=dylib=c++_shared on Android so libchanora_bridge
.so carries DT_NEEDED libc++_shared.so; this is required by Android
API 24+ per-library linker namespaces to resolve __cxa_pure_virtual
and friends at System.loadLibrary time.
Includes the FRB-regenerated Dart counterparts so each commit is
independently buildable.
Trace: SDD-105, SDD-106 §5, SDD-118 item 6 (extended).
610 lines
21 KiB
Dart
610 lines
21 KiB
Dart
// This file is automatically generated, so please do not edit it.
|
|
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
|
|
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
|
|
import 'frb_generated.dart';
|
|
import 'lib.dart';
|
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
|
part 'api.freezed.dart';
|
|
|
|
// These functions are ignored because they are not marked as `pub`: `log_file_path`, `log_sink`, `open_log_file`, `permission_events`, `publish_permission_state`, `runtime`, `session`, `transmit_mode_from_u8`
|
|
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`
|
|
// These functions are ignored (category: IgnoreBecauseExplicitAttribute): `from_kotlin_str`, `to_permission_gate`
|
|
|
|
/// Return the platform-conventional log-file path as a string, or
|
|
/// an empty string if the platform does not have one (mobile).
|
|
String logFilePathStr() => RustLib.instance.api.crateApiLogFilePathStr();
|
|
|
|
/// Connect to a TeamSpeak-compatible server and return the initial
|
|
/// state snapshot. Honours the DEC-006 single-connection invariant
|
|
/// via [`BridgeError::AlreadyConnected`].
|
|
///
|
|
/// `password` is optional — pass an empty string for servers that
|
|
/// don't require one.
|
|
Future<BridgeSnapshot> connect({
|
|
required String host,
|
|
required String nickname,
|
|
required String password,
|
|
}) => RustLib.instance.api.crateApiConnect(
|
|
host: host,
|
|
nickname: nickname,
|
|
password: password,
|
|
);
|
|
|
|
/// Re-fetch a fresh snapshot from the active connection.
|
|
Future<BridgeSnapshot> snapshot() => RustLib.instance.api.crateApiSnapshot();
|
|
|
|
/// Disconnect from the server. No-op if not connected.
|
|
Future<void> disconnect() => RustLib.instance.api.crateApiDisconnect();
|
|
|
|
/// True if a connection is currently active.
|
|
Future<bool> isConnected() => RustLib.instance.api.crateApiIsConnected();
|
|
|
|
/// Handle iOS AVAudioSession route changes (SDD-100).
|
|
void handleRouteChange() => RustLib.instance.api.crateApiHandleRouteChange();
|
|
|
|
/// Handle iOS AVAudioSession interruption begin (SDD-101).
|
|
void handleInterruptionBegan() =>
|
|
RustLib.instance.api.crateApiHandleInterruptionBegan();
|
|
|
|
/// Handle iOS AVAudioSession interruption end (SDD-101).
|
|
void handleInterruptionEnded({required bool shouldResume}) => RustLib
|
|
.instance
|
|
.api
|
|
.crateApiHandleInterruptionEnded(shouldResume: shouldResume);
|
|
|
|
/// Set the push-to-talk state.
|
|
///
|
|
/// Superseded in v1 by [`set_transmit_mode`] + the binding capture
|
|
/// dialog. Retained so legacy callers and integration tests keep
|
|
/// working; the new VoiceBar UI no longer invokes this.
|
|
Future<void> setPtt({required bool active}) =>
|
|
RustLib.instance.api.crateApiSetPtt(active: active);
|
|
|
|
/// Join a voice channel (SDD-094). Moves the user to `channel_id`,
|
|
/// brings up the audio engine if needed, and emits
|
|
/// `BridgeEvent::VoiceState`. `password` may be empty.
|
|
Future<void> voiceJoin({required BigInt channelId, required String password}) =>
|
|
RustLib.instance.api.crateApiVoiceJoin(
|
|
channelId: channelId,
|
|
password: password,
|
|
);
|
|
|
|
/// Leave the current voice channel (SDD-094). Tears down the audio
|
|
/// engine and emits `BridgeEvent::VoiceState`.
|
|
Future<void> voiceLeave() => RustLib.instance.api.crateApiVoiceLeave();
|
|
|
|
/// Set the active transmit mode (SDD-095).
|
|
Future<void> setTransmitMode({required BridgeTransmitMode mode}) =>
|
|
RustLib.instance.api.crateApiSetTransmitMode(mode: mode);
|
|
|
|
/// Read the active transmit mode.
|
|
Future<BridgeTransmitMode> getTransmitMode() =>
|
|
RustLib.instance.api.crateApiGetTransmitMode();
|
|
|
|
/// Update the release-tail in milliseconds (SDD-096). Values are
|
|
/// clamped to `0..=500` on the Rust side; passing anything larger
|
|
/// silently saturates.
|
|
Future<void> setReleaseTailMs({required int ms}) =>
|
|
RustLib.instance.api.crateApiSetReleaseTailMs(ms: ms);
|
|
|
|
/// Read the current release-tail in milliseconds.
|
|
Future<int> getReleaseTailMs() =>
|
|
RustLib.instance.api.crateApiGetReleaseTailMs();
|
|
|
|
/// Engage or release the hard-mute clamp (SDD-094). When `true`
|
|
/// the audio engine transmits nothing regardless of mode.
|
|
Future<void> setHardMute({required bool muted}) =>
|
|
RustLib.instance.api.crateApiSetHardMute(muted: muted);
|
|
|
|
/// Update the active PTT binding (gen2 v0.9.3 / DEC-026). The
|
|
/// platform_key string is opaque to the bridge — it identifies the
|
|
/// bound key inside the platform backend and never appears in any
|
|
/// log record or diagnostic export (DEC-027 enforced at the
|
|
/// diagnostics-sanitizer layer).
|
|
Future<void> setPttBinding({
|
|
required BridgePttInputClass inputClass,
|
|
required String platformKey,
|
|
}) => RustLib.instance.api.crateApiSetPttBinding(
|
|
inputClass: inputClass,
|
|
platformKey: platformKey,
|
|
);
|
|
|
|
/// Read the current PTT capability descriptor. Returns a
|
|
/// `(level, backend_id, bound_input_class)` triple matching the
|
|
/// privacy-safe `BridgeEvent::PttCapability` event shape; useful
|
|
/// for the initial UI render before the first event arrives.
|
|
Future<(String, String, String)> pttDescriptor() =>
|
|
RustLib.instance.api.crateApiPttDescriptor();
|
|
|
|
/// Return the persisted PTT binding as a
|
|
/// `(input_class, platform_key)` pair so the UI can hydrate its
|
|
/// display state at launch (e.g. show "PTT: Space" next to the
|
|
/// badge before the user re-opens the binding dialog). Empty
|
|
/// strings mean no binding has been persisted yet.
|
|
Future<(String, String)> getPttBinding() =>
|
|
RustLib.instance.api.crateApiGetPttBinding();
|
|
|
|
/// Move our own client to `channel_id`. Optional channel password
|
|
/// for password-protected channels — pass an empty string when not
|
|
/// required.
|
|
Future<void> moveToChannel({
|
|
required BigInt channelId,
|
|
required String password,
|
|
}) => RustLib.instance.api.crateApiMoveToChannel(
|
|
channelId: channelId,
|
|
password: password,
|
|
);
|
|
|
|
/// Toggle self input-mute (microphone) on the server. Independent
|
|
/// of push-to-talk: a muted client never transmits regardless of
|
|
/// PTT state.
|
|
Future<void> setInputMuted({required bool muted}) =>
|
|
RustLib.instance.api.crateApiSetInputMuted(muted: muted);
|
|
|
|
/// Toggle self output-mute (speaker). Mutes locally *and* informs
|
|
/// the server. The server uses this for the channel icon next to
|
|
/// the client name; the local mute kicks in immediately even
|
|
/// before the server acknowledges.
|
|
Future<void> setOutputMuted({required bool muted}) =>
|
|
RustLib.instance.api.crateApiSetOutputMuted(muted: muted);
|
|
|
|
/// Set master output gain. `1.0` is unity, `0.0` is silent. Values
|
|
/// above `1.0` amplify and can clip downstream. Errors when audio
|
|
/// is not started.
|
|
Future<void> setOutputGain({required double gain}) =>
|
|
RustLib.instance.api.crateApiSetOutputGain(gain: gain);
|
|
|
|
/// User-initiated diagnostic export. Returns a multi-line text
|
|
/// blob, redacted per the production policy, that the user can
|
|
/// share or copy. DEC-016 forbids automatic uploads — this is the
|
|
/// only path that surfaces logs.
|
|
///
|
|
/// SDD-112 item 10 / SDD-113 item 7 / SDD-116 item 3: when an
|
|
/// Android voice session is open this export embeds the
|
|
/// `[audio.android]` verification-matrix fragment (requested /
|
|
/// achieved stream config, per-effect engagement, latency tier).
|
|
/// On non-Android targets or before a voice session opens the
|
|
/// section is omitted. Per SDD-090 every field in that section is
|
|
/// a device-side technical scalar — no PII admitted.
|
|
String exportDiagnostics() => RustLib.instance.api.crateApiExportDiagnostics();
|
|
|
|
/// Wire the identity persistence store to a platform-private
|
|
/// directory. Should be called once on app start after Flutter has
|
|
/// resolved `getApplicationSupportDirectory()` (or equivalent).
|
|
///
|
|
/// Subsequent [`connect`] calls will reuse the persisted identity,
|
|
/// or generate-and-persist a fresh one on first use. This keeps the
|
|
/// server-visible UID stable across app restarts.
|
|
///
|
|
/// Beta caveat: the identity is stored as a plain file (mode 0600
|
|
/// on Unix). It is *not* encrypted at rest. RISK-PoC-002 documents
|
|
/// this gap; the v0.4 storage rework lands the proper Secret
|
|
/// Service + Android Keystore + iOS Keychain backends.
|
|
Future<void> initStorage({required String dir}) =>
|
|
RustLib.instance.api.crateApiInitStorage(dir: dir);
|
|
|
|
/// List persisted bookmarks.
|
|
Future<List<BridgeBookmark>> listBookmarks() =>
|
|
RustLib.instance.api.crateApiListBookmarks();
|
|
|
|
/// Insert a bookmark and return its assigned id. The `id` field on
|
|
/// the input is ignored.
|
|
Future<PlatformInt64> addBookmark({required BridgeBookmark b}) =>
|
|
RustLib.instance.api.crateApiAddBookmark(b: b);
|
|
|
|
/// Update an existing bookmark.
|
|
Future<void> updateBookmark({required BridgeBookmark b}) =>
|
|
RustLib.instance.api.crateApiUpdateBookmark(b: b);
|
|
|
|
/// Delete a bookmark by id.
|
|
Future<void> deleteBookmark({required PlatformInt64 id}) =>
|
|
RustLib.instance.api.crateApiDeleteBookmark(id: id);
|
|
|
|
/// Notify the core of the latest OS-reported connectivity state.
|
|
/// Called by the Flutter side from `connectivity_plus` callbacks.
|
|
/// The core's supervisor uses this to (a) pre-charge the watchdog
|
|
/// on Offline and (b) short-circuit reconnect backoff on Online.
|
|
void setNetworkState({required BridgeNetworkState state}) =>
|
|
RustLib.instance.api.crateApiSetNetworkState(state: state);
|
|
|
|
/// Subscribe to lifecycle events. Each call yields a fresh
|
|
/// subscription; multiple subscribers are supported. On slow
|
|
/// consumers, events are dropped rather than blocking the supervisor
|
|
/// (consistent with `tokio::sync::broadcast::Receiver` semantics).
|
|
Stream<BridgeEvent> eventsStream() =>
|
|
RustLib.instance.api.crateApiEventsStream();
|
|
|
|
/// Read audio statistics. Errors if no connection or audio not started.
|
|
Future<BridgeAudioStats> audioStats() =>
|
|
RustLib.instance.api.crateApiAudioStats();
|
|
|
|
/// Statistics from the audio engine.
|
|
class BridgeAudioStats {
|
|
/// Number of Opus frames sent since audio started.
|
|
final int framesSent;
|
|
|
|
/// Number of inbound voice packets decoded.
|
|
final int framesReceived;
|
|
|
|
/// Current push-to-talk state.
|
|
final bool pttActive;
|
|
|
|
const BridgeAudioStats({
|
|
required this.framesSent,
|
|
required this.framesReceived,
|
|
required this.pttActive,
|
|
});
|
|
|
|
@override
|
|
int get hashCode =>
|
|
framesSent.hashCode ^ framesReceived.hashCode ^ pttActive.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is BridgeAudioStats &&
|
|
runtimeType == other.runtimeType &&
|
|
framesSent == other.framesSent &&
|
|
framesReceived == other.framesReceived &&
|
|
pttActive == other.pttActive;
|
|
}
|
|
|
|
/// Bookmark DTO mirroring [`chanora_core::Bookmark`].
|
|
class BridgeBookmark {
|
|
/// Row id assigned by SQLite. Use `0` when adding new rows;
|
|
/// the returned id is then meaningful.
|
|
final PlatformInt64 id;
|
|
|
|
/// User-facing label.
|
|
final String displayName;
|
|
|
|
/// `hostname[:port]` or TSDNS name.
|
|
final String host;
|
|
|
|
/// Nickname to use for this bookmark.
|
|
final String nickname;
|
|
|
|
/// Optional remembered password. Empty string = none.
|
|
final String password;
|
|
|
|
const BridgeBookmark({
|
|
required this.id,
|
|
required this.displayName,
|
|
required this.host,
|
|
required this.nickname,
|
|
required this.password,
|
|
});
|
|
|
|
@override
|
|
int get hashCode =>
|
|
id.hashCode ^
|
|
displayName.hashCode ^
|
|
host.hashCode ^
|
|
nickname.hashCode ^
|
|
password.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is BridgeBookmark &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
displayName == other.displayName &&
|
|
host == other.host &&
|
|
nickname == other.nickname &&
|
|
password == other.password;
|
|
}
|
|
|
|
/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo`
|
|
/// but with primitive `u64` ids so the Dart side gets `BigInt`s
|
|
/// without any wrapper-type ceremony.
|
|
class BridgeChannel {
|
|
/// Stable channel id.
|
|
final BigInt id;
|
|
|
|
/// Parent channel id; 0 means top-level.
|
|
final BigInt parent;
|
|
|
|
/// Display name.
|
|
final String name;
|
|
|
|
/// Server-side ordering hint.
|
|
final PlatformInt64 order;
|
|
|
|
const BridgeChannel({
|
|
required this.id,
|
|
required this.parent,
|
|
required this.name,
|
|
required this.order,
|
|
});
|
|
|
|
@override
|
|
int get hashCode =>
|
|
id.hashCode ^ parent.hashCode ^ name.hashCode ^ order.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is BridgeChannel &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
parent == other.parent &&
|
|
name == other.name &&
|
|
order == other.order;
|
|
}
|
|
|
|
/// Client as seen by Dart.
|
|
class BridgeClient {
|
|
/// Stable client id.
|
|
final BigInt id;
|
|
|
|
/// Channel id the client is currently in.
|
|
final BigInt channel;
|
|
|
|
/// Nickname.
|
|
final String name;
|
|
|
|
/// True for TeamSpeak ServerQuery clients.
|
|
final bool isServerQuery;
|
|
|
|
const BridgeClient({
|
|
required this.id,
|
|
required this.channel,
|
|
required this.name,
|
|
required this.isServerQuery,
|
|
});
|
|
|
|
@override
|
|
int get hashCode =>
|
|
id.hashCode ^ channel.hashCode ^ name.hashCode ^ isServerQuery.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is BridgeClient &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
channel == other.channel &&
|
|
name == other.name &&
|
|
isServerQuery == other.isServerQuery;
|
|
}
|
|
|
|
@freezed
|
|
sealed class BridgeEvent with _$BridgeEvent {
|
|
const BridgeEvent._();
|
|
|
|
/// Initial connect succeeded, or a reconnect attempt succeeded.
|
|
const factory BridgeEvent.connected({
|
|
/// Server name reported by the server snapshot.
|
|
required String serverName,
|
|
}) = BridgeEvent_Connected;
|
|
|
|
/// Connection lost; supervisor will retry.
|
|
const factory BridgeEvent.lost({
|
|
/// Reason classification from the protocol layer.
|
|
required String reason,
|
|
}) = BridgeEvent_Lost;
|
|
|
|
/// Supervisor is sleeping before its next reconnect attempt.
|
|
const factory BridgeEvent.reconnecting({
|
|
/// 1-based attempt counter for the current outage.
|
|
required int attempt,
|
|
|
|
/// Seconds the supervisor will sleep before this attempt.
|
|
required int delaySecs,
|
|
}) = BridgeEvent_Reconnecting;
|
|
|
|
/// Session ended (user-requested disconnect or unrecoverable).
|
|
const factory BridgeEvent.disconnected({
|
|
/// Reason classification.
|
|
required String reason,
|
|
}) = BridgeEvent_Disconnected;
|
|
|
|
/// Audio engine started.
|
|
const factory BridgeEvent.audioStarted() = BridgeEvent_AudioStarted;
|
|
|
|
/// Audio engine stopped.
|
|
const factory BridgeEvent.audioStopped() = BridgeEvent_AudioStopped;
|
|
|
|
/// Snapshot probe observed a change in channel/client counts.
|
|
/// UI uses this to drive an auto-refresh without active
|
|
/// polling.
|
|
const factory BridgeEvent.snapshotChanged({
|
|
/// Latest channel count.
|
|
required int channels,
|
|
|
|
/// Latest client count.
|
|
required int clients,
|
|
}) = BridgeEvent_SnapshotChanged;
|
|
|
|
/// Detected desktop Push-to-Talk capability (gen2 v0.9.3,
|
|
/// DEC-023..028). The fields carry only privacy-safe values per
|
|
/// DEC-027: the capability level, a stable backend identifier,
|
|
/// and the bound input class. No key codes, scan codes, or
|
|
/// virtual-key values cross this boundary.
|
|
const factory BridgeEvent.pttCapability({
|
|
/// Stable capability identifier (`"L0Focused"`,
|
|
/// `"L1GlobalShortcut"`, `"L2GlobalHoldToTalk"`,
|
|
/// `"L3GlobalWithMouseButtons"`, or `"L4DeviceAware"`).
|
|
required String level,
|
|
|
|
/// Stable backend identifier (e.g. `"focused"`).
|
|
required String backendId,
|
|
|
|
/// Coarse bound input class (e.g. `"keyboard"`,
|
|
/// `"mouse-side-button"`); empty when no binding is active.
|
|
required String boundInputClass,
|
|
}) = BridgeEvent_PttCapability;
|
|
|
|
/// Voice subsystem state snapshot (SDD-094). The Flutter
|
|
/// VoiceBar listens to this stream.
|
|
const factory BridgeEvent.voiceState({
|
|
/// True when the session is currently joined to a voice
|
|
/// channel and the audio engine is running.
|
|
required bool inChannel,
|
|
|
|
/// Active transmit mode.
|
|
required BridgeTransmitMode transmitMode,
|
|
|
|
/// True when the hard-mute clamp is engaged.
|
|
required bool mute,
|
|
|
|
/// Current release-tail in milliseconds (0..=500).
|
|
required int releaseTailMs,
|
|
}) = BridgeEvent_VoiceState;
|
|
|
|
/// iOS audio interruption state (SDD-101).
|
|
const factory BridgeEvent.interruptionState({
|
|
/// True when interruption began, false when it ended.
|
|
required bool began,
|
|
|
|
/// Resume recommendation from the platform. False on begin.
|
|
required bool shouldResume,
|
|
}) = BridgeEvent_InterruptionState;
|
|
|
|
/// SDD-106 §5: resolved platform-level permission state. On
|
|
/// Android this is published by the JNI hook in
|
|
/// [`crate::permission_jni`] whenever
|
|
/// `AndroidPermissionRequester` reports a state transition
|
|
/// (initial grant, denial, permanent denial, or mid-session
|
|
/// revocation). The audio engine's `TransmitModeSelector`
|
|
/// observes the `RECORD_AUDIO` variant of this event as an
|
|
/// authoritative clamp on the transmit gate per SDD-106 §6
|
|
/// (and SRS-209's listen-only fail-safe).
|
|
///
|
|
/// Carries the canonical Android permission string in
|
|
/// `permission` (e.g. `"android.permission.RECORD_AUDIO"`)
|
|
/// and the resolved state in `state`. No raw user input,
|
|
/// timestamps, or other PII cross this boundary.
|
|
const factory BridgeEvent.permissionState({
|
|
/// Canonical Android permission identifier.
|
|
required String permission,
|
|
|
|
/// Resolved permission state.
|
|
required PermissionStateKind state,
|
|
}) = BridgeEvent_PermissionState;
|
|
}
|
|
|
|
/// Coarse OS-reported network state. Mirrors
|
|
/// [`chanora_core::NetworkState`] across the bridge.
|
|
enum BridgeNetworkState {
|
|
/// No signal seen yet.
|
|
unknown,
|
|
|
|
/// OS reports a usable network.
|
|
online,
|
|
|
|
/// OS reports no network.
|
|
offline,
|
|
}
|
|
|
|
/// Coarse PTT input class (gen2 v0.9.3 / DEC-026). Stable strings;
|
|
/// the bridge never carries raw key codes.
|
|
enum BridgePttInputClass {
|
|
/// No binding is active.
|
|
none,
|
|
|
|
/// A keyboard key.
|
|
keyboard,
|
|
|
|
/// A mouse side button (Mouse4 / Mouse5).
|
|
mouseSideButton,
|
|
}
|
|
|
|
/// Server snapshot as seen by Dart.
|
|
class BridgeSnapshot {
|
|
/// Server name.
|
|
final String serverName;
|
|
|
|
/// Welcome banner text.
|
|
final String welcomeMessage;
|
|
|
|
/// Server platform (e.g. "Linux").
|
|
final String platform;
|
|
|
|
/// Server version string.
|
|
final String version;
|
|
|
|
/// Channels currently known.
|
|
final List<BridgeChannel> channels;
|
|
|
|
/// Clients currently known.
|
|
final List<BridgeClient> clients;
|
|
|
|
/// Our own client id. Useful for the UI to highlight our row
|
|
/// in the client list and to know which channel we are in
|
|
/// without trusting the optimistic local state.
|
|
final BigInt ownClientId;
|
|
|
|
const BridgeSnapshot({
|
|
required this.serverName,
|
|
required this.welcomeMessage,
|
|
required this.platform,
|
|
required this.version,
|
|
required this.channels,
|
|
required this.clients,
|
|
required this.ownClientId,
|
|
});
|
|
|
|
@override
|
|
int get hashCode =>
|
|
serverName.hashCode ^
|
|
welcomeMessage.hashCode ^
|
|
platform.hashCode ^
|
|
version.hashCode ^
|
|
channels.hashCode ^
|
|
clients.hashCode ^
|
|
ownClientId.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is BridgeSnapshot &&
|
|
runtimeType == other.runtimeType &&
|
|
serverName == other.serverName &&
|
|
welcomeMessage == other.welcomeMessage &&
|
|
platform == other.platform &&
|
|
version == other.version &&
|
|
channels == other.channels &&
|
|
clients == other.clients &&
|
|
ownClientId == other.ownClientId;
|
|
}
|
|
|
|
/// Voice transmit mode mirror (SDD-095). Schema-controlled enum;
|
|
/// the wire encoding matches [`chanora_core::TransmitMode::as_u8`].
|
|
enum BridgeTransmitMode {
|
|
/// Push-to-talk (default).
|
|
ptt,
|
|
|
|
/// Continuous transmission while in channel and not muted.
|
|
continuous,
|
|
|
|
/// Voice-activity detection — reserved per DEC-030; v1 behaves
|
|
/// as `Continuous`.
|
|
voiceActivity,
|
|
}
|
|
|
|
/// Schema-controlled mirror of the Kotlin
|
|
/// `AndroidPermissionRequester.PermissionState` sealed class
|
|
/// (SDD-106 §5). Crosses the bridge as an enum so the Dart side can
|
|
/// `switch` on it exhaustively without parsing strings.
|
|
enum PermissionStateKind {
|
|
/// Permission granted by the user; capture may proceed.
|
|
granted,
|
|
|
|
/// Permission denied (re-promptable).
|
|
denied,
|
|
|
|
/// Permission permanently denied — the UI is expected to
|
|
/// deep-link to system settings (SDD-106 §3).
|
|
permanentlyDenied,
|
|
|
|
/// Any state string that did not match the contract above.
|
|
/// Treated identically to `Denied` by the transmit clamp
|
|
/// (fail-safe per SRS-209).
|
|
unknown,
|
|
}
|