Add 'apps/mobile/' from commit '5fa30f365f21531094cd4d2045042bb4f1370ac3'
git-subtree-dir: apps/mobile git-subtree-mainline:86f8987dffgit-subtree-split:5fa30f365f
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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;
|
||||
business?: { name: string } | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type LiveActivityHandle = {
|
||||
update: (props: TimeClockActivityProps) => Promise<void>;
|
||||
end: (policy?: "default" | "immediate") => Promise<void>;
|
||||
};
|
||||
|
||||
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 ?? "",
|
||||
businessName: invoice?.business?.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;
|
||||
}
|
||||
|
||||
const instance = factory.start(props, "beenvoice://timer");
|
||||
await instance.update(props);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user