feat: Initialize monorepo structure for Electron, Web, and Android apps with shared UI components for voice and presence.
All checks were successful
Build and Release / build-and-release (push) Successful in 14m34s

This commit is contained in:
Bryan1029384756
2026-02-21 15:57:37 -06:00
parent 7981b408db
commit e6a1a76116
8 changed files with 69 additions and 12 deletions

View File

@@ -72,6 +72,7 @@ export const VoiceProvider = ({ children }) => {
);
const isMovingRef = useRef(false);
const isDMCallRef = useRef(false);
const lastSpokeRef = useRef(Date.now());
const [isReceivingScreenShareAudio, setIsReceivingScreenShareAudio] = useState(false);
const [isReconnecting, setIsReconnecting] = useState(false);
const [connectionQualities, setConnectionQualities] = useState({});
@@ -245,6 +246,7 @@ export const VoiceProvider = ({ children }) => {
setActiveChannelId(channelId);
setActiveChannelName(channelName);
setConnectionState('connecting');
window.__inVoiceCall = true;
try {
// Request microphone permission (triggers Android runtime prompt)
@@ -381,6 +383,7 @@ export const VoiceProvider = ({ children }) => {
playSound('leave');
setConnectionState('disconnected');
setActiveChannelId(null);
window.__inVoiceCall = false;
setRoom(null);
setToken(null);
clearSpeakerTimers();
@@ -397,6 +400,12 @@ export const VoiceProvider = ({ children }) => {
newRoom.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
const currentSpeakerIds = new Set(speakers.map(p => p.identity));
// Track when local user last spoke (for Android AFK detection)
const localIdentity = newRoom.localParticipant?.identity;
if (localIdentity && currentSpeakerIds.has(localIdentity)) {
lastSpokeRef.current = Date.now();
}
// Cancel pending removal timers for anyone who is speaking again
for (const id of currentSpeakerIds) {
const timer = speakerRemovalTimers.current.get(id);
@@ -456,6 +465,7 @@ export const VoiceProvider = ({ children }) => {
console.error('Voice Connection Failed:', err);
setConnectionState('error');
setActiveChannelId(null);
window.__inVoiceCall = false;
}
};
@@ -565,16 +575,33 @@ export const VoiceProvider = ({ children }) => {
// AFK idle polling: move user to AFK channel when idle exceeds timeout
useEffect(() => {
if (!activeChannelId || !serverSettings?.afkChannelId || isInAfkChannel) return;
if (!idle?.getSystemIdleTime) return;
if (isDMCallRef.current) return; // Skip AFK for DM calls
const isCapacitor = !!window.Capacitor?.isNativePlatform?.();
// On desktop/web, require system idle API; on Capacitor, use lastSpokeRef
if (!isCapacitor && !idle?.getSystemIdleTime) return;
const afkTimeout = serverSettings.afkTimeout || 300;
const interval = setInterval(async () => {
try {
const idleSeconds = await idle.getSystemIdleTime();
let idleSeconds;
if (isCapacitor) {
// On Android, idle = time since user last spoke in voice
idleSeconds = Math.floor((Date.now() - lastSpokeRef.current) / 1000);
} else {
idleSeconds = await idle.getSystemIdleTime();
}
if (idleSeconds >= afkTimeout) {
const userId = localStorage.getItem('userId');
if (!userId) return;
// On Capacitor, also set user status to idle
if (isCapacitor) {
await convex.mutation(api.auth.updateStatus, { userId, status: 'idle' });
}
await convex.mutation(api.voiceState.afkMove, {
userId,
afkChannelId: serverSettings.afkChannelId,
@@ -766,6 +793,7 @@ export const VoiceProvider = ({ children }) => {
const disconnectVoice = () => {
console.log('User manually disconnected voice');
isDMCallRef.current = false;
window.__inVoiceCall = false;
voiceService?.stopService();
if (room) room.disconnect();
};