diff --git a/android/app/build.gradle b/android/app/build.gradle index 7c7a58a9..18033c55 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "olcxja.miarven" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2 - versionName "0.1.1" + versionCode 1 + versionName "0.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json index db64989f..ae56e047 100644 --- a/android/app/src/main/assets/public/blah/en-cat.json +++ b/android/app/src/main/assets/public/blah/en-cat.json @@ -290,24 +290,5 @@ "message.delete.failed": "Failed to delete meow", "message.react.failed": "Failed to react to meow", "message.send.failed": "Failed to send meow", - "owner.transferred": "Purrship transferred successfully", - "title.voice.chat": "voice chat :3", - "status.voice.connecting": "connecting... :3", - "status.voice.disconnected": "disconnected :3", - "action.call.mute": "mute microphone", - "action.call.camera": "camera", - "action.call.screen": "share screen", - "action.call.disconnect": "disconnect :3", - "action.call.join": "join :3", - "action.call.decline": "decline :c", - "title.incoming.call": "incoming voice chat :3", - "desc.call.in.chat": "{0} in chat :3", - "label.user.volume": "user volume", - "label.stream.volume": "screen volume", - "error.call.unsupported": "no permission or not supported: {0} :c", - "error.call.camera": "camera error: {0} :c", - "error.call.screen": "screen share error: {0} :c", - "error.call.screen.unsupported": "ur browser doesn't support screen sharing :c", - "label.stream.cam": "cam", - "label.stream.screen": "screen" -} + "owner.transferred": "Purrship transferred successfully" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index ee67f52d..42d98cb7 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -299,24 +299,5 @@ "message.delete.failed": "Failed to delete message", "message.react.failed": "Failed to react to message", "message.send.failed": "Failed to send message", - "owner.transferred": "Ownership transferred successfully", - "title.voice.chat": "Voice Chat", - "status.voice.connecting": "Connecting...", - "status.voice.disconnected": "Disconnected", - "action.call.mute": "Mute Microphone", - "action.call.camera": "Camera", - "action.call.screen": "Share Screen", - "action.call.disconnect": "Disconnect", - "action.call.join": "Join", - "action.call.decline": "Decline", - "title.incoming.call": "Incoming Voice Chat", - "desc.call.in.chat": "{0} in chat", - "label.user.volume": "User Volume", - "label.stream.volume": "Screen Volume", - "error.call.unsupported": "No permission or feature not supported: {0}", - "error.call.camera": "Camera error: {0}", - "error.call.screen": "Screen share error: {0}", - "error.call.screen.unsupported": "Screen sharing is not supported on this browser.", - "label.stream.cam": "Cam", - "label.stream.screen": "Screen" -} + "owner.transferred": "Ownership transferred successfully" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/pl-pl.json b/android/app/src/main/assets/public/blah/pl-pl.json index 7d6f14ab..d1fea3af 100644 --- a/android/app/src/main/assets/public/blah/pl-pl.json +++ b/android/app/src/main/assets/public/blah/pl-pl.json @@ -299,24 +299,5 @@ "message.delete.failed": "Nie udało się usunąć wiadomości", "message.react.failed": "Nie udało się dodać reakcji", "message.send.failed": "Nie udało się wysłać wiadomości", - "owner.transferred": "Własność przekazana pomyślnie", - "title.voice.chat": "Czat głosowy", - "status.voice.connecting": "Łączenie...", - "status.voice.disconnected": "Rozłączono", - "action.call.mute": "Wycisz mikrofon", - "action.call.camera": "Kamera", - "action.call.screen": "Udostępnij ekran", - "action.call.disconnect": "Rozłącz", - "action.call.join": "Dołącz", - "action.call.decline": "Odrzuć", - "title.incoming.call": "Połączenie głosowe", - "desc.call.in.chat": "{0} w rozmowie", - "label.user.volume": "Głośność użytkownika", - "label.stream.volume": "Głośność ekranu", - "error.call.unsupported": "Brak uprawnień lub funkcja nieobsługiwana: {0}", - "error.call.camera": "Błąd kamery: {0}", - "error.call.screen": "Błąd udostępniania ekranu: {0}", - "error.call.screen.unsupported": "Udostępnianie ekranu nie jest obsługiwane w tej przeglądarce.", - "label.stream.cam": "Kamera", - "label.stream.screen": "Ekran" -} + "owner.transferred": "Własność przekazana pomyślnie" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index f514589c..ecd51413 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -1461,8 +1461,8 @@ function showFixedContextMenu(rect, html) { fixedContextMenu.style.transition = 'none'; fixedContextMenu.style.opacity = '0'; - fixedContextMenu.style.left = '0rem'; - fixedContextMenu.style.top = '0rem'; + fixedContextMenu.style.left = '0px'; + fixedContextMenu.style.top = '0px'; fixedContextMenu.classList.add("show"); @@ -3139,7 +3139,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { let safeType = encodeURIComponent(att.type).replace(/'/g, "%27"); let safeName = encodeURIComponent(att.name).replace(/'/g, "%27"); - attachmentsHtml += `
+ attachmentsHtml += `
${iconSvg}
${escapeHtml(displayName)}
@@ -3653,12 +3653,12 @@ async function downloadAndRenderAttachment(container, msgId, attId, type, name, container.dataset.url = blobUrl; if (type.startsWith("image/")) { - container.innerHTML = ``; + container.innerHTML = ``; } else if (type.startsWith("video/")) { - container.innerHTML = ``; + container.innerHTML = ``; } else { container.innerHTML = originalHtml; - container.style.border = "0.0625rem solid var(--accent-color)"; + container.style.border = "1px solid var(--accent-color)"; let a = document.createElement("a"); a.href = blobUrl; a.download = name; @@ -3716,7 +3716,7 @@ function renderAttachmentsPreview() { } html += ` -
+
${iconHtml}
${escapeHtml(file.name)}
-
`; document.body.appendChild(incomingUi); - requestAnimationFrame(() => { - requestAnimationFrame(() => incomingUi.classList.add("show")); - }); - - resolveCallerInfo(roomId).then(({name, pfpUrl}) => { - if (!document.body.contains(incomingUi)) return; - let nameEl = incomingUi.querySelector(".incoming-call-name"); - let avatarEl = incomingUi.querySelector(".incoming-call-avatar"); - if (nameEl) nameEl.innerText = name; - if (avatarEl && pfpUrl) avatarEl.src = pfpUrl; - }); -} - -function dismissIncomingCall() { - let el = document.getElementById("incoming-call-popup"); - if (!el || el.classList.contains("hide")) return; - el.classList.remove("show"); - el.classList.add("hide"); - setTimeout(() => el.remove(), 300); -} - -function joinIncomingCall(roomId) { - dismissIncomingCall(); - initiateVoiceCall(roomId); } async function sendVoiceSignalToUser(targetUserId, roomId) { @@ -662,39 +503,6 @@ async function handleRoomSignal(data) { } } -async function resolveVoiceChatTitle() { - try { - if (currentVoiceCallType === 'groups') { - if (!currentVoiceCallId || !window.fetchEncrypted) return null; - let infoPayload = await fetchEncrypted("group/info", currentVoiceCallId); - if (infoPayload && infoPayload.startsWith("{")) { - return JSON.parse(infoPayload).string2 || null; - } - return null; - } - if (!currentVoiceCallId) return null; - let parts = String(currentVoiceCallId).split('_'); - let myId = `${window.id};${window.host}`; - let targetId = parts[0] === myId ? parts[1] : parts[0]; - if (!targetId) return null; - let name = window.dmUsernameCache ? window.dmUsernameCache[targetId] : null; - if (!name && window.fetchAsync) { - name = await window.fetchAsync(`${window.url}/idtoname?id=${targetId}`); - if (name && !name.startsWith("error:") && window.dmUsernameCache) window.dmUsernameCache[targetId] = name; - } - if (!name || name.startsWith("error:")) return null; - return name.split(':')[0]; - } catch (e) { - return null; - } -} - -async function updateVoiceChatTitle() { - let name = await resolveVoiceChatTitle(); - let el = document.querySelector("#voice-header .voice-title span"); - if (el && name) el.innerText = name; -} - function showVoiceUI() { if (voiceUiContainer) return; @@ -702,49 +510,40 @@ function showVoiceUI() { voiceUiContainer.id = "voice-call-popup"; voiceUiContainer.innerHTML = ` -
-
- ${ICONS.call({width: "1.1rem", height: "1.1rem"})} - ${processBlah("title.voice.chat")} -
-
- - - ${processBlah("status.voice.connecting")} +
+
+ Voice Chat
+
Connecting...
-
-
+
+
`; - + document.body.appendChild(voiceUiContainer); - requestAnimationFrame(() => { - requestAnimationFrame(() => voiceUiContainer.classList.add("show")); - }); let header = document.getElementById("voice-header"); + let isDragging = false; let startX, startY, initialLeft, initialTop; - - header.addEventListener('pointerdown', (e) => { - if (e.button !== undefined && e.button !== 0) return; - voiceDragState = true; - try { header.setPointerCapture(e.pointerId); } catch (err) {} + + header.addEventListener('mousedown', (e) => { + isDragging = true; let rect = voiceUiContainer.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; @@ -754,149 +553,17 @@ function showVoiceUI() { voiceUiContainer.style.bottom = "auto"; voiceUiContainer.style.left = initialLeft + "px"; voiceUiContainer.style.top = initialTop + "px"; - clampVoiceWindowToViewport(voiceUiContainer); }); - - header.addEventListener('pointermove', (e) => { - if (!voiceDragState) return; + + window.addEventListener('mousemove', (e) => { + if (!isDragging) return; let dx = e.clientX - startX; let dy = e.clientY - startY; voiceUiContainer.style.left = (initialLeft + dx) + "px"; voiceUiContainer.style.top = (initialTop + dy) + "px"; - clampVoiceWindowToViewport(voiceUiContainer); }); - - let stopDrag = () => { voiceDragState = false; }; - header.addEventListener('pointerup', stopDrag); - header.addEventListener('pointercancel', stopDrag); - - ensureVoiceGridObserver(); - - setupVoiceWindowResize(); -} - -function setupVoiceWindowResize() { - const popup = voiceUiContainer; - if (!popup || popup.dataset.resizeInit) return; - popup.dataset.resizeInit = "1"; - - const minW = () => parseFloat(getComputedStyle(popup).minWidth) || 200; - const minH = () => parseFloat(getComputedStyle(popup).minHeight) || 160; - - const applySize = (l, t, w, h) => { - const W = window.innerWidth, H = window.innerHeight; - w = Math.round(Math.min(Math.max(w, minW()), W)); - h = Math.round(Math.min(Math.max(h, minH()), H)); - l = Math.round(Math.min(Math.max(l, 0), W - w)); - t = Math.round(Math.min(Math.max(t, 0), H - h)); - popup.style.right = "auto"; - popup.style.bottom = "auto"; - popup.style.width = w + "px"; - popup.style.height = h + "px"; - popup.style.left = l + "px"; - popup.style.top = t + "px"; - }; - - let edgeResizing = false; - - ["n", "s", "e", "w", "ne", "nw", "se", "sw"].forEach((dir) => { - const handle = document.createElement("div"); - handle.className = `voice-resize-handle voice-resize-${dir}`; - popup.appendChild(handle); - - handle.addEventListener("pointerdown", (e) => { - if (e.button !== undefined && e.button !== 0) return; - e.stopPropagation(); - e.preventDefault(); - edgeResizing = true; - const sx = e.clientX, sy = e.clientY; - const rect = popup.getBoundingClientRect(); - const sw = rect.width, sh = rect.height, sl = rect.left, st = rect.top; - try { handle.setPointerCapture(e.pointerId); } catch (err) {} - - const onMove = (ev) => { - const dx = ev.clientX - sx, dy = ev.clientY - sy; - let w = sw, h = sh, l = sl, t = st; - if (dir.includes("e")) w = sw + dx; - if (dir.includes("s")) h = sh + dy; - if (dir.includes("w")) { - w = sw - dx; - l = sl + (sw - w); - } - if (dir.includes("n")) { - h = sh - dy; - t = st + (sh - h); - } - applySize(l, t, w, h); - }; - const onUp = () => { - edgeResizing = false; - handle.removeEventListener("pointermove", onMove); - handle.removeEventListener("pointerup", onUp); - handle.removeEventListener("pointercancel", onUp); - }; - handle.addEventListener("pointermove", onMove); - handle.addEventListener("pointerup", onUp); - handle.addEventListener("pointercancel", onUp); - }); - }); - - // pinch-to-resize (dwa palce jak zoom) - const pointers = new Map(); - let pinch = null; - let suppressClick = false; - - popup.addEventListener("pointerdown", (e) => { - pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); - if (pointers.size === 2 && !pinch && !voiceDragState && !edgeResizing) { - const [a, b] = [...pointers.values()]; - const rect = popup.getBoundingClientRect(); - pinch = { - dist: Math.max(20, Math.hypot(a.x - b.x, a.y - b.y)), - w: rect.width, - h: rect.height - }; - suppressClick = true; - } - if (pointers.size >= 2 || pinch) { - try { popup.setPointerCapture(e.pointerId); } catch (err) {} - } - }); - - popup.addEventListener("pointermove", (e) => { - if (!pointers.has(e.pointerId)) return; - pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); - if (pointers.size === 2 && pinch) { - const [a, b] = [...pointers.values()]; - const scale = Math.max(0.05, Math.hypot(a.x - b.x, a.y - b.y)) / pinch.dist; - const rect = popup.getBoundingClientRect(); - const cx = rect.left + rect.width / 2; - const cy = rect.top + rect.height / 2; - applySize(cx - pinch.w * scale / 2, cy - pinch.h * scale / 2, pinch.w * scale, pinch.h * scale); - } - }); - - const releasePointer = (e) => { - pointers.delete(e.pointerId); - if (pointers.size < 2) pinch = null; - if (pointers.size === 0) setTimeout(() => { suppressClick = false; }, 250); - }; - popup.addEventListener("pointerup", releasePointer); - popup.addEventListener("pointercancel", releasePointer); - - popup.addEventListener("click", (e) => { - if (suppressClick) { - e.stopPropagation(); - e.preventDefault(); - suppressClick = false; - } - }, true); - - if (voiceResizeAbort) voiceResizeAbort.abort(); - voiceResizeAbort = new AbortController(); - window.addEventListener("resize", () => { - clampVoiceWindowToViewport(popup); - }, { signal: voiceResizeAbort.signal }); + + window.addEventListener('mouseup', () => { isDragging = false; }); } function updateVoiceUIUsers() { @@ -907,10 +574,9 @@ function updateVoiceUIUsers() { let div = document.getElementById(`voice-tile-${u}`); if (!div) { div = document.createElement("div"); - div.className = "voice-tile tile-enter"; + div.className = "voice-tile"; div.id = `voice-tile-${u}`; div.onclick = (e) => showVoiceUserSettings(u, e); - setTimeout(() => div.classList.remove("tile-enter"), 400); div.innerHTML = `
${u}
@@ -930,8 +596,8 @@ function updateVoiceUIUsers() { } }); } - let resolvedName = authorName && authorName !== u ? authorName : null; - if (!resolvedName && u === String(window.id) && window.username) resolvedName = window.host ? `${window.username}:${window.host}` : window.username; + if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0]; + if (!authorName && u === String(window.id)) authorName = window.username; let avatarRes = window.dmPfpCache ? window.dmPfpCache[u] : null; let avatarUrl = avatarRes ? avatarRes : "assets/default_avatar.png"; @@ -948,7 +614,7 @@ function updateVoiceUIUsers() { div.style.backgroundImage = `url('${avatarUrl.replace(/'/g, "\\'")}')`; let nameEl = document.getElementById(`voice-name-${u}`); - if (nameEl) nameEl.innerText = resolvedName ? `${resolvedName} (${u})` : String(u); + if (nameEl) nameEl.innerText = authorName || u; } Array.from(container.children).forEach(child => { @@ -968,13 +634,15 @@ function updateVoiceUIUsers() { let div = document.getElementById(`voice-tile-stream-${decoderId}`); if (!div) { div = document.createElement("div"); - div.className = "voice-tile tile-enter"; + div.className = "voice-tile"; div.id = `voice-tile-stream-${decoderId}`; - div.onclick = (e) => showVoiceUserSettings(clientId, e, true); - setTimeout(() => div.classList.remove("tile-enter"), 400); let mediaEl = streamData.canvas || streamData.video; if (mediaEl) { + mediaEl.style.width = "100%"; + mediaEl.style.height = "100%"; + mediaEl.style.objectFit = "cover"; + mediaEl.style.background = "#000"; div.appendChild(mediaEl); } @@ -986,13 +654,10 @@ function updateVoiceUIUsers() { } let authorName = window.dmUsernameCache ? window.dmUsernameCache[clientId] : null; - if (!authorName && clientId === String(window.id) && window.username) authorName = window.host ? `${window.username}:${window.host}` : window.username; - - let streamLabel = processBlah(type == '1' ? "label.stream.cam" : "label.stream.screen"); - let baseName = authorName && authorName !== clientId ? `${authorName} (${clientId})` : clientId; - + if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0]; + let nameEl = document.getElementById(`voice-stream-name-${decoderId}`); - if (nameEl) nameEl.innerText = `${baseName} · ${streamLabel}`; + if (nameEl) nameEl.innerText = `${authorName || clientId} (${type == '1' ? 'Cam' : 'Screen'})`; } Array.from(container.children).forEach(child => { @@ -1003,110 +668,26 @@ function updateVoiceUIUsers() { } } }); - - let orderedTiles = []; - for (let u of voiceActiveUsers) { - let userTile = document.getElementById(`voice-tile-${u}`); - if (userTile) orderedTiles.push(userTile); - for (let decoderId in remoteVideos) { - if (decoderId.split('-')[0] === u) { - let streamTile = document.getElementById(`voice-tile-stream-${decoderId}`); - if (streamTile) orderedTiles.push(streamTile); - } - } - } - for (let tile of orderedTiles) { - container.appendChild(tile); - } - - relayoutVoiceTiles(); } -let voiceGridRO = null; - -function ensureVoiceGridObserver() { - let container = document.getElementById("voice-users"); - if (!container || !container.isConnected || container.dataset.roInit) return; - container.dataset.roInit = "1"; - if (!voiceGridRO) { - voiceGridRO = new ResizeObserver(() => relayoutVoiceTiles()); - } - voiceGridRO.observe(container); -} - -function relayoutVoiceTiles() { - let container = document.getElementById("voice-users"); - if (!container || !container.isConnected) return; - - const tiles = Array.from(container.children).filter(el => el.classList.contains("voice-tile")); - const n = tiles.length; - if (!n) return; - - const rootFs = parseFloat(getComputedStyle(document.documentElement).fontSize); - const cs = getComputedStyle(container); - const innerW = container.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); - const innerH = container.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom); - const minCol = 6.25 * rootFs; - const minRow = 7 * rootFs; - const gap = 0.75 * rootFs; - - const maxCols = Math.max(1, Math.floor((innerW + gap) / (minCol + gap))); - const cols = Math.min(n, maxCols); - const rows = Math.ceil(n / cols); - const w = Math.floor((innerW - gap * (cols - 1)) / cols); - const h = Math.floor(Math.max(minRow, (innerH - gap * (rows - 1)) / rows)); - - const sig = `${n}:${cols}:${w}x${h}`; - if (container.dataset.layoutSig === sig) return; - container.dataset.layoutSig = sig; - - container.style.setProperty("--voice-tile-w", w + "px"); - container.style.setProperty("--voice-tile-h", h + "px"); -} - -let voiceSettingsState = null; - -function hideVoiceUserSettings() { - let popup = document.getElementById("voice-user-settings"); - if (popup) popup.remove(); - voiceSettingsState = null; - document.removeEventListener('click', closeVoiceUserSettingsOnOutsideClick); -} - -function closeVoiceUserSettingsOnOutsideClick(ev) { - let popup = document.getElementById("voice-user-settings"); - if (!popup) { - hideVoiceUserSettings(); - return; - } - if (!popup.contains(ev.target)) hideVoiceUserSettings(); -} - -function showVoiceUserSettings(clientId, e, isStream = false) { +function showVoiceUserSettings(clientId, e) { e.stopPropagation(); - - if (voiceSettingsState && voiceSettingsState.clientId === clientId && voiceSettingsState.isStream === isStream) { - hideVoiceUserSettings(); - return; - } - hideVoiceUserSettings(); - - voiceSettingsState = { clientId, isStream }; - + let existing = document.getElementById("voice-user-settings"); + if (existing) existing.remove(); + let popup = document.createElement("div"); popup.id = "voice-user-settings"; - popup.innerHTML = isStream ? ` + popup.innerHTML = `
- - + +
- ` : `
- - + +
`; - + document.body.appendChild(popup); let rect = popup.getBoundingClientRect(); let left = e.clientX; @@ -1115,15 +696,17 @@ function showVoiceUserSettings(clientId, e, isStream = false) { if (top + rect.height > window.innerHeight) top = window.innerHeight - rect.height - 10; popup.style.left = left + "px"; popup.style.top = top + "px"; - requestAnimationFrame(() => { - requestAnimationFrame(() => popup.classList.add("open")); - }); - - document.addEventListener('click', closeVoiceUserSettingsOnOutsideClick); + + const closePopup = (ev) => { + if (!popup.contains(ev.target)) { + popup.remove(); + document.removeEventListener('mousedown', closePopup); + } + }; + setTimeout(() => document.addEventListener('mousedown', closePopup), 10); } window.updateVoiceVol = function(clientId, val, type) { - voiceVolumeState[`${type}:${clientId}`] = val; let el = document.getElementById(`val-${type}-${clientId}`); if (el) el.innerText = val + '%'; const audioId = type === 'screen' ? `${clientId}_screen` : clientId; @@ -1135,7 +718,6 @@ window.updateVoiceVol = function(clientId, val, type) { function toggleVoiceMic() { isMicMuted = !isMicMuted; - if (isMicMuted) setTileSpeaking(String(window.id), false); let btn = document.getElementById("voice-btn-mic"); if (btn) { if (isMicMuted) { @@ -1147,8 +729,6 @@ function toggleVoiceMic() { } function endVoiceSession() { - setVoiceStatus("disconnected"); - if (currentVoiceCallId) { let payload = JSON.stringify({ string1: currentVoiceCallType, string2: currentVoiceCallId }); if (window.fetchEncrypted) window.fetchEncrypted("room/voice/leave", payload).catch(()=>{}); @@ -1199,24 +779,9 @@ function endVoiceSession() { currentVoiceCallType = null; currentVoiceCallId = null; - clearAllSpeaking(); - - if (voiceResizeAbort) { - voiceResizeAbort.abort(); - voiceResizeAbort = null; - } - - if (voiceGridRO) { - voiceGridRO.disconnect(); - voiceGridRO = null; - } - if (voiceUiContainer) { - let popupEl = voiceUiContainer; + voiceUiContainer.remove(); voiceUiContainer = null; - popupEl.classList.remove("show"); - popupEl.classList.add("hide"); - setTimeout(() => popupEl.remove(), 350); } } @@ -1330,7 +895,7 @@ async function startHardwareVideo(btnEl, streamPromise, typeLabel, typeId) { } else if (typeId === 1) { camControl = null; } }; return { stop: stopFunction }; - } catch (e) { console.error(e); alert(processBlah(`:error.call.unsupported:${typeLabel}`)); return null; } + } catch (e) { console.error(e); alert(`No permission or feature not supported: ${typeLabel}`); return null; } } function toggleVoiceCam() { @@ -1340,7 +905,7 @@ function toggleVoiceCam() { } else { navigator.mediaDevices.getUserMedia({video: {width: {ideal: 1280}, height: {ideal: 720}}}).then(stream => { startHardwareVideo(btn, Promise.resolve(stream), "Camera", 1).then(ctrl => camControl = ctrl); - }).catch(err => alert(processBlah(`:error.call.camera:${err.message}`))); + }).catch(err => alert("Camera error: " + err.message)); } } @@ -1349,11 +914,11 @@ function toggleVoiceScreen() { if (screenControl) { screenControl.stop(); } else { - if (!navigator.mediaDevices.getDisplayMedia) { alert(processBlah("error.call.screen.unsupported")); return; } + if (!navigator.mediaDevices.getDisplayMedia) { alert("Screen sharing is not supported on this browser."); return; } navigator.mediaDevices.getDisplayMedia({ video: true, audio: { autoGainControl: false, echoCancellation: false, noiseSuppression: false }, systemAudio: "include", selfBrowserSurface: "exclude" }).then(stream => { startHardwareVideo(btn, Promise.resolve(stream), "Screen", 2).then(ctrl => screenControl = ctrl); - }).catch(err => alert(processBlah(`:error.call.screen:${err.message}`))); + }).catch(err => alert("Screen error: " + err.message)); } } diff --git a/electron/assets/icon.png b/electron/assets/icon.png index a63fdef2..b50fbd13 100644 Binary files a/electron/assets/icon.png and b/electron/assets/icon.png differ diff --git a/electron/package.json b/electron/package.json index d07682a5..ee95df90 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "Miarven", - "version": "0.1.1", + "version": "0.1.0", "description": "An Amazing Capacitor App", "author": { "name": "", diff --git a/icons/icon.png b/icons/icon.png index a63fdef2..b50fbd13 100644 Binary files a/icons/icon.png and b/icons/icon.png differ diff --git a/package.json b/package.json index 02b0c6a4..c8778cd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "olcxja.miarven", - "version": "0.1.1", + "version": "0.1.0", "description": "Larpix client", "license": "AGPL-3.0", "author": "olcxja's softworks", diff --git a/version.json b/version.json index 863e6940..931d63ab 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "versionCode": 2, - "versionName": "0.1.1", - "npmVersion": "0.1.1" + "versionCode": 1, + "versionName": "0.1.0", + "npmVersion": "0.1.0" } diff --git a/webroot/blah/en-cat.json b/webroot/blah/en-cat.json index db64989f..ae56e047 100644 --- a/webroot/blah/en-cat.json +++ b/webroot/blah/en-cat.json @@ -290,24 +290,5 @@ "message.delete.failed": "Failed to delete meow", "message.react.failed": "Failed to react to meow", "message.send.failed": "Failed to send meow", - "owner.transferred": "Purrship transferred successfully", - "title.voice.chat": "voice chat :3", - "status.voice.connecting": "connecting... :3", - "status.voice.disconnected": "disconnected :3", - "action.call.mute": "mute microphone", - "action.call.camera": "camera", - "action.call.screen": "share screen", - "action.call.disconnect": "disconnect :3", - "action.call.join": "join :3", - "action.call.decline": "decline :c", - "title.incoming.call": "incoming voice chat :3", - "desc.call.in.chat": "{0} in chat :3", - "label.user.volume": "user volume", - "label.stream.volume": "screen volume", - "error.call.unsupported": "no permission or not supported: {0} :c", - "error.call.camera": "camera error: {0} :c", - "error.call.screen": "screen share error: {0} :c", - "error.call.screen.unsupported": "ur browser doesn't support screen sharing :c", - "label.stream.cam": "cam", - "label.stream.screen": "screen" -} + "owner.transferred": "Purrship transferred successfully" +} \ No newline at end of file diff --git a/webroot/blah/en-us.json b/webroot/blah/en-us.json index ee67f52d..42d98cb7 100644 --- a/webroot/blah/en-us.json +++ b/webroot/blah/en-us.json @@ -299,24 +299,5 @@ "message.delete.failed": "Failed to delete message", "message.react.failed": "Failed to react to message", "message.send.failed": "Failed to send message", - "owner.transferred": "Ownership transferred successfully", - "title.voice.chat": "Voice Chat", - "status.voice.connecting": "Connecting...", - "status.voice.disconnected": "Disconnected", - "action.call.mute": "Mute Microphone", - "action.call.camera": "Camera", - "action.call.screen": "Share Screen", - "action.call.disconnect": "Disconnect", - "action.call.join": "Join", - "action.call.decline": "Decline", - "title.incoming.call": "Incoming Voice Chat", - "desc.call.in.chat": "{0} in chat", - "label.user.volume": "User Volume", - "label.stream.volume": "Screen Volume", - "error.call.unsupported": "No permission or feature not supported: {0}", - "error.call.camera": "Camera error: {0}", - "error.call.screen": "Screen share error: {0}", - "error.call.screen.unsupported": "Screen sharing is not supported on this browser.", - "label.stream.cam": "Cam", - "label.stream.screen": "Screen" -} + "owner.transferred": "Ownership transferred successfully" +} \ No newline at end of file diff --git a/webroot/blah/pl-pl.json b/webroot/blah/pl-pl.json index 7d6f14ab..d1fea3af 100644 --- a/webroot/blah/pl-pl.json +++ b/webroot/blah/pl-pl.json @@ -299,24 +299,5 @@ "message.delete.failed": "Nie udało się usunąć wiadomości", "message.react.failed": "Nie udało się dodać reakcji", "message.send.failed": "Nie udało się wysłać wiadomości", - "owner.transferred": "Własność przekazana pomyślnie", - "title.voice.chat": "Czat głosowy", - "status.voice.connecting": "Łączenie...", - "status.voice.disconnected": "Rozłączono", - "action.call.mute": "Wycisz mikrofon", - "action.call.camera": "Kamera", - "action.call.screen": "Udostępnij ekran", - "action.call.disconnect": "Rozłącz", - "action.call.join": "Dołącz", - "action.call.decline": "Odrzuć", - "title.incoming.call": "Połączenie głosowe", - "desc.call.in.chat": "{0} w rozmowie", - "label.user.volume": "Głośność użytkownika", - "label.stream.volume": "Głośność ekranu", - "error.call.unsupported": "Brak uprawnień lub funkcja nieobsługiwana: {0}", - "error.call.camera": "Błąd kamery: {0}", - "error.call.screen": "Błąd udostępniania ekranu: {0}", - "error.call.screen.unsupported": "Udostępnianie ekranu nie jest obsługiwane w tej przeglądarce.", - "label.stream.cam": "Kamera", - "label.stream.screen": "Ekran" -} + "owner.transferred": "Własność przekazana pomyślnie" +} \ No newline at end of file diff --git a/webroot/main.js b/webroot/main.js index f514589c..ecd51413 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -1461,8 +1461,8 @@ function showFixedContextMenu(rect, html) { fixedContextMenu.style.transition = 'none'; fixedContextMenu.style.opacity = '0'; - fixedContextMenu.style.left = '0rem'; - fixedContextMenu.style.top = '0rem'; + fixedContextMenu.style.left = '0px'; + fixedContextMenu.style.top = '0px'; fixedContextMenu.classList.add("show"); @@ -3139,7 +3139,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { let safeType = encodeURIComponent(att.type).replace(/'/g, "%27"); let safeName = encodeURIComponent(att.name).replace(/'/g, "%27"); - attachmentsHtml += `
+ attachmentsHtml += `
${iconSvg}
${escapeHtml(displayName)}
@@ -3653,12 +3653,12 @@ async function downloadAndRenderAttachment(container, msgId, attId, type, name, container.dataset.url = blobUrl; if (type.startsWith("image/")) { - container.innerHTML = ``; + container.innerHTML = ``; } else if (type.startsWith("video/")) { - container.innerHTML = ``; + container.innerHTML = ``; } else { container.innerHTML = originalHtml; - container.style.border = "0.0625rem solid var(--accent-color)"; + container.style.border = "1px solid var(--accent-color)"; let a = document.createElement("a"); a.href = blobUrl; a.download = name; @@ -3716,7 +3716,7 @@ function renderAttachmentsPreview() { } html += ` -
+
${iconHtml}
${escapeHtml(file.name)}
-
`; document.body.appendChild(incomingUi); - requestAnimationFrame(() => { - requestAnimationFrame(() => incomingUi.classList.add("show")); - }); - - resolveCallerInfo(roomId).then(({name, pfpUrl}) => { - if (!document.body.contains(incomingUi)) return; - let nameEl = incomingUi.querySelector(".incoming-call-name"); - let avatarEl = incomingUi.querySelector(".incoming-call-avatar"); - if (nameEl) nameEl.innerText = name; - if (avatarEl && pfpUrl) avatarEl.src = pfpUrl; - }); -} - -function dismissIncomingCall() { - let el = document.getElementById("incoming-call-popup"); - if (!el || el.classList.contains("hide")) return; - el.classList.remove("show"); - el.classList.add("hide"); - setTimeout(() => el.remove(), 300); -} - -function joinIncomingCall(roomId) { - dismissIncomingCall(); - initiateVoiceCall(roomId); } async function sendVoiceSignalToUser(targetUserId, roomId) { @@ -662,39 +503,6 @@ async function handleRoomSignal(data) { } } -async function resolveVoiceChatTitle() { - try { - if (currentVoiceCallType === 'groups') { - if (!currentVoiceCallId || !window.fetchEncrypted) return null; - let infoPayload = await fetchEncrypted("group/info", currentVoiceCallId); - if (infoPayload && infoPayload.startsWith("{")) { - return JSON.parse(infoPayload).string2 || null; - } - return null; - } - if (!currentVoiceCallId) return null; - let parts = String(currentVoiceCallId).split('_'); - let myId = `${window.id};${window.host}`; - let targetId = parts[0] === myId ? parts[1] : parts[0]; - if (!targetId) return null; - let name = window.dmUsernameCache ? window.dmUsernameCache[targetId] : null; - if (!name && window.fetchAsync) { - name = await window.fetchAsync(`${window.url}/idtoname?id=${targetId}`); - if (name && !name.startsWith("error:") && window.dmUsernameCache) window.dmUsernameCache[targetId] = name; - } - if (!name || name.startsWith("error:")) return null; - return name.split(':')[0]; - } catch (e) { - return null; - } -} - -async function updateVoiceChatTitle() { - let name = await resolveVoiceChatTitle(); - let el = document.querySelector("#voice-header .voice-title span"); - if (el && name) el.innerText = name; -} - function showVoiceUI() { if (voiceUiContainer) return; @@ -702,49 +510,40 @@ function showVoiceUI() { voiceUiContainer.id = "voice-call-popup"; voiceUiContainer.innerHTML = ` -
-
- ${ICONS.call({width: "1.1rem", height: "1.1rem"})} - ${processBlah("title.voice.chat")} -
-
- - - ${processBlah("status.voice.connecting")} +
+
+ Voice Chat
+
Connecting...
-
-
+
+
`; - + document.body.appendChild(voiceUiContainer); - requestAnimationFrame(() => { - requestAnimationFrame(() => voiceUiContainer.classList.add("show")); - }); let header = document.getElementById("voice-header"); + let isDragging = false; let startX, startY, initialLeft, initialTop; - - header.addEventListener('pointerdown', (e) => { - if (e.button !== undefined && e.button !== 0) return; - voiceDragState = true; - try { header.setPointerCapture(e.pointerId); } catch (err) {} + + header.addEventListener('mousedown', (e) => { + isDragging = true; let rect = voiceUiContainer.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; @@ -754,149 +553,17 @@ function showVoiceUI() { voiceUiContainer.style.bottom = "auto"; voiceUiContainer.style.left = initialLeft + "px"; voiceUiContainer.style.top = initialTop + "px"; - clampVoiceWindowToViewport(voiceUiContainer); }); - - header.addEventListener('pointermove', (e) => { - if (!voiceDragState) return; + + window.addEventListener('mousemove', (e) => { + if (!isDragging) return; let dx = e.clientX - startX; let dy = e.clientY - startY; voiceUiContainer.style.left = (initialLeft + dx) + "px"; voiceUiContainer.style.top = (initialTop + dy) + "px"; - clampVoiceWindowToViewport(voiceUiContainer); }); - - let stopDrag = () => { voiceDragState = false; }; - header.addEventListener('pointerup', stopDrag); - header.addEventListener('pointercancel', stopDrag); - - ensureVoiceGridObserver(); - - setupVoiceWindowResize(); -} - -function setupVoiceWindowResize() { - const popup = voiceUiContainer; - if (!popup || popup.dataset.resizeInit) return; - popup.dataset.resizeInit = "1"; - - const minW = () => parseFloat(getComputedStyle(popup).minWidth) || 200; - const minH = () => parseFloat(getComputedStyle(popup).minHeight) || 160; - - const applySize = (l, t, w, h) => { - const W = window.innerWidth, H = window.innerHeight; - w = Math.round(Math.min(Math.max(w, minW()), W)); - h = Math.round(Math.min(Math.max(h, minH()), H)); - l = Math.round(Math.min(Math.max(l, 0), W - w)); - t = Math.round(Math.min(Math.max(t, 0), H - h)); - popup.style.right = "auto"; - popup.style.bottom = "auto"; - popup.style.width = w + "px"; - popup.style.height = h + "px"; - popup.style.left = l + "px"; - popup.style.top = t + "px"; - }; - - let edgeResizing = false; - - ["n", "s", "e", "w", "ne", "nw", "se", "sw"].forEach((dir) => { - const handle = document.createElement("div"); - handle.className = `voice-resize-handle voice-resize-${dir}`; - popup.appendChild(handle); - - handle.addEventListener("pointerdown", (e) => { - if (e.button !== undefined && e.button !== 0) return; - e.stopPropagation(); - e.preventDefault(); - edgeResizing = true; - const sx = e.clientX, sy = e.clientY; - const rect = popup.getBoundingClientRect(); - const sw = rect.width, sh = rect.height, sl = rect.left, st = rect.top; - try { handle.setPointerCapture(e.pointerId); } catch (err) {} - - const onMove = (ev) => { - const dx = ev.clientX - sx, dy = ev.clientY - sy; - let w = sw, h = sh, l = sl, t = st; - if (dir.includes("e")) w = sw + dx; - if (dir.includes("s")) h = sh + dy; - if (dir.includes("w")) { - w = sw - dx; - l = sl + (sw - w); - } - if (dir.includes("n")) { - h = sh - dy; - t = st + (sh - h); - } - applySize(l, t, w, h); - }; - const onUp = () => { - edgeResizing = false; - handle.removeEventListener("pointermove", onMove); - handle.removeEventListener("pointerup", onUp); - handle.removeEventListener("pointercancel", onUp); - }; - handle.addEventListener("pointermove", onMove); - handle.addEventListener("pointerup", onUp); - handle.addEventListener("pointercancel", onUp); - }); - }); - - // pinch-to-resize (dwa palce jak zoom) - const pointers = new Map(); - let pinch = null; - let suppressClick = false; - - popup.addEventListener("pointerdown", (e) => { - pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); - if (pointers.size === 2 && !pinch && !voiceDragState && !edgeResizing) { - const [a, b] = [...pointers.values()]; - const rect = popup.getBoundingClientRect(); - pinch = { - dist: Math.max(20, Math.hypot(a.x - b.x, a.y - b.y)), - w: rect.width, - h: rect.height - }; - suppressClick = true; - } - if (pointers.size >= 2 || pinch) { - try { popup.setPointerCapture(e.pointerId); } catch (err) {} - } - }); - - popup.addEventListener("pointermove", (e) => { - if (!pointers.has(e.pointerId)) return; - pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); - if (pointers.size === 2 && pinch) { - const [a, b] = [...pointers.values()]; - const scale = Math.max(0.05, Math.hypot(a.x - b.x, a.y - b.y)) / pinch.dist; - const rect = popup.getBoundingClientRect(); - const cx = rect.left + rect.width / 2; - const cy = rect.top + rect.height / 2; - applySize(cx - pinch.w * scale / 2, cy - pinch.h * scale / 2, pinch.w * scale, pinch.h * scale); - } - }); - - const releasePointer = (e) => { - pointers.delete(e.pointerId); - if (pointers.size < 2) pinch = null; - if (pointers.size === 0) setTimeout(() => { suppressClick = false; }, 250); - }; - popup.addEventListener("pointerup", releasePointer); - popup.addEventListener("pointercancel", releasePointer); - - popup.addEventListener("click", (e) => { - if (suppressClick) { - e.stopPropagation(); - e.preventDefault(); - suppressClick = false; - } - }, true); - - if (voiceResizeAbort) voiceResizeAbort.abort(); - voiceResizeAbort = new AbortController(); - window.addEventListener("resize", () => { - clampVoiceWindowToViewport(popup); - }, { signal: voiceResizeAbort.signal }); + + window.addEventListener('mouseup', () => { isDragging = false; }); } function updateVoiceUIUsers() { @@ -907,10 +574,9 @@ function updateVoiceUIUsers() { let div = document.getElementById(`voice-tile-${u}`); if (!div) { div = document.createElement("div"); - div.className = "voice-tile tile-enter"; + div.className = "voice-tile"; div.id = `voice-tile-${u}`; div.onclick = (e) => showVoiceUserSettings(u, e); - setTimeout(() => div.classList.remove("tile-enter"), 400); div.innerHTML = `
${u}
@@ -930,8 +596,8 @@ function updateVoiceUIUsers() { } }); } - let resolvedName = authorName && authorName !== u ? authorName : null; - if (!resolvedName && u === String(window.id) && window.username) resolvedName = window.host ? `${window.username}:${window.host}` : window.username; + if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0]; + if (!authorName && u === String(window.id)) authorName = window.username; let avatarRes = window.dmPfpCache ? window.dmPfpCache[u] : null; let avatarUrl = avatarRes ? avatarRes : "assets/default_avatar.png"; @@ -948,7 +614,7 @@ function updateVoiceUIUsers() { div.style.backgroundImage = `url('${avatarUrl.replace(/'/g, "\\'")}')`; let nameEl = document.getElementById(`voice-name-${u}`); - if (nameEl) nameEl.innerText = resolvedName ? `${resolvedName} (${u})` : String(u); + if (nameEl) nameEl.innerText = authorName || u; } Array.from(container.children).forEach(child => { @@ -968,13 +634,15 @@ function updateVoiceUIUsers() { let div = document.getElementById(`voice-tile-stream-${decoderId}`); if (!div) { div = document.createElement("div"); - div.className = "voice-tile tile-enter"; + div.className = "voice-tile"; div.id = `voice-tile-stream-${decoderId}`; - div.onclick = (e) => showVoiceUserSettings(clientId, e, true); - setTimeout(() => div.classList.remove("tile-enter"), 400); let mediaEl = streamData.canvas || streamData.video; if (mediaEl) { + mediaEl.style.width = "100%"; + mediaEl.style.height = "100%"; + mediaEl.style.objectFit = "cover"; + mediaEl.style.background = "#000"; div.appendChild(mediaEl); } @@ -986,13 +654,10 @@ function updateVoiceUIUsers() { } let authorName = window.dmUsernameCache ? window.dmUsernameCache[clientId] : null; - if (!authorName && clientId === String(window.id) && window.username) authorName = window.host ? `${window.username}:${window.host}` : window.username; - - let streamLabel = processBlah(type == '1' ? "label.stream.cam" : "label.stream.screen"); - let baseName = authorName && authorName !== clientId ? `${authorName} (${clientId})` : clientId; - + if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0]; + let nameEl = document.getElementById(`voice-stream-name-${decoderId}`); - if (nameEl) nameEl.innerText = `${baseName} · ${streamLabel}`; + if (nameEl) nameEl.innerText = `${authorName || clientId} (${type == '1' ? 'Cam' : 'Screen'})`; } Array.from(container.children).forEach(child => { @@ -1003,110 +668,26 @@ function updateVoiceUIUsers() { } } }); - - let orderedTiles = []; - for (let u of voiceActiveUsers) { - let userTile = document.getElementById(`voice-tile-${u}`); - if (userTile) orderedTiles.push(userTile); - for (let decoderId in remoteVideos) { - if (decoderId.split('-')[0] === u) { - let streamTile = document.getElementById(`voice-tile-stream-${decoderId}`); - if (streamTile) orderedTiles.push(streamTile); - } - } - } - for (let tile of orderedTiles) { - container.appendChild(tile); - } - - relayoutVoiceTiles(); } -let voiceGridRO = null; - -function ensureVoiceGridObserver() { - let container = document.getElementById("voice-users"); - if (!container || !container.isConnected || container.dataset.roInit) return; - container.dataset.roInit = "1"; - if (!voiceGridRO) { - voiceGridRO = new ResizeObserver(() => relayoutVoiceTiles()); - } - voiceGridRO.observe(container); -} - -function relayoutVoiceTiles() { - let container = document.getElementById("voice-users"); - if (!container || !container.isConnected) return; - - const tiles = Array.from(container.children).filter(el => el.classList.contains("voice-tile")); - const n = tiles.length; - if (!n) return; - - const rootFs = parseFloat(getComputedStyle(document.documentElement).fontSize); - const cs = getComputedStyle(container); - const innerW = container.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); - const innerH = container.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom); - const minCol = 6.25 * rootFs; - const minRow = 7 * rootFs; - const gap = 0.75 * rootFs; - - const maxCols = Math.max(1, Math.floor((innerW + gap) / (minCol + gap))); - const cols = Math.min(n, maxCols); - const rows = Math.ceil(n / cols); - const w = Math.floor((innerW - gap * (cols - 1)) / cols); - const h = Math.floor(Math.max(minRow, (innerH - gap * (rows - 1)) / rows)); - - const sig = `${n}:${cols}:${w}x${h}`; - if (container.dataset.layoutSig === sig) return; - container.dataset.layoutSig = sig; - - container.style.setProperty("--voice-tile-w", w + "px"); - container.style.setProperty("--voice-tile-h", h + "px"); -} - -let voiceSettingsState = null; - -function hideVoiceUserSettings() { - let popup = document.getElementById("voice-user-settings"); - if (popup) popup.remove(); - voiceSettingsState = null; - document.removeEventListener('click', closeVoiceUserSettingsOnOutsideClick); -} - -function closeVoiceUserSettingsOnOutsideClick(ev) { - let popup = document.getElementById("voice-user-settings"); - if (!popup) { - hideVoiceUserSettings(); - return; - } - if (!popup.contains(ev.target)) hideVoiceUserSettings(); -} - -function showVoiceUserSettings(clientId, e, isStream = false) { +function showVoiceUserSettings(clientId, e) { e.stopPropagation(); - - if (voiceSettingsState && voiceSettingsState.clientId === clientId && voiceSettingsState.isStream === isStream) { - hideVoiceUserSettings(); - return; - } - hideVoiceUserSettings(); - - voiceSettingsState = { clientId, isStream }; - + let existing = document.getElementById("voice-user-settings"); + if (existing) existing.remove(); + let popup = document.createElement("div"); popup.id = "voice-user-settings"; - popup.innerHTML = isStream ? ` + popup.innerHTML = `
- - + +
- ` : `
- - + +
`; - + document.body.appendChild(popup); let rect = popup.getBoundingClientRect(); let left = e.clientX; @@ -1115,15 +696,17 @@ function showVoiceUserSettings(clientId, e, isStream = false) { if (top + rect.height > window.innerHeight) top = window.innerHeight - rect.height - 10; popup.style.left = left + "px"; popup.style.top = top + "px"; - requestAnimationFrame(() => { - requestAnimationFrame(() => popup.classList.add("open")); - }); - - document.addEventListener('click', closeVoiceUserSettingsOnOutsideClick); + + const closePopup = (ev) => { + if (!popup.contains(ev.target)) { + popup.remove(); + document.removeEventListener('mousedown', closePopup); + } + }; + setTimeout(() => document.addEventListener('mousedown', closePopup), 10); } window.updateVoiceVol = function(clientId, val, type) { - voiceVolumeState[`${type}:${clientId}`] = val; let el = document.getElementById(`val-${type}-${clientId}`); if (el) el.innerText = val + '%'; const audioId = type === 'screen' ? `${clientId}_screen` : clientId; @@ -1135,7 +718,6 @@ window.updateVoiceVol = function(clientId, val, type) { function toggleVoiceMic() { isMicMuted = !isMicMuted; - if (isMicMuted) setTileSpeaking(String(window.id), false); let btn = document.getElementById("voice-btn-mic"); if (btn) { if (isMicMuted) { @@ -1147,8 +729,6 @@ function toggleVoiceMic() { } function endVoiceSession() { - setVoiceStatus("disconnected"); - if (currentVoiceCallId) { let payload = JSON.stringify({ string1: currentVoiceCallType, string2: currentVoiceCallId }); if (window.fetchEncrypted) window.fetchEncrypted("room/voice/leave", payload).catch(()=>{}); @@ -1199,24 +779,9 @@ function endVoiceSession() { currentVoiceCallType = null; currentVoiceCallId = null; - clearAllSpeaking(); - - if (voiceResizeAbort) { - voiceResizeAbort.abort(); - voiceResizeAbort = null; - } - - if (voiceGridRO) { - voiceGridRO.disconnect(); - voiceGridRO = null; - } - if (voiceUiContainer) { - let popupEl = voiceUiContainer; + voiceUiContainer.remove(); voiceUiContainer = null; - popupEl.classList.remove("show"); - popupEl.classList.add("hide"); - setTimeout(() => popupEl.remove(), 350); } } @@ -1330,7 +895,7 @@ async function startHardwareVideo(btnEl, streamPromise, typeLabel, typeId) { } else if (typeId === 1) { camControl = null; } }; return { stop: stopFunction }; - } catch (e) { console.error(e); alert(processBlah(`:error.call.unsupported:${typeLabel}`)); return null; } + } catch (e) { console.error(e); alert(`No permission or feature not supported: ${typeLabel}`); return null; } } function toggleVoiceCam() { @@ -1340,7 +905,7 @@ function toggleVoiceCam() { } else { navigator.mediaDevices.getUserMedia({video: {width: {ideal: 1280}, height: {ideal: 720}}}).then(stream => { startHardwareVideo(btn, Promise.resolve(stream), "Camera", 1).then(ctrl => camControl = ctrl); - }).catch(err => alert(processBlah(`:error.call.camera:${err.message}`))); + }).catch(err => alert("Camera error: " + err.message)); } } @@ -1349,11 +914,11 @@ function toggleVoiceScreen() { if (screenControl) { screenControl.stop(); } else { - if (!navigator.mediaDevices.getDisplayMedia) { alert(processBlah("error.call.screen.unsupported")); return; } + if (!navigator.mediaDevices.getDisplayMedia) { alert("Screen sharing is not supported on this browser."); return; } navigator.mediaDevices.getDisplayMedia({ video: true, audio: { autoGainControl: false, echoCancellation: false, noiseSuppression: false }, systemAudio: "include", selfBrowserSurface: "exclude" }).then(stream => { startHardwareVideo(btn, Promise.resolve(stream), "Screen", 2).then(ctrl => screenControl = ctrl); - }).catch(err => alert(processBlah(`:error.call.screen:${err.message}`))); + }).catch(err => alert("Screen error: " + err.message)); } }