fix(android): adapt fold layout and diagnostics
This commit is contained in:
@@ -1234,7 +1234,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
final headerActions = [
|
||||
if (_phase == _Phase.connected &&
|
||||
_inChannel &&
|
||||
MediaQuery.of(context).size.width < 840.0) ...[
|
||||
MediaQuery.of(context).size.width < 600.0) ...[
|
||||
IconButton(
|
||||
tooltip: l10n.voiceHardMuteLabel,
|
||||
icon: Icon(_hardMute ? Icons.mic_off : Icons.mic),
|
||||
@@ -1268,7 +1268,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
|
||||
final bodyContent = LayoutBuilder(
|
||||
builder: (ctx, bodyConstraints) {
|
||||
const wideBreakpoint = 840.0;
|
||||
const wideBreakpoint = 600.0;
|
||||
final isWideSnapshot =
|
||||
bodyConstraints.maxWidth >= wideBreakpoint &&
|
||||
_phase == _Phase.connected &&
|
||||
@@ -1390,9 +1390,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
pendingVoiceChannelId: _pendingVoiceChannelId,
|
||||
hasJoinPending: _pendingVoiceChannelId != null,
|
||||
canJoinVoiceChannel: _canJoinVoiceChannel,
|
||||
canLeaveVoiceChannel: _canLeaveVoiceChannel,
|
||||
onJoinChannel: _onJoinChannel,
|
||||
onLeaveVoice: _onLeaveVoice,
|
||||
);
|
||||
const voiceBarWidthWide = 320.0;
|
||||
if (constraints.maxWidth >= wideBreakpoint) {
|
||||
@@ -2047,9 +2045,7 @@ class _SnapshotView extends StatelessWidget {
|
||||
required this.pendingVoiceChannelId,
|
||||
required this.hasJoinPending,
|
||||
required this.canJoinVoiceChannel,
|
||||
required this.canLeaveVoiceChannel,
|
||||
required this.onJoinChannel,
|
||||
required this.onLeaveVoice,
|
||||
});
|
||||
|
||||
final rust.BridgeSnapshot snapshot;
|
||||
@@ -2057,9 +2053,7 @@ class _SnapshotView extends StatelessWidget {
|
||||
final BigInt? pendingVoiceChannelId;
|
||||
final bool hasJoinPending;
|
||||
final bool canJoinVoiceChannel;
|
||||
final bool canLeaveVoiceChannel;
|
||||
final ValueChanged<rust.BridgeChannel> onJoinChannel;
|
||||
final VoidCallback onLeaveVoice;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -2136,27 +2130,22 @@ class _SnapshotView extends StatelessWidget {
|
||||
),
|
||||
title: Text(ch.name),
|
||||
subtitle: Text('id=${ch.id} parent=${ch.parent}'),
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
ch.id == currentVoiceChannelId
|
||||
? Icons.logout
|
||||
: ch.id == pendingVoiceChannelId
|
||||
? Icons.hourglass_top
|
||||
: Icons.login,
|
||||
),
|
||||
tooltip: ch.id == currentVoiceChannelId
|
||||
? l10n.leaveChannelAction
|
||||
: ch.id == pendingVoiceChannelId
|
||||
? l10n.statusConnecting
|
||||
: l10n.joinChannelAction,
|
||||
onPressed: hasJoinPending
|
||||
? null
|
||||
: ch.id == pendingVoiceChannelId
|
||||
? null
|
||||
: ch.id == currentVoiceChannelId
|
||||
? (canLeaveVoiceChannel ? onLeaveVoice : null)
|
||||
: () => onJoinChannel(ch),
|
||||
),
|
||||
trailing: ch.id == currentVoiceChannelId
|
||||
? null
|
||||
: IconButton(
|
||||
icon: Icon(
|
||||
ch.id == pendingVoiceChannelId
|
||||
? Icons.hourglass_top
|
||||
: Icons.login,
|
||||
),
|
||||
tooltip: ch.id == pendingVoiceChannelId
|
||||
? l10n.statusConnecting
|
||||
: l10n.joinChannelAction,
|
||||
onPressed:
|
||||
hasJoinPending || ch.id == pendingVoiceChannelId
|
||||
? null
|
||||
: () => onJoinChannel(ch),
|
||||
),
|
||||
selected: ch.id == currentVoiceChannelId,
|
||||
onTap:
|
||||
hasJoinPending ||
|
||||
|
||||
@@ -440,14 +440,13 @@ impl AndroidVoiceUnit {
|
||||
let input_share = share_from_oboe(input_stream.get_sharing_mode());
|
||||
let input_sample_rate = input_stream.get_sample_rate();
|
||||
let input_frames_per_burst = input_stream.get_frames_per_burst();
|
||||
let session_id = match input_stream.get_session_id() {
|
||||
SessionId::None => None,
|
||||
// `SessionId::Allocate` is the request value; once a
|
||||
// stream is open, oboe returns the concrete allocated
|
||||
// id via `SessionId::Id(i32)` in some versions or via
|
||||
// a getter — defensively match.
|
||||
other => session_id_value(other),
|
||||
};
|
||||
let session_id = None;
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
"android: oboe-rs get_session_id is skipped because oboe 0.6.1 \
|
||||
panics on some Android allocated-session values; hardware \
|
||||
effects are disabled for this stream"
|
||||
);
|
||||
|
||||
// --- Open output stream (SDD-112) --------------------------
|
||||
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 -----------------------------
|
||||
//
|
||||
// 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
|
||||
/// for Alpha (DEC-006 single-connection invariant); the Mutex inside
|
||||
/// `ChanoraSession` enforces the connection-count invariant.
|
||||
@@ -177,6 +211,7 @@ pub fn bridge_init() {
|
||||
}
|
||||
}
|
||||
|
||||
install_panic_diagnostic_hook();
|
||||
info!(target: "chanora_bridge", "bridge initialised");
|
||||
if let Some(p) = log_file_path() {
|
||||
info!(target: "chanora_bridge", path = %p.display(), "log file path");
|
||||
@@ -415,7 +450,7 @@ pub async fn connect(
|
||||
let snap = runtime()
|
||||
.spawn(async move { session().connect(cfg).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("connect", e))??;
|
||||
Ok(snap.into())
|
||||
}
|
||||
|
||||
@@ -424,7 +459,7 @@ pub async fn snapshot() -> Result<BridgeSnapshot, BridgeError> {
|
||||
let snap = runtime()
|
||||
.spawn(async { session().snapshot().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("snapshot", e))??;
|
||||
Ok(snap.into())
|
||||
}
|
||||
|
||||
@@ -433,7 +468,7 @@ pub async fn disconnect() -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async { session().disconnect().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("disconnect", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -488,7 +523,7 @@ pub async fn set_ptt(active: bool) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_ptt(active).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_ptt", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -545,7 +580,7 @@ pub async fn voice_join(channel_id: u64, password: String) -> Result<(), BridgeE
|
||||
runtime()
|
||||
.spawn(async move { session().voice_join(channel_id, pw).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("voice_join", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -555,7 +590,7 @@ pub async fn voice_leave() -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async { session().voice_leave().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("voice_leave", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -564,7 +599,7 @@ pub async fn set_transmit_mode(mode: BridgeTransmitMode) -> Result<(), BridgeErr
|
||||
runtime()
|
||||
.spawn(async move { session().set_transmit_mode(mode.into()).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_transmit_mode", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -584,7 +619,7 @@ pub async fn set_release_tail_ms(ms: u32) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_release_tail_ms(ms).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_release_tail_ms", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -602,7 +637,7 @@ pub async fn set_hard_mute(muted: bool) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_hard_mute(muted).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_hard_mute", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -644,7 +679,7 @@ pub async fn set_ptt_binding(
|
||||
runtime()
|
||||
.spawn(async move { session().set_ptt_binding(binding).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_ptt_binding", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -683,7 +718,7 @@ pub async fn move_to_channel(channel_id: u64, password: String) -> Result<(), Br
|
||||
runtime()
|
||||
.spawn(async move { session().move_to_channel(channel_id, pw).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("move_to_channel", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -694,7 +729,7 @@ pub async fn set_input_muted(muted: bool) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_self_muted(Some(muted), None).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_input_muted", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -706,7 +741,7 @@ pub async fn set_output_muted(muted: bool) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_self_muted(None, Some(muted)).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_output_muted", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -717,7 +752,7 @@ pub async fn set_output_gain(gain: f32) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().set_output_gain(gain).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("set_output_gain", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -790,7 +825,7 @@ pub async fn init_storage(dir: String) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().init_storage(&dir).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("init_storage", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -843,7 +878,7 @@ pub async fn list_bookmarks() -> Result<Vec<BridgeBookmark>, BridgeError> {
|
||||
let v = runtime()
|
||||
.spawn(async { session().list_bookmarks().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())
|
||||
}
|
||||
|
||||
@@ -854,7 +889,7 @@ pub async fn add_bookmark(b: BridgeBookmark) -> Result<i64, BridgeError> {
|
||||
let id = runtime()
|
||||
.spawn(async move { session().add_bookmark(core_b).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("add_bookmark", e))??;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -864,7 +899,7 @@ pub async fn update_bookmark(b: BridgeBookmark) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().update_bookmark(core_b).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("update_bookmark", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -873,7 +908,7 @@ pub async fn delete_bookmark(id: i64) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async move { session().delete_bookmark(id).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("delete_bookmark", e))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1273,7 +1308,7 @@ pub async fn audio_stats() -> Result<BridgeAudioStats, BridgeError> {
|
||||
let (s, r, p) = runtime()
|
||||
.spawn(async { session().audio_stats().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
.map_err(|e| task_join_error("audio_stats", e))??;
|
||||
Ok(BridgeAudioStats {
|
||||
frames_sent: s,
|
||||
frames_received: r,
|
||||
|
||||
Reference in New Issue
Block a user