Skip to content

Commit 88bd90f

Browse files
JSKittyclaude
andcommitted
fix: Clear Mini App "Playing" state on Android close
The Android onMiniAppClosed JNI callback was cleaning up backend state but never emitting miniapp_realtime_status to the frontend. This left the attachment's data-playing attribute set, preventing reopening. Now mirrors the desktop Destroyed handler: removes the realtime channel, clears stale event targets, and emits is_active=false so the frontend clears the "Playing" state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6789b00 commit 88bd90f

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

src-tauri/src/android/miniapp_jni.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,25 +96,28 @@ pub extern "C" fn Java_io_vectorapp_miniapp_MiniAppManager_onMiniAppClosed(
9696

9797
info!("Mini App closed (JNI callback): {}", miniapp_id);
9898

99-
// Clean up realtime channel and instance state
99+
// Clean up realtime channel and instance state, notify frontend
100100
if let Some(app) = TAURI_APP.get() {
101101
let app = app.clone();
102102
let miniapp_id_owned = miniapp_id.clone();
103103
tauri::async_runtime::spawn(async move {
104104
let state = app.state::<crate::miniapps::state::MiniAppsState>();
105105

106-
// Read the topic before remove_instance cleans up the realtime channel state
107-
let topic = state.get_realtime_channel(&miniapp_id_owned).await;
106+
// Remove the realtime channel state (marks us as not playing)
107+
let channel_state = state.remove_realtime_channel(&miniapp_id_owned).await;
108108

109-
// Remove instance (also removes realtime channel state internally)
110-
state.remove_instance(&miniapp_id_owned).await;
109+
if let Some(channel) = channel_state {
110+
let topic_encoded = crate::miniapps::realtime::encode_topic_id(&channel.topic);
111+
112+
// Get current peer count and clear stale event target
113+
let peer_count = if let Ok(iroh) = state.realtime.get_or_init().await {
114+
iroh.clear_event_target(&channel.topic).await;
115+
let count = iroh.get_peer_count(&channel.topic).await;
111116

112-
// Leave the Iroh gossip channel if one was active (with timeout to avoid hanging)
113-
if let Some(topic) = topic {
114-
if let Ok(iroh) = state.realtime.get_or_init().await {
117+
// Leave the Iroh gossip channel (with timeout to avoid hanging)
115118
match tokio::time::timeout(
116119
tokio::time::Duration::from_secs(5),
117-
iroh.leave_channel(topic),
120+
iroh.leave_channel(channel.topic),
118121
).await {
119122
Ok(Ok(())) => {
120123
info!("[WEBXDC] Left Iroh channel on Mini App close: {}", miniapp_id_owned);
@@ -126,8 +129,26 @@ pub extern "C" fn Java_io_vectorapp_miniapp_MiniAppManager_onMiniAppClosed(
126129
warn!("[WEBXDC] Timed out leaving Iroh channel on close: {}", miniapp_id_owned);
127130
}
128131
}
132+
133+
count
134+
} else {
135+
0
136+
};
137+
138+
// Emit status update to main window so frontend clears "Playing" state
139+
if let Some(main_window) = app.get_webview_window("main") {
140+
let _ = main_window.emit("miniapp_realtime_status", serde_json::json!({
141+
"topic": topic_encoded,
142+
"peer_count": peer_count,
143+
"is_active": false,
144+
"has_pending_peers": peer_count > 0,
145+
}));
146+
info!("[WEBXDC] Emitted miniapp_realtime_status: active=false, peer_count={} for topic {}", peer_count, topic_encoded);
129147
}
130148
}
149+
150+
// Remove the instance
151+
state.remove_instance(&miniapp_id_owned).await;
131152
});
132153
}
133154
}

0 commit comments

Comments
 (0)