diff --git a/.claude/settings.local.json b/.claude/settings.local.json index df80065..8a9fd2a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -43,7 +43,8 @@ "Bash(keytool:*)", "Bash(echo:*)", "Bash(python -c \"import base64; print\\(base64.b64encode\\(open\\(r''C:\\\\Users\\\\bryan\\\\Desktop\\\\Discord Clone\\\\discord-clone-release.keystore'',''rb''\\).read\\(\\)\\).decode\\(\\)\\)\")", - "WebFetch(domain:gitea.moyettes.com)" + "WebFetch(domain:gitea.moyettes.com)", + "Bash(grep:*)" ] } } diff --git a/apps/android/package.json b/apps/android/package.json index 63787ec..929ef2a 100644 --- a/apps/android/package.json +++ b/apps/android/package.json @@ -1,7 +1,7 @@ { "name": "@discord-clone/android", "private": true, - "version": "1.0.28", + "version": "1.0.29", "type": "module", "scripts": { "cap:sync": "npx cap sync", diff --git a/apps/electron/package.json b/apps/electron/package.json index 8a79d19..0c12c88 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -1,7 +1,7 @@ { "name": "@discord-clone/electron", "private": true, - "version": "1.0.28", + "version": "1.0.29", "description": "Discord Clone - Electron app", "author": "Moyettes", "type": "module", diff --git a/apps/electron/src/platform/index.js b/apps/electron/src/platform/index.js index 888b7ab..3643542 100644 --- a/apps/electron/src/platform/index.js +++ b/apps/electron/src/platform/index.js @@ -56,12 +56,14 @@ const electronPlatform = { updates: { checkUpdate: () => window.updateAPI.checkFlatpakUpdate(), }, + systemBars: null, searchDB, features: { hasWindowControls: true, hasScreenCapture: true, hasNativeUpdates: true, hasSearch: true, + hasSystemBars: false, }, }; diff --git a/apps/web/package.json b/apps/web/package.json index 13270be..37c0a3f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@discord-clone/web", "private": true, - "version": "1.0.28", + "version": "1.0.29", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/platform-web/src/index.js b/packages/platform-web/src/index.js index b766ed0..c6f8f3b 100644 --- a/packages/platform-web/src/index.js +++ b/packages/platform-web/src/index.js @@ -35,12 +35,16 @@ const webPlatform = { }, windowControls: null, updates: null, + voiceService: null, + systemBars: null, searchDB, features: { hasWindowControls: false, hasScreenCapture: true, hasNativeUpdates: false, hasSearch: true, + hasVoiceService: false, + hasSystemBars: false, }, }; @@ -104,6 +108,34 @@ if (window.Capacitor?.isNativePlatform?.()) { }, }; webPlatform.features.hasNativeUpdates = true; + webPlatform.features.hasScreenCapture = false; + + // Native voice foreground service + const VoiceService = window.Capacitor.Plugins.VoiceService; + if (VoiceService) { + webPlatform.voiceService = { + async startService({ channelName, isMuted, isDeafened }) { + await VoiceService.startService({ channelName, isMuted, isDeafened }); + }, + async stopService() { + await VoiceService.stopService(); + }, + async updateNotification(opts) { + await VoiceService.updateNotification(opts); + }, + addNotificationActionListener(callback) { + return VoiceService.addListener('voiceNotificationAction', callback); + }, + }; + webPlatform.features.hasVoiceService = true; + } + + // Native system bar coloring + const SystemBars = window.Capacitor.Plugins.SystemBars; + if (SystemBars) { + webPlatform.systemBars = { setColors: (opts) => SystemBars.setColors(opts) }; + webPlatform.features.hasSystemBars = true; + } } export default webPlatform; diff --git a/packages/shared/src/App.jsx b/packages/shared/src/App.jsx index bc47c91..fc7fecb 100644 --- a/packages/shared/src/App.jsx +++ b/packages/shared/src/App.jsx @@ -6,6 +6,7 @@ import Recovery from './pages/Recovery'; import Chat from './pages/Chat'; import { usePlatform } from './platform'; import { useSearch } from './contexts/SearchContext'; +import { useSystemBars } from './hooks/useSystemBars'; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; @@ -15,6 +16,7 @@ function AuthGuard({ children }) { const navigate = useNavigate(); const { session, settings } = usePlatform(); const searchCtx = useSearch(); + useSystemBars(null); useEffect(() => { let cancelled = false; diff --git a/packages/shared/src/assets/sounds/camera_off.mp3 b/packages/shared/src/assets/sounds/camera_off.mp3 new file mode 100644 index 0000000..f6731fb Binary files /dev/null and b/packages/shared/src/assets/sounds/camera_off.mp3 differ diff --git a/packages/shared/src/assets/sounds/camera_on.mp3 b/packages/shared/src/assets/sounds/camera_on.mp3 new file mode 100644 index 0000000..65ad834 Binary files /dev/null and b/packages/shared/src/assets/sounds/camera_on.mp3 differ diff --git a/packages/shared/src/assets/sounds/deafen.mp3 b/packages/shared/src/assets/sounds/deafen.mp3 index 8e732e1..8168123 100644 Binary files a/packages/shared/src/assets/sounds/deafen.mp3 and b/packages/shared/src/assets/sounds/deafen.mp3 differ diff --git a/packages/shared/src/assets/sounds/join_call.mp3 b/packages/shared/src/assets/sounds/join_call.mp3 index 8a7b92f..3e030fa 100644 Binary files a/packages/shared/src/assets/sounds/join_call.mp3 and b/packages/shared/src/assets/sounds/join_call.mp3 differ diff --git a/packages/shared/src/assets/sounds/leave_call.mp3 b/packages/shared/src/assets/sounds/leave_call.mp3 index 491d03d..c58e9ed 100644 Binary files a/packages/shared/src/assets/sounds/leave_call.mp3 and b/packages/shared/src/assets/sounds/leave_call.mp3 differ diff --git a/packages/shared/src/assets/sounds/mute.mp3 b/packages/shared/src/assets/sounds/mute.mp3 index d80ae37..b87de43 100644 Binary files a/packages/shared/src/assets/sounds/mute.mp3 and b/packages/shared/src/assets/sounds/mute.mp3 differ diff --git a/packages/shared/src/assets/sounds/outgoing_ring.mp3 b/packages/shared/src/assets/sounds/outgoing_ring.mp3 new file mode 100644 index 0000000..5a00cd0 Binary files /dev/null and b/packages/shared/src/assets/sounds/outgoing_ring.mp3 differ diff --git a/packages/shared/src/assets/sounds/screenshare_start.mp3 b/packages/shared/src/assets/sounds/screenshare_start.mp3 index 36e4604..444fce8 100644 Binary files a/packages/shared/src/assets/sounds/screenshare_start.mp3 and b/packages/shared/src/assets/sounds/screenshare_start.mp3 differ diff --git a/packages/shared/src/assets/sounds/undeafen.mp3 b/packages/shared/src/assets/sounds/undeafen.mp3 index 8459628..797c56e 100644 Binary files a/packages/shared/src/assets/sounds/undeafen.mp3 and b/packages/shared/src/assets/sounds/undeafen.mp3 differ diff --git a/packages/shared/src/assets/sounds/unmute.mp3 b/packages/shared/src/assets/sounds/unmute.mp3 index 7d386a6..fd78422 100644 Binary files a/packages/shared/src/assets/sounds/unmute.mp3 and b/packages/shared/src/assets/sounds/unmute.mp3 differ diff --git a/packages/shared/src/assets/sounds/user_moved.mp3 b/packages/shared/src/assets/sounds/user_moved.mp3 new file mode 100644 index 0000000..1dfb081 Binary files /dev/null and b/packages/shared/src/assets/sounds/user_moved.mp3 differ diff --git a/packages/shared/src/components/ChatArea.jsx b/packages/shared/src/components/ChatArea.jsx index 2593d0a..8234829 100644 --- a/packages/shared/src/components/ChatArea.jsx +++ b/packages/shared/src/components/ChatArea.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import ReactDOM from 'react-dom'; import { Virtuoso } from 'react-virtuoso'; import { useQuery, usePaginatedQuery, useMutation, useConvex } from 'convex/react'; import { api } from '../../../../convex/_generated/api'; @@ -30,6 +31,7 @@ import ColoredIcon from './ColoredIcon'; import { usePlatform } from '../platform'; import { useVoice } from '../contexts/VoiceContext'; import { useSearch } from '../contexts/SearchContext'; +import { useIsMobile } from '../hooks/useIsMobile'; import { generateUniqueMessage } from '../utils/floodMessages'; const SCROLL_DEBUG = true; @@ -340,7 +342,7 @@ const Attachment = ({ metadata, onLoad, onImageClick }) => { if (error) return
{error}
; if (metadata.mimeType.startsWith('image/')) { - return {metadata.filename} onImageClick(url)} />; + return {metadata.filename} onImageClick(url)} />; } if (metadata.mimeType.startsWith('video/')) { const handlePlayClick = () => { setShowControls(true); if (videoRef.current) videoRef.current.play(); }; @@ -510,6 +512,7 @@ const InputContextMenu = ({ x, y, onClose, onPaste }) => { const ChatArea = ({ channelId, channelName, channelType, username, channelKey, userId: currentUserId, showMembers, onToggleMembers, onOpenDM, showPinned, onTogglePinned, jumpToMessageId, onClearJumpToMessage }) => { const { crypto } = usePlatform(); + const isMobile = useIsMobile(); const { isReceivingScreenShareAudio } = useVoice(); const searchCtx = useSearch(); const [decryptedMessages, setDecryptedMessages] = useState([]); @@ -2234,9 +2237,9 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u isHovered={hoveredMessageId === msg.id} editInput={editInput} username={username} - onHover={() => setHoveredMessageId(msg.id)} - onLeave={() => setHoveredMessageId(null)} - onContextMenu={(e) => { e.preventDefault(); window.dispatchEvent(new Event('close-context-menus')); setContextMenu({ x: e.clientX, y: e.clientY, messageId: msg.id, isOwner, isAttachment: !!parseAttachment(msg.content), canDelete }); }} + onHover={isMobile ? undefined : () => setHoveredMessageId(msg.id)} + onLeave={isMobile ? undefined : () => setHoveredMessageId(null)} + onContextMenu={isMobile ? undefined : (e) => { e.preventDefault(); window.dispatchEvent(new Event('close-context-menus')); setContextMenu({ x: e.clientX, y: e.clientY, messageId: msg.id, isOwner, isAttachment: !!parseAttachment(msg.content), canDelete }); }} onAddReaction={(emoji) => { if (emoji) { addReaction({ messageId: msg.id, userId: currentUserId, emoji }); } else { setReactionPickerMsgId(reactionPickerMsgId === msg.id ? null : msg.id); } }} onEdit={() => { setEditingMessage({ id: msg.id, content: msg.content }); setEditInput(msg.content); }} onReply={() => setReplyingTo({ messageId: msg.id, username: msg.username, content: msg.content?.substring(0, 100) })} @@ -2481,10 +2484,11 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u - {zoomedImage && ( + {zoomedImage && ReactDOM.createPortal(
setZoomedImage(null)}> Zoomed e.stopPropagation()} /> -
+ , + document.body )} {profilePopup && ( -
{ +
{ if (!e.target.closest('.channel-item') && !e.target.closest('.channel-category-header')) { e.preventDefault(); window.dispatchEvent(new Event('close-context-menus')); @@ -1453,7 +1453,7 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam collapsed={collapsedCategories[group.id]} onToggle={toggleCategory} onAddChannel={handleAddChannelToCategory} - onContextMenu={group.id !== '__uncategorized__' ? (e) => { + onContextMenu={group.id !== '__uncategorized__' && !isMobile ? (e) => { e.preventDefault(); e.stopPropagation(); window.dispatchEvent(new Event('close-context-menus')); diff --git a/packages/shared/src/components/VoiceStage.jsx b/packages/shared/src/components/VoiceStage.jsx index 8eb4a3d..0c5370d 100644 --- a/packages/shared/src/components/VoiceStage.jsx +++ b/packages/shared/src/components/VoiceStage.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Track, RoomEvent, ConnectionQuality } from 'livekit-client'; import { useVoice } from '../contexts/VoiceContext'; +import { usePlatform } from '../platform/PlatformProvider'; import ScreenShareModal from './ScreenShareModal'; import Avatar from './Avatar'; import { VideoRenderer, findTrackPubs, useParticipantTrack } from '../utils/streamUtils.jsx'; @@ -621,6 +622,7 @@ const FocusedStreamView = ({ const VoiceStage = ({ room, channelId, voiceStates, channelName }) => { const [participants, setParticipants] = useState([]); const { isMuted, toggleMute, disconnectVoice, setScreenSharing, connectToVoice, watchingStreamOf, setWatchingStreamOf, isReceivingScreenShareAudio, isReconnecting } = useVoice(); + const { features } = usePlatform(); const [isScreenShareModalOpen, setIsScreenShareModalOpen] = useState(false); const [isScreenShareActive, setIsScreenShareActive] = useState(false); const screenShareAudioTrackRef = useRef(null); @@ -1039,6 +1041,7 @@ const VoiceStage = ({ room, channelId, voiceStates, channelName }) => { + {features.hasScreenCapture && ( + )}