fix(android): adapt fold layout and diagnostics
This commit is contained in:
@@ -1234,7 +1234,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
final headerActions = [
|
final headerActions = [
|
||||||
if (_phase == _Phase.connected &&
|
if (_phase == _Phase.connected &&
|
||||||
_inChannel &&
|
_inChannel &&
|
||||||
MediaQuery.of(context).size.width < 840.0) ...[
|
MediaQuery.of(context).size.width < 600.0) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: l10n.voiceHardMuteLabel,
|
tooltip: l10n.voiceHardMuteLabel,
|
||||||
icon: Icon(_hardMute ? Icons.mic_off : Icons.mic),
|
icon: Icon(_hardMute ? Icons.mic_off : Icons.mic),
|
||||||
@@ -1268,7 +1268,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
|
|
||||||
final bodyContent = LayoutBuilder(
|
final bodyContent = LayoutBuilder(
|
||||||
builder: (ctx, bodyConstraints) {
|
builder: (ctx, bodyConstraints) {
|
||||||
const wideBreakpoint = 840.0;
|
const wideBreakpoint = 600.0;
|
||||||
final isWideSnapshot =
|
final isWideSnapshot =
|
||||||
bodyConstraints.maxWidth >= wideBreakpoint &&
|
bodyConstraints.maxWidth >= wideBreakpoint &&
|
||||||
_phase == _Phase.connected &&
|
_phase == _Phase.connected &&
|
||||||
@@ -1390,9 +1390,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
|||||||
pendingVoiceChannelId: _pendingVoiceChannelId,
|
pendingVoiceChannelId: _pendingVoiceChannelId,
|
||||||
hasJoinPending: _pendingVoiceChannelId != null,
|
hasJoinPending: _pendingVoiceChannelId != null,
|
||||||
canJoinVoiceChannel: _canJoinVoiceChannel,
|
canJoinVoiceChannel: _canJoinVoiceChannel,
|
||||||
canLeaveVoiceChannel: _canLeaveVoiceChannel,
|
|
||||||
onJoinChannel: _onJoinChannel,
|
onJoinChannel: _onJoinChannel,
|
||||||
onLeaveVoice: _onLeaveVoice,
|
|
||||||
);
|
);
|
||||||
const voiceBarWidthWide = 320.0;
|
const voiceBarWidthWide = 320.0;
|
||||||
if (constraints.maxWidth >= wideBreakpoint) {
|
if (constraints.maxWidth >= wideBreakpoint) {
|
||||||
@@ -2047,9 +2045,7 @@ class _SnapshotView extends StatelessWidget {
|
|||||||
required this.pendingVoiceChannelId,
|
required this.pendingVoiceChannelId,
|
||||||
required this.hasJoinPending,
|
required this.hasJoinPending,
|
||||||
required this.canJoinVoiceChannel,
|
required this.canJoinVoiceChannel,
|
||||||
required this.canLeaveVoiceChannel,
|
|
||||||
required this.onJoinChannel,
|
required this.onJoinChannel,
|
||||||
required this.onLeaveVoice,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final rust.BridgeSnapshot snapshot;
|
final rust.BridgeSnapshot snapshot;
|
||||||
@@ -2057,9 +2053,7 @@ class _SnapshotView extends StatelessWidget {
|
|||||||
final BigInt? pendingVoiceChannelId;
|
final BigInt? pendingVoiceChannelId;
|
||||||
final bool hasJoinPending;
|
final bool hasJoinPending;
|
||||||
final bool canJoinVoiceChannel;
|
final bool canJoinVoiceChannel;
|
||||||
final bool canLeaveVoiceChannel;
|
|
||||||
final ValueChanged<rust.BridgeChannel> onJoinChannel;
|
final ValueChanged<rust.BridgeChannel> onJoinChannel;
|
||||||
final VoidCallback onLeaveVoice;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -2136,25 +2130,20 @@ class _SnapshotView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: Text(ch.name),
|
title: Text(ch.name),
|
||||||
subtitle: Text('id=${ch.id} parent=${ch.parent}'),
|
subtitle: Text('id=${ch.id} parent=${ch.parent}'),
|
||||||
trailing: IconButton(
|
trailing: ch.id == currentVoiceChannelId
|
||||||
|
? null
|
||||||
|
: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
ch.id == currentVoiceChannelId
|
ch.id == pendingVoiceChannelId
|
||||||
? Icons.logout
|
|
||||||
: ch.id == pendingVoiceChannelId
|
|
||||||
? Icons.hourglass_top
|
? Icons.hourglass_top
|
||||||
: Icons.login,
|
: Icons.login,
|
||||||
),
|
),
|
||||||
tooltip: ch.id == currentVoiceChannelId
|
tooltip: ch.id == pendingVoiceChannelId
|
||||||
? l10n.leaveChannelAction
|
|
||||||
: ch.id == pendingVoiceChannelId
|
|
||||||
? l10n.statusConnecting
|
? l10n.statusConnecting
|
||||||
: l10n.joinChannelAction,
|
: l10n.joinChannelAction,
|
||||||
onPressed: hasJoinPending
|
onPressed:
|
||||||
|
hasJoinPending || ch.id == pendingVoiceChannelId
|
||||||
? null
|
? null
|
||||||
: ch.id == pendingVoiceChannelId
|
|
||||||
? null
|
|
||||||
: ch.id == currentVoiceChannelId
|
|
||||||
? (canLeaveVoiceChannel ? onLeaveVoice : null)
|
|
||||||
: () => onJoinChannel(ch),
|
: () => onJoinChannel(ch),
|
||||||
),
|
),
|
||||||
selected: ch.id == currentVoiceChannelId,
|
selected: ch.id == currentVoiceChannelId,
|
||||||
|
|||||||
@@ -440,14 +440,13 @@ impl AndroidVoiceUnit {
|
|||||||
let input_share = share_from_oboe(input_stream.get_sharing_mode());
|
let input_share = share_from_oboe(input_stream.get_sharing_mode());
|
||||||
let input_sample_rate = input_stream.get_sample_rate();
|
let input_sample_rate = input_stream.get_sample_rate();
|
||||||
let input_frames_per_burst = input_stream.get_frames_per_burst();
|
let input_frames_per_burst = input_stream.get_frames_per_burst();
|
||||||
let session_id = match input_stream.get_session_id() {
|
let session_id = None;
|
||||||
SessionId::None => None,
|
warn!(
|
||||||
// `SessionId::Allocate` is the request value; once a
|
target: "chanora_audio",
|
||||||
// stream is open, oboe returns the concrete allocated
|
"android: oboe-rs get_session_id is skipped because oboe 0.6.1 \
|
||||||
// id via `SessionId::Id(i32)` in some versions or via
|
panics on some Android allocated-session values; hardware \
|
||||||
// a getter — defensively match.
|
effects are disabled for this stream"
|
||||||
other => session_id_value(other),
|
);
|
||||||
};
|
|
||||||
|
|
||||||
// --- Open output stream (SDD-112) --------------------------
|
// --- Open output stream (SDD-112) --------------------------
|
||||||
let output_builder = AudioStreamBuilder::default()
|
let output_builder = AudioStreamBuilder::default()
|
||||||
@@ -829,20 +828,6 @@ fn share_from_oboe(s: SharingMode) -> AchievedSharingMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session_id_value(sid: SessionId) -> Option<AudioSessionId> {
|
|
||||||
// SessionId is a Rust enum from oboe-rs. `Allocate` is the
|
|
||||||
// request token; once allocated the platform returns a positive
|
|
||||||
// integer carried in the enum's tuple variant. Match explicitly
|
|
||||||
// — `mem::transmute` would be UB on a non-`#[repr(i32)]` enum,
|
|
||||||
// even if it happens to lay out correctly today.
|
|
||||||
match sid {
|
|
||||||
SessionId::None => None,
|
|
||||||
// `SessionId::Allocate` is a request marker; treat it as
|
|
||||||
// "id not yet known" rather than fabricating one.
|
|
||||||
SessionId::Allocate => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SDD-113 hardware-effect binding -----------------------------
|
// --- SDD-113 hardware-effect binding -----------------------------
|
||||||
//
|
//
|
||||||
// We attach `AcousticEchoCanceler`, `NoiseSuppressor`,
|
// We attach `AcousticEchoCanceler`, `NoiseSuppressor`,
|
||||||
|
|||||||
@@ -32,6 +32,40 @@ fn runtime() -> &'static Runtime {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn task_join_error(task: &'static str, error: tokio::task::JoinError) -> BridgeError {
|
||||||
|
warn!(
|
||||||
|
target: "chanora_bridge",
|
||||||
|
task,
|
||||||
|
error = %error,
|
||||||
|
"runtime task failed; surfacing in diagnostics"
|
||||||
|
);
|
||||||
|
BridgeError::Unmapped(format!("join: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_panic_diagnostic_hook() {
|
||||||
|
static INSTALLED: OnceLock<()> = OnceLock::new();
|
||||||
|
let _ = INSTALLED.get_or_init(|| {
|
||||||
|
std::panic::set_hook(Box::new(|panic_info| {
|
||||||
|
let message = panic_info
|
||||||
|
.payload()
|
||||||
|
.downcast_ref::<&'static str>()
|
||||||
|
.map(|s| (*s).to_string())
|
||||||
|
.or_else(|| panic_info.payload().downcast_ref::<String>().cloned())
|
||||||
|
.unwrap_or_else(|| "non-string panic payload".to_string());
|
||||||
|
let location = panic_info
|
||||||
|
.location()
|
||||||
|
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||||
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
warn!(
|
||||||
|
target: "chanora_bridge",
|
||||||
|
panic_message = %message,
|
||||||
|
panic_location = %location,
|
||||||
|
"panic captured for diagnostic export"
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Process-wide session handle. One instance per process is enough
|
/// Process-wide session handle. One instance per process is enough
|
||||||
/// for Alpha (DEC-006 single-connection invariant); the Mutex inside
|
/// for Alpha (DEC-006 single-connection invariant); the Mutex inside
|
||||||
/// `ChanoraSession` enforces the connection-count invariant.
|
/// `ChanoraSession` enforces the connection-count invariant.
|
||||||
@@ -177,6 +211,7 @@ pub fn bridge_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_panic_diagnostic_hook();
|
||||||
info!(target: "chanora_bridge", "bridge initialised");
|
info!(target: "chanora_bridge", "bridge initialised");
|
||||||
if let Some(p) = log_file_path() {
|
if let Some(p) = log_file_path() {
|
||||||
info!(target: "chanora_bridge", path = %p.display(), "log file path");
|
info!(target: "chanora_bridge", path = %p.display(), "log file path");
|
||||||
@@ -415,7 +450,7 @@ pub async fn connect(
|
|||||||
let snap = runtime()
|
let snap = runtime()
|
||||||
.spawn(async move { session().connect(cfg).await })
|
.spawn(async move { session().connect(cfg).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("connect", e))??;
|
||||||
Ok(snap.into())
|
Ok(snap.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +459,7 @@ pub async fn snapshot() -> Result<BridgeSnapshot, BridgeError> {
|
|||||||
let snap = runtime()
|
let snap = runtime()
|
||||||
.spawn(async { session().snapshot().await })
|
.spawn(async { session().snapshot().await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("snapshot", e))??;
|
||||||
Ok(snap.into())
|
Ok(snap.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +468,7 @@ pub async fn disconnect() -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async { session().disconnect().await })
|
.spawn(async { session().disconnect().await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("disconnect", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +523,7 @@ pub async fn set_ptt(active: bool) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_ptt(active).await })
|
.spawn(async move { session().set_ptt(active).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_ptt", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +580,7 @@ pub async fn voice_join(channel_id: u64, password: String) -> Result<(), BridgeE
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().voice_join(channel_id, pw).await })
|
.spawn(async move { session().voice_join(channel_id, pw).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("voice_join", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +590,7 @@ pub async fn voice_leave() -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async { session().voice_leave().await })
|
.spawn(async { session().voice_leave().await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("voice_leave", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +599,7 @@ pub async fn set_transmit_mode(mode: BridgeTransmitMode) -> Result<(), BridgeErr
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_transmit_mode(mode.into()).await })
|
.spawn(async move { session().set_transmit_mode(mode.into()).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_transmit_mode", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,7 +619,7 @@ pub async fn set_release_tail_ms(ms: u32) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_release_tail_ms(ms).await })
|
.spawn(async move { session().set_release_tail_ms(ms).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_release_tail_ms", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +637,7 @@ pub async fn set_hard_mute(muted: bool) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_hard_mute(muted).await })
|
.spawn(async move { session().set_hard_mute(muted).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_hard_mute", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,7 +679,7 @@ pub async fn set_ptt_binding(
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_ptt_binding(binding).await })
|
.spawn(async move { session().set_ptt_binding(binding).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_ptt_binding", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +718,7 @@ pub async fn move_to_channel(channel_id: u64, password: String) -> Result<(), Br
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().move_to_channel(channel_id, pw).await })
|
.spawn(async move { session().move_to_channel(channel_id, pw).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("move_to_channel", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +729,7 @@ pub async fn set_input_muted(muted: bool) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_self_muted(Some(muted), None).await })
|
.spawn(async move { session().set_self_muted(Some(muted), None).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_input_muted", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,7 +741,7 @@ pub async fn set_output_muted(muted: bool) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_self_muted(None, Some(muted)).await })
|
.spawn(async move { session().set_self_muted(None, Some(muted)).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_output_muted", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +752,7 @@ pub async fn set_output_gain(gain: f32) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().set_output_gain(gain).await })
|
.spawn(async move { session().set_output_gain(gain).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("set_output_gain", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,7 +825,7 @@ pub async fn init_storage(dir: String) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().init_storage(&dir).await })
|
.spawn(async move { session().init_storage(&dir).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("init_storage", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,7 +878,7 @@ pub async fn list_bookmarks() -> Result<Vec<BridgeBookmark>, BridgeError> {
|
|||||||
let v = runtime()
|
let v = runtime()
|
||||||
.spawn(async { session().list_bookmarks().await })
|
.spawn(async { session().list_bookmarks().await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("list_bookmarks", e))??;
|
||||||
Ok(v.into_iter().map(Into::into).collect())
|
Ok(v.into_iter().map(Into::into).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,7 +889,7 @@ pub async fn add_bookmark(b: BridgeBookmark) -> Result<i64, BridgeError> {
|
|||||||
let id = runtime()
|
let id = runtime()
|
||||||
.spawn(async move { session().add_bookmark(core_b).await })
|
.spawn(async move { session().add_bookmark(core_b).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("add_bookmark", e))??;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,7 +899,7 @@ pub async fn update_bookmark(b: BridgeBookmark) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().update_bookmark(core_b).await })
|
.spawn(async move { session().update_bookmark(core_b).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("update_bookmark", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +908,7 @@ pub async fn delete_bookmark(id: i64) -> Result<(), BridgeError> {
|
|||||||
runtime()
|
runtime()
|
||||||
.spawn(async move { session().delete_bookmark(id).await })
|
.spawn(async move { session().delete_bookmark(id).await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("delete_bookmark", e))??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1273,7 +1308,7 @@ pub async fn audio_stats() -> Result<BridgeAudioStats, BridgeError> {
|
|||||||
let (s, r, p) = runtime()
|
let (s, r, p) = runtime()
|
||||||
.spawn(async { session().audio_stats().await })
|
.spawn(async { session().audio_stats().await })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
.map_err(|e| task_join_error("audio_stats", e))??;
|
||||||
Ok(BridgeAudioStats {
|
Ok(BridgeAudioStats {
|
||||||
frames_sent: s,
|
frames_sent: s,
|
||||||
frames_received: r,
|
frames_received: r,
|
||||||
|
|||||||
Reference in New Issue
Block a user