From 69faf55b2bbff328253ab7df8b31a7ffe411f74a65a1b53bf60d57aca4e061b1 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Fri, 24 Jul 2026 15:02:56 +0400 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=B8=D0=BA=D0=BE=D0=BD=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=BD=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 2 +- shine-UI/js/services/call-ui-service.js | 121 +++++++++++++++++++----- shine-UI/styles/components.css | 94 ++++++++++++++++-- 3 files changed, 184 insertions(+), 33 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index 04751ddd..167f3309 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.348 +client.version=1.2.349 server.version=1.2.319 diff --git a/shine-UI/js/services/call-ui-service.js b/shine-UI/js/services/call-ui-service.js index c9102eb5..a392e4c7 100644 --- a/shine-UI/js/services/call-ui-service.js +++ b/shine-UI/js/services/call-ui-service.js @@ -21,6 +21,9 @@ let hangupBtn = null; let minimizeBtn = null; let barEl = null; let barTextEl = null; +let barActionsEl = null; +let barMuteBtn = null; +let barHangupBtn = null; let unbind = null; let tickerId = 0; let currentSnapshot = null; @@ -55,6 +58,59 @@ function buildBarText(snapshot) { return `Звонок с ${peer} · ${elapsed}`; } +function stopEvent(event) { + event.preventDefault(); + event.stopPropagation(); +} + +function getIconSvg(name) { + if (name === 'mic-on') { + return ''; + } + if (name === 'mic-off') { + return ''; + } + if (name === 'hangup') { + return ''; + } + if (name === 'minimize') { + return ''; + } + return ''; +} + +function setButtonIcon(button, iconName, label) { + if (!button) return; + button.innerHTML = getIconSvg(iconName); + button.setAttribute('aria-label', label); + button.title = label; +} + +function updateMuteButtons(muted) { + const iconName = muted ? 'mic-off' : 'mic-on'; + const label = muted ? 'Включить микрофон' : 'Выключить микрофон'; + setButtonIcon(muteBtn, iconName, label); + setButtonIcon(barMuteBtn, iconName, label); + if (muteBtn) muteBtn.dataset.muted = muted ? '1' : '0'; + if (barMuteBtn) barMuteBtn.dataset.muted = muted ? '1' : '0'; +} + +async function handleMuteClick(event) { + stopEvent(event); + const nowMuted = event.currentTarget?.dataset?.muted === '1'; + await setMicMuted(!nowMuted); +} + +async function handleEndCallClick(event) { + stopEvent(event); + if (!currentSnapshot) return; + if (currentSnapshot.canDecline && !currentSnapshot.canHangup) { + await declineIncomingCall(); + return; + } + await hangupActiveCall(); +} + function setMinimized(nextValue) { minimized = Boolean(nextValue); render(); @@ -81,18 +137,33 @@ function ensureUi() { rootEl.className = 'call-ui-root'; rootEl.hidden = true; - barEl = document.createElement('button'); - barEl.type = 'button'; + barEl = document.createElement('div'); barEl.className = 'call-minimized-bar'; barEl.hidden = true; - barEl.addEventListener('click', () => { + + barTextEl = document.createElement('button'); + barTextEl.type = 'button'; + barTextEl.className = 'call-minimized-bar-text'; + barTextEl.addEventListener('click', () => { if (!currentSnapshot) return; setMinimized(false); }); - barTextEl = document.createElement('span'); - barTextEl.className = 'call-minimized-bar-text'; - barEl.append(barTextEl); + barActionsEl = document.createElement('div'); + barActionsEl.className = 'call-minimized-bar-actions'; + + barMuteBtn = document.createElement('button'); + barMuteBtn.type = 'button'; + barMuteBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--secondary'; + barMuteBtn.addEventListener('click', handleMuteClick); + + barHangupBtn = document.createElement('button'); + barHangupBtn.type = 'button'; + barHangupBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--danger'; + barHangupBtn.addEventListener('click', handleEndCallClick); + + barActionsEl.append(barMuteBtn, barHangupBtn); + barEl.append(barTextEl, barActionsEl); overlayEl = document.createElement('div'); overlayEl.className = 'call-overlay'; @@ -109,9 +180,10 @@ function ensureUi() { minimizeBtn = document.createElement('button'); minimizeBtn.type = 'button'; - minimizeBtn.className = 'secondary-btn call-overlay-minimize-btn'; - minimizeBtn.textContent = 'Свернуть'; - minimizeBtn.addEventListener('click', () => { + minimizeBtn.className = 'call-icon-btn call-icon-btn--secondary call-overlay-minimize-btn'; + setButtonIcon(minimizeBtn, 'minimize', 'Свернуть звонок'); + minimizeBtn.addEventListener('click', (event) => { + stopEvent(event); if (!currentSnapshot) return; setMinimized(true); }); @@ -124,12 +196,8 @@ function ensureUi() { muteBtn = document.createElement('button'); muteBtn.type = 'button'; - muteBtn.className = 'secondary-btn'; - muteBtn.textContent = 'Микрофон'; - muteBtn.addEventListener('click', async () => { - const nowMuted = muteBtn.dataset.muted === '1'; - await setMicMuted(!nowMuted); - }); + muteBtn.className = 'call-icon-btn call-icon-btn--secondary'; + muteBtn.addEventListener('click', handleMuteClick); acceptBtn = document.createElement('button'); acceptBtn.type = 'button'; @@ -141,19 +209,17 @@ function ensureUi() { declineBtn = document.createElement('button'); declineBtn.type = 'button'; - declineBtn.className = 'destructive-btn'; - declineBtn.textContent = 'Сбросить'; + declineBtn.className = 'call-icon-btn call-icon-btn--danger'; + setButtonIcon(declineBtn, 'hangup', 'Отклонить звонок'); declineBtn.addEventListener('click', async () => { await declineIncomingCall(); }); hangupBtn = document.createElement('button'); hangupBtn.type = 'button'; - hangupBtn.className = 'destructive-btn'; - hangupBtn.textContent = 'Положить трубку'; - hangupBtn.addEventListener('click', async () => { - await hangupActiveCall(); - }); + hangupBtn.className = 'call-icon-btn call-icon-btn--danger'; + setButtonIcon(hangupBtn, 'hangup', 'Положить трубку'); + hangupBtn.addEventListener('click', handleEndCallClick); controls.append(muteBtn, acceptBtn, declineBtn, hangupBtn); headerEl.append(titleEl, minimizeBtn); @@ -171,8 +237,7 @@ function applyExpandedState(snapshot) { statusEl.textContent = snapshot.statusText || ''; const muted = Boolean(snapshot.muted); - muteBtn.dataset.muted = muted ? '1' : '0'; - muteBtn.textContent = muted ? 'Включить микрофон' : 'Выключить микрофон'; + updateMuteButtons(muted); muteBtn.hidden = !snapshot.canMute; muteBtn.disabled = !snapshot.canMute; @@ -186,6 +251,14 @@ function applyMinimizedState(snapshot) { overlayEl.hidden = true; barEl.hidden = false; barTextEl.textContent = buildBarText(snapshot); + const muted = Boolean(snapshot.muted); + updateMuteButtons(muted); + barMuteBtn.hidden = !snapshot.canMute; + barMuteBtn.disabled = !snapshot.canMute; + const canEnd = snapshot.canHangup || snapshot.canDecline; + barHangupBtn.hidden = !canEnd; + barHangupBtn.disabled = !canEnd; + setButtonIcon(barHangupBtn, 'hangup', snapshot.canDecline && !snapshot.canHangup ? 'Отклонить звонок' : 'Положить трубку'); } function syncShellState() { diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 7f65c562..ea060e6a 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -1749,29 +1749,106 @@ top: 0; left: 0; right: 0; + display: flex; + align-items: center; + gap: 10px; min-height: 44px; padding: calc(8px + env(safe-area-inset-top)) 14px 8px; - border: 0; border-bottom: 1px solid rgba(159, 255, 196, 0.28); background: linear-gradient(180deg, rgba(30, 138, 79, 0.98), rgba(18, 106, 60, 0.98)); box-shadow: 0 10px 24px rgba(5, 30, 16, 0.28); + pointer-events: auto; +} + +.call-minimized-bar-text { + flex: 1 1 auto; + min-width: 0; + border: 0; + background: transparent; color: #f3fff8; font-size: 13px; font-weight: 700; line-height: 1.2; text-align: left; - pointer-events: auto; + padding: 0; cursor: pointer; overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; + white-space: nowrap; } -.call-minimized-bar-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.call-minimized-bar-actions { + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; +} + +.call-icon-btn { + width: 40px; + min-width: 40px; + height: 40px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 12px; + border: 1px solid rgba(132, 162, 228, 0.42); + background: linear-gradient(180deg, rgba(19, 35, 63, 0.9), rgba(14, 26, 50, 0.95)); + color: #e7efff; + cursor: pointer; + transition: 0.2s ease; +} + +.call-icon-btn:hover { + transform: translateY(-1px); +} + +.call-icon-btn:disabled { + opacity: 1; + color: #93a6cc; + background: linear-gradient(180deg, rgba(18, 30, 54, 0.8), rgba(11, 20, 39, 0.86)); + border-color: rgba(124, 145, 189, 0.28); + box-shadow: none; + cursor: not-allowed; + transform: none; +} + +.call-icon-btn svg { + width: 20px; + height: 20px; +} + +.call-icon-btn--secondary { + border-color: rgba(132, 162, 228, 0.42); + background: linear-gradient(180deg, rgba(19, 35, 63, 0.9), rgba(14, 26, 50, 0.95)); + color: #e7efff; +} + +.call-icon-btn--secondary:hover { + border-color: rgba(167, 195, 255, 0.76); +} + +.call-icon-btn--danger { + border-color: rgba(255, 129, 147, 0.52); + background: linear-gradient(120deg, rgba(188, 42, 65, 0.96), rgba(142, 28, 47, 0.98)); + color: #fff2f5; +} + +.call-icon-btn--danger:hover { + border-color: rgba(255, 163, 176, 0.82); +} + +.call-icon-btn--bar { + width: 34px; + min-width: 34px; + height: 34px; + border-radius: 10px; +} + +.call-icon-btn--bar svg { + width: 18px; + height: 18px; } .call-overlay { @@ -1823,6 +1900,7 @@ display: flex; flex-wrap: wrap; gap: 8px; + align-items: center; } .call-accept-btn {