fix: query the server for a contact's upcoming events instead of the calendar cache

This commit is contained in:
Linus Rath
2026-08-29 18:04:48 +02:00
parent 78114b620a
commit 0e514659be
+40 -5
View File
@@ -7,14 +7,17 @@ import { format, isAfter, parseISO } from 'date-fns';
import type { ContactCard, Email, CalendarEvent } from '../../api/types'; import type { ContactCard, Email, CalendarEvent } from '../../api/types';
import type { RootStackParamList } from '../../navigation/types'; import type { RootStackParamList } from '../../navigation/types';
import { getEmails, queryEmailsByFilter } from '../../api/email'; import { getEmails, queryEmailsByFilter } from '../../api/email';
import { getEvents, queryEvents } from '../../api/calendar';
import { useCalendarStore } from '../../stores/calendar-store'; import { useCalendarStore } from '../../stores/calendar-store';
import { getEventColor } from '../../lib/calendar-utils'; import { getEventColor } from '../../lib/calendar-utils';
import { hasCalendarCapability } from '../../lib/capabilities';
import SenderAvatar from '../SenderAvatar'; import SenderAvatar from '../SenderAvatar';
import { radius, spacing, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { radius, spacing, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
import { useColors } from '../../theme/colors'; import { useColors } from '../../theme/colors';
const EMAIL_LIMIT = 5; const EMAIL_LIMIT = 5;
const EVENT_LIMIT = 5; const EVENT_LIMIT = 5;
const EVENT_LOOKAHEAD_DAYS = 365;
type Nav = NativeStackNavigationProp<RootStackParamList>; type Nav = NativeStackNavigationProp<RootStackParamList>;
@@ -124,22 +127,54 @@ export function ContactActivity({ contact }: Props) {
}; };
}, [addressKey]); // eslint-disable-line react-hooks/exhaustive-deps }, [addressKey]); // eslint-disable-line react-hooks/exhaustive-deps
const upcomingEvents = React.useMemo(() => { const pickUpcoming = React.useCallback((events: CalendarEvent[]): CalendarEvent[] => {
if (addresses.length === 0) return [];
const addrSet = new Set(addresses); const addrSet = new Set(addresses);
const now = new Date(); const now = new Date();
return calendarEvents const horizon = new Date(now.getTime() + EVENT_LOOKAHEAD_DAYS * 24 * 60 * 60 * 1000);
return events
.filter((e) => eventInvolvesContact(e, addrSet)) .filter((e) => eventInvolvesContact(e, addrSet))
.filter((e) => { .filter((e) => {
try { try {
return isAfter(parseISO(e.utcStart || e.start), now); const start = parseISO(e.utcStart || e.start);
return isAfter(start, now) && !isAfter(start, horizon);
} catch { } catch {
return false; return false;
} }
}) })
.sort((a, b) => (a.utcStart || a.start).localeCompare(b.utcStart || b.start)) .sort((a, b) => (a.utcStart || a.start).localeCompare(b.utcStart || b.start))
.slice(0, EVENT_LIMIT); .slice(0, EVENT_LIMIT);
}, [calendarEvents, addresses]); }, [addresses]);
// Ask the server for the next year instead of relying on whatever range the
// calendar tab happened to load (empty on a fresh start). The cached events
// render immediately while the query is in flight.
const [serverEvents, setServerEvents] = React.useState<CalendarEvent[] | null>(null);
React.useEffect(() => {
if (addresses.length === 0 || !hasCalendarCapability()) {
setServerEvents(null);
return;
}
let cancelled = false;
(async () => {
try {
const now = new Date();
const before = new Date(now.getTime() + EVENT_LOOKAHEAD_DAYS * 24 * 60 * 60 * 1000);
const ids = await queryEvents([], now.toISOString(), before.toISOString());
const fetched = ids.length > 0 ? await getEvents(ids) : [];
if (!cancelled) setServerEvents(fetched);
} catch {
if (!cancelled) setServerEvents(null);
}
})();
return () => {
cancelled = true;
};
}, [addressKey]); // eslint-disable-line react-hooks/exhaustive-deps
const upcomingEvents = React.useMemo(() => {
if (addresses.length === 0) return [];
return pickUpcoming(serverEvents ?? calendarEvents);
}, [serverEvents, calendarEvents, addresses, pickUpcoming]);
if (addresses.length === 0) return null; if (addresses.length === 0) return null;