feat: redesign + avatars + theme + call presence fixes (v0.6.0)
Visual:
- Light/dark theme via ThemeContext + CSS vars
- New design tokens (surface/fg/accent/line/etc.) across all pages
- Reusable Avatar component (img + letter fallback) wired into UserBar,
ConversationHeader, ChatsPage, FriendsPage, GroupInfoPanel, IncomingCallPanel
Calls:
- Split call UI: IncomingCallPanel, InCallPanel, CallControls,
CallParticipantTile, ScreenShareViewer
- Active speaker hook (useActiveSpeakers)
- Fix: ActiveCallBanner stayed hidden after hangup while peers in room.
- useCallPresence: bind presence callbacks only when we own subscribe
(Supabase forbids .on() after .subscribe() on shared dedup'd channels)
- useCallPresence: never removeChannel — channel is shared with CallContext
so tearing it down on ConversationHeader unmount killed live tracking
- ActiveCallBanner: lastCallConversationId fallback so banner shows
instantly after hangup, auto-dismiss when room confirmed empty
- Drop unused useAnyActiveCall
Bump tauri version 0.5.0 -> 0.6.0
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
MicIcon,
|
||||
MicOffIcon,
|
||||
MonitorShareIcon,
|
||||
MonitorStopIcon,
|
||||
PhoneOffIcon,
|
||||
UsersIcon,
|
||||
VideoIcon,
|
||||
} from './icons';
|
||||
|
||||
interface Props {
|
||||
muted: boolean;
|
||||
sharing: boolean;
|
||||
video: boolean;
|
||||
onToggleMute: () => void;
|
||||
onToggleShare: () => void;
|
||||
onToggleVideo?: () => void;
|
||||
onHangup: () => void;
|
||||
onOpenParticipants?: () => void;
|
||||
/** Compact variant used inside the docked call (36px buttons). */
|
||||
compact?: boolean;
|
||||
/** Glass variant used when controls float on fullscreen cinema mode. */
|
||||
glass?: boolean;
|
||||
/** Disable transient interactions while the call isn't fully connected. */
|
||||
disabledMedia?: boolean;
|
||||
}
|
||||
|
||||
export function CallControls({
|
||||
muted,
|
||||
sharing,
|
||||
video,
|
||||
onToggleMute,
|
||||
onToggleShare,
|
||||
onToggleVideo,
|
||||
onHangup,
|
||||
onOpenParticipants,
|
||||
compact = false,
|
||||
glass = false,
|
||||
disabledMedia = false,
|
||||
}: Props) {
|
||||
const { t } = useTranslation(['app']);
|
||||
const wrapBase = glass
|
||||
? 'glass-pill flex items-center justify-center gap-2 rounded-[18px] p-2.5'
|
||||
: 'flex items-center justify-center gap-2 border-t border-line bg-surface-3 p-3';
|
||||
const btnSize = compact ? 'h-9 w-9 rounded-[10px]' : 'h-12 w-12 rounded-[14px]';
|
||||
const hangupSize = compact ? 'h-9 w-[54px] rounded-[10px]' : 'h-12 w-[72px] rounded-[14px]';
|
||||
return (
|
||||
<div className={wrapBase}>
|
||||
<CallButton
|
||||
label={muted ? t('app:call.unmute') : t('app:call.mute')}
|
||||
active={muted}
|
||||
activeTone="danger"
|
||||
onClick={onToggleMute}
|
||||
disabled={disabledMedia}
|
||||
glass={glass}
|
||||
className={btnSize}
|
||||
>
|
||||
{muted ? <MicOffIcon className="h-5 w-5" /> : <MicIcon className="h-5 w-5" />}
|
||||
</CallButton>
|
||||
{onToggleVideo && (
|
||||
<CallButton
|
||||
label={t('app:call.start_video', { defaultValue: 'Video' })}
|
||||
active={video}
|
||||
activeTone="accent"
|
||||
onClick={onToggleVideo}
|
||||
disabled={disabledMedia}
|
||||
glass={glass}
|
||||
className={btnSize}
|
||||
>
|
||||
<VideoIcon className="h-5 w-5" />
|
||||
</CallButton>
|
||||
)}
|
||||
<CallButton
|
||||
label={
|
||||
sharing
|
||||
? t('app:call.stop_share_screen', { defaultValue: 'Screen-Share stoppen' })
|
||||
: t('app:call.share_screen', { defaultValue: 'Bildschirm teilen' })
|
||||
}
|
||||
active={sharing}
|
||||
activeTone="accent"
|
||||
onClick={onToggleShare}
|
||||
disabled={disabledMedia}
|
||||
glass={glass}
|
||||
className={btnSize}
|
||||
>
|
||||
{sharing ? (
|
||||
<MonitorStopIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<MonitorShareIcon className="h-5 w-5" />
|
||||
)}
|
||||
</CallButton>
|
||||
{onOpenParticipants && (
|
||||
<CallButton
|
||||
label={t('app:call.participants', { defaultValue: 'Teilnehmer' })}
|
||||
onClick={onOpenParticipants}
|
||||
glass={glass}
|
||||
className={btnSize}
|
||||
>
|
||||
<UsersIcon className="h-5 w-5" />
|
||||
</CallButton>
|
||||
)}
|
||||
<CallButton
|
||||
label={t('app:call.hangup')}
|
||||
onClick={onHangup}
|
||||
tone="danger"
|
||||
glass={glass}
|
||||
className={hangupSize}
|
||||
>
|
||||
<PhoneOffIcon className="h-5 w-5" />
|
||||
</CallButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CallButtonProps {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
activeTone?: 'accent' | 'danger';
|
||||
tone?: 'default' | 'danger';
|
||||
glass?: boolean;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function CallButton({
|
||||
label,
|
||||
onClick,
|
||||
disabled,
|
||||
active,
|
||||
activeTone = 'accent',
|
||||
tone = 'default',
|
||||
glass = false,
|
||||
className = '',
|
||||
children,
|
||||
}: CallButtonProps) {
|
||||
const base =
|
||||
'inline-flex cursor-pointer items-center justify-center border transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 disabled:cursor-not-allowed disabled:opacity-50';
|
||||
|
||||
let toneClass: string;
|
||||
if (tone === 'danger') {
|
||||
toneClass = 'bg-rose-600 text-white border-rose-600 hover:bg-rose-500';
|
||||
} else if (active && activeTone === 'danger') {
|
||||
toneClass = 'bg-rose-600 text-white border-rose-600 hover:bg-rose-500';
|
||||
} else if (active) {
|
||||
toneClass = 'bg-accent text-accent-fg border-accent hover:brightness-110';
|
||||
} else if (glass) {
|
||||
toneClass =
|
||||
'border-white/15 bg-white/10 text-white hover:bg-white/15';
|
||||
} else {
|
||||
toneClass = 'border-line bg-surface-2 text-fg hover:bg-surface';
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
title={label}
|
||||
className={`${base} ${toneClass} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user