import { requireOptionalNativeModule } from "expo-modules-core"; import { Platform } from "react-native"; import { formatElapsedHoursMinutes, formatElapsedSeconds, resolveClockDescription } from "@/lib/time-clock"; import type { TimeClockActivityProps } from "@/lib/time-clock-live-activity.types"; type RunningEntry = { description: string; startedAt: Date | string; client?: { name: string } | null; invoice?: { invoicePrefix: string | null; invoiceNumber: string } | null; }; type LiveActivityHandle = { update: (props: TimeClockActivityProps) => Promise; end: (policy?: "default" | "immediate") => Promise; }; type LiveActivityFactory = { start: (props: TimeClockActivityProps, url?: string) => LiveActivityHandle; getInstances: () => LiveActivityHandle[]; }; let factoryCache: LiveActivityFactory | null | undefined; function isExpoWidgetsAvailable() { return Platform.OS === "ios" && requireOptionalNativeModule("ExpoWidgets") != null; } function getFactory(): LiveActivityFactory | null { if (factoryCache !== undefined) { return factoryCache; } if (!isExpoWidgetsAvailable()) { factoryCache = null; return null; } try { factoryCache = require("@/widgets/TimeClockActivity").default as LiveActivityFactory; } catch { factoryCache = null; } return factoryCache; } export function isTimeClockLiveActivitySupported() { return getFactory() != null; } export function buildTimeClockActivityProps( running: RunningEntry, elapsedSeconds: number, ): TimeClockActivityProps { const invoice = running.invoice; return { startedAtMs: new Date(running.startedAt).getTime(), elapsed: formatElapsedSeconds(elapsedSeconds), elapsedShort: formatElapsedHoursMinutes(elapsedSeconds), clockTime: new Date().toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit", }), description: resolveClockDescription(running.description), clientName: running.client?.name ?? "", invoiceLabel: invoice ? `${invoice.invoicePrefix ?? "#"}${invoice.invoiceNumber}` : "", }; } export async function syncTimeClockLiveActivity( running: RunningEntry | null | undefined, elapsedSeconds: number, ) { const factory = getFactory(); if (!factory) return; if (!running) { await endTimeClockLiveActivity(); return; } try { const props = buildTimeClockActivityProps(running, elapsedSeconds); const instances = factory.getInstances(); if (instances.length > 0) { await instances[0]!.update(props); return; } factory.start(props, "beenvoice://timer"); } catch (error) { if (__DEV__) { console.warn("[LiveActivity] sync failed:", error); } factoryCache = undefined; } } export async function endTimeClockLiveActivity() { const factory = getFactory(); if (!factory) return; try { const instances = factory.getInstances(); await Promise.all(instances.map((instance) => instance.end("immediate"))); } catch { factoryCache = undefined; } }