use dioxus::prelude::*;
use crate::room::call::call_state::{CallEndReason, CallState, CallStatus, CallType};
use crate::state::app_state::AppState;
pub const ELEMENT_CALL_BASE_URL: &str = "https://call.element.io";
pub fn build_element_call_url(client: &matrix_sdk::Client, room_id: &matrix_sdk::ruma::RoomId) -> String {
let call_url = format!(
"{}/room?roomId={}",
ELEMENT_CALL_BASE_URL,
urlencoding::encode(room_id.as_str()),
);
format!(
"{}&homeserver={}",
call_url,
urlencoding::encode(client.homeserver().as_str()),
)
}
#[derive(Clone, Debug, PartialEq)]
enum ElementCallState {
Idle,
Active,
Ended,
}
#[component]
pub fn ElementCallWidget(room_id: String) -> Element {
let mut state = use_context::<Signal<AppState>>();
let mut link_copied = use_signal(|| false);
let room_name = state
.read()
.rooms
.values()
.find(|room| room.room_id.to_string() == room_id)
.map(|room| room.display_name.clone())
.unwrap_or_else(|| "Call".to_string());
let call_snapshot = state.read().call_state.clone();
let current_call_state = match call_snapshot.status {
CallStatus::InCall => ElementCallState::Active,
CallStatus::Ended(_) => ElementCallState::Ended,
_ => ElementCallState::Idle,
};
let muted = call_snapshot.is_muted;
let camera = call_snapshot.is_video_enabled;
let sharing = call_snapshot.is_screen_sharing;
let copied = *link_copied.read();
let on_hold = call_snapshot.is_on_hold;
let full_url = call_snapshot
.widget_url
.clone()
.or_else(|| {
let client = state.read().client.clone()?;
let room_id = matrix_sdk::ruma::OwnedRoomId::try_from(room_id.as_str()).ok()?;
Some(build_element_call_url(&client, room_id.as_ref()))
})
.unwrap_or_else(|| ELEMENT_CALL_BASE_URL.to_string());
let url_for_browser = full_url.clone();
let url_for_active_browser = full_url.clone();
let url_for_active_display = full_url.clone();
let url_for_copy = full_url.clone();
let rid = room_id.clone();
let on_join_call = move |_| {
let mut s = state.write();
if let Ok(parsed_room_id) = matrix_sdk::ruma::OwnedRoomId::try_from(rid.as_str()) {
let call_type = s.call_state.call_type.clone();
let widget_url = s.call_state.widget_url.clone();
s.call_state = CallState {
status: CallStatus::InCall,
room_id: Some(parsed_room_id),
call_type: call_type.clone(),
is_muted: false,
is_video_enabled: matches!(call_type, CallType::Video),
is_screen_sharing: false,
duration_secs: s.call_state.duration_secs,
call_id: s.call_state.call_id.clone(),
participants: s.call_state.participants.clone(),
is_on_hold: false,
is_group_call: true,
widget_url,
};
}
};
let on_leave_call = move |_| {
let mut s = state.write();
s.call_state.status = CallStatus::Ended(CallEndReason::HungUp);
s.call_state.is_on_hold = false;
};
let on_toggle_mute = move |_| {
let mut s = state.write();
s.call_state.is_muted = !s.call_state.is_muted;
};
let on_toggle_camera = move |_| {
let mut s = state.write();
s.call_state.is_video_enabled = !s.call_state.is_video_enabled;
};
let on_toggle_screen = move |_| {
let mut s = state.write();
s.call_state.is_screen_sharing = !s.call_state.is_screen_sharing;
};
let on_toggle_hold = move |_| {
let mut s = state.write();
let next_hold = !s.call_state.is_on_hold;
s.call_state.is_on_hold = next_hold;
if next_hold {
s.call_state.is_muted = true;
s.call_state.is_video_enabled = false;
} else if matches!(s.call_state.call_type, CallType::Video) {
s.call_state.is_video_enabled = true;
}
};
let on_copy_link = move |_| {
tracing::info!("Element Call link copied: {}", url_for_copy);
link_copied.set(true);
spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
link_copied.set(false);
});
};
rsx! {
div {
class: "element-call",
div {
class: "element-call__header",
div {
class: "element-call__branding",
span { class: "element-call__logo", "Element Call" }
}
span {
class: if matches!(current_call_state, ElementCallState::Active) {
"element-call__status element-call__status--active"
} else {
"element-call__status"
},
if on_hold {
"On hold"
} else if matches!(current_call_state, ElementCallState::Active) {
"Connected"
} else {
"Ready"
}
}
}
match current_call_state {
ElementCallState::Idle => rsx! {
div {
class: "element-call__join-screen",
div {
class: "element-call__join-info",
h3 { "Start a Call" }
p { "Open Element Call for this room. Other room members can join from the same room." }
}
div {
class: "element-call__preview",
div {
class: "element-call__preview-placeholder",
"Browser-based call"
}
}
div {
class: "element-call__join-actions",
button {
class: "btn btn--primary element-call__join-btn",
onclick: on_join_call,
"Join Call"
}
a {
class: "element-call__browser-link",
href: "{url_for_browser}",
target: "_blank",
rel: "noopener noreferrer",
"Open in browser"
}
}
}
},
ElementCallState::Active => rsx! {
div {
class: "element-call__active-banner",
div {
class: "element-call__active-banner-icon",
span { class: "element-call__pulse-dot" }
}
div {
class: "element-call__active-banner-text",
h3 { class: "element-call__active-title", "{room_name}" }
p {
class: "element-call__active-subtitle",
if on_hold {
"This call is on hold in the desktop client. Resume it before rejoining in the browser."
} else {
"Use the browser session below for the live media stream."
}
}
}
}
div {
class: "element-call__url-section",
div { class: "element-call__url-label", "Call URL" }
div {
class: "element-call__url-row",
code {
class: "element-call__url-code",
"{url_for_active_display}"
}
button {
class: if copied {
"element-call__copy-btn element-call__copy-btn--copied"
} else {
"element-call__copy-btn"
},
onclick: on_copy_link,
if copied { "Copied!" } else { "Copy Link" }
}
a {
class: "element-call__open-btn",
href: "{url_for_active_browser}",
target: "_blank",
rel: "noopener noreferrer",
"Open in browser"
}
}
}
div {
class: "element-call__participants-area",
div {
class: "element-call__self-view",
if on_hold {
"Call on hold"
} else if camera {
"Camera enabled"
} else {
"Camera off"
}
}
}
div {
class: "element-call__controls",
button {
class: if muted {
"element-call__control-btn element-call__control-btn--active"
} else {
"element-call__control-btn"
},
title: if muted { "Unmute" } else { "Mute" },
onclick: on_toggle_mute,
if muted { "Unmute" } else { "Mute" }
}
button {
class: if !camera {
"element-call__control-btn element-call__control-btn--active"
} else {
"element-call__control-btn"
},
title: if camera { "Turn off camera" } else { "Turn on camera" },
onclick: on_toggle_camera,
if camera { "Camera Off" } else { "Camera On" }
}
button {
class: if sharing {
"element-call__control-btn element-call__control-btn--active"
} else {
"element-call__control-btn"
},
title: if sharing { "Stop sharing screen" } else { "Share screen" },
onclick: on_toggle_screen,
if sharing { "Stop Share" } else { "Share Screen" }
}
button {
class: if on_hold {
"element-call__control-btn element-call__control-btn--active"
} else {
"element-call__control-btn"
},
title: if on_hold { "Resume call" } else { "Hold call" },
onclick: on_toggle_hold,
if on_hold { "Resume" } else { "Hold" }
}
button {
class: "element-call__control-btn element-call__control-btn--hangup",
title: "Leave call",
onclick: on_leave_call,
"Leave"
}
}
},
ElementCallState::Ended => rsx! {
div {
class: "element-call__ended",
h3 { "Call Ended" }
p { "You have left the Element Call session." }
div {
class: "element-call__ended-actions",
button {
class: "btn btn--primary",
onclick: move |_| {
let mut s = state.write();
s.call_state.status = CallStatus::Connecting;
s.call_state.is_on_hold = false;
},
"Rejoin"
}
}
}
},
}
}
}
}