fix: call media-connect state, blocklist banner staleness, group rename, avatar URL

- stores/calls.ts, CallRoot.tsx: distinguish signaling-connected from
  actual media-joined (mediaJoined flag), so a call whose mic never
  attaches no longer shows a live timer/"网络良好"; propagate a
  disconnected reason on post-accept media failure so both sides agree
  on the outcome instead of one side seeing a normal "已结束".
- DetailColumn.tsx: invalidate conversation-context alongside blocks on
  block/unblock, so the composer's blocked-notice banner updates
  immediately instead of only after a window-focus refetch.
- GroupDetailColumn.tsx: add an owner-only group rename control
  (updateGroup was already implemented but never wired to any UI).
- Avatar.tsx: stop re-wrapping an already-absolute avatar_file_id (seen
  on default-avatar placeholders) into a malformed
  /v0/file/s/https://... request.
This commit is contained in:
Your Name
2026-09-18 15:01:08 +08:00
parent 0c5b329179
commit 7fff1a7b3a
5 changed files with 133 additions and 22 deletions
+21 -9
View File
@@ -127,7 +127,7 @@ function MobileIncomingCall() {
* minimizing is pure local UI state, matching the old app's own real behavior (CallPanel is never
* unmounted between full-screen and minimized). */
function MinimizedCallBar() {
const { topic, peerName, connectedAt, muted, toggleMinimize, toggleMute, hangUp } = useCallStore();
const { topic, peerName, connectedAt, mediaJoined, muted, toggleMinimize, toggleMute, hangUp } = useCallStore();
const elapsed = useElapsedSeconds(connectedAt);
return (
// `bottom-[106px] sm:bottom-6`: same viewport-vs-Outlet-box fixed-positioning issue as Toast.tsx
@@ -141,7 +141,10 @@ function MinimizedCallBar() {
</span>
<span className="flex min-w-0 flex-col">
<strong className="truncate text-sm font-medium">{peerName}</strong>
<span className="text-xs text-success">{connectedAt ? `通话中 · ${formatElapsed(elapsed)}` : '正在连接…'}</span>
{/* `mediaJoined`, not bare `connectedAt` -- signaling accept flips `connectedAt` before
media has actually joined (see stores/calls.ts's own comment on this field), so this
used to claim "通话中" for a call whose mic never actually attached. */}
<span className="text-xs text-success">{connectedAt && mediaJoined ? `通话中 · ${formatElapsed(elapsed)}` : '正在连接…'}</span>
</span>
</button>
<button type="button" onClick={toggleMute} aria-label={muted ? '取消静音' : '静音'} className="grid h-8 w-8 flex-none place-items-center rounded-full bg-white/10">
@@ -165,6 +168,7 @@ function CallWindow() {
phase,
peerName,
connectedAt,
mediaJoined,
muted,
cameraOff,
hasRemoteVideo,
@@ -189,7 +193,11 @@ function CallWindow() {
if (minimized) return <MinimizedCallBar />;
const statusLabel = phase === 'connected' ? `${formatElapsed(elapsed)} · 网络良好` : '正在等待对方接听…';
// Signaling `connected` and actual media both being up are two different things (see stores/
// calls.ts's `mediaJoined` comment) -- a real "网络良好" claim needs the second one too, and the
// brief signaling-only window in between gets its own label rather than either overstating the
// connection or reusing the pre-answer ringing text.
const statusLabel = phase === 'connected' ? (mediaJoined ? `${formatElapsed(elapsed)} · 网络良好` : '正在连接中…') : '正在等待对方接听…';
// 06-calls.dc.html #1b (呼出等待中) labels this same button "取消", matching the protocol doc's own
// "发起方取消发送 hang-up:cancelled" wording for the pre-connect case -- it was hardcoded to "挂断"
// at all 3 call sites below regardless of phase, which is only actually correct once connected.
@@ -241,9 +249,13 @@ function CallWindow() {
<strong className="text-base font-semibold">{peerName}</strong>
<span className="flex items-center gap-1 text-xs text-white/70">
{phase === 'connected' ? (
<>
{formatElapsed(elapsed)} · <Icon name="network_wifi" className="text-sm text-[#1fb36a]" /> 网络良好
</>
mediaJoined ? (
<>
{formatElapsed(elapsed)} · <Icon name="network_wifi" className="text-sm text-[#1fb36a]" /> 网络良好
</>
) : (
'正在连接中…'
)
) : (
'正在等待对方接听…'
)}
@@ -294,7 +306,7 @@ function CallWindow() {
>
{avatarLetter(peerName)}
</span>
<span className="text-sm text-white/70">{phase === 'connected' ? '对方已关闭摄像头' : ''}</span>
<span className="text-sm text-white/70">{phase === 'connected' && mediaJoined ? '对方已关闭摄像头' : ''}</span>
</div>
</div>
{/* Mobile fallback (06-calls.dc.html #1f): avatar + name + duration together, since mobile
@@ -307,12 +319,12 @@ function CallWindow() {
</span>
<strong className="mt-2 text-xl font-semibold">{peerName}</strong>
<span className="mt-1 text-sm text-white/70">
{phase === 'connected' ? formatElapsed(elapsed) : '正在等待对方接听…'}
{phase === 'connected' ? (mediaJoined ? formatElapsed(elapsed) : '正在连接中…') : '正在等待对方接听…'}
</span>
{/* Desktop's version of this same fallback state shows this explanation (line 285
above); mobile silently dropped it, showing only the elapsed-time line with no
indication of WHY there's no video. */}
{phase === 'connected' ? <span className="text-sm text-white/70">对方已关闭摄像头</span> : null}
{phase === 'connected' && mediaJoined ? <span className="text-sm text-white/70">对方已关闭摄像头</span> : null}
</div>
</>
)}