/** * Device contacts import (mobile). Reads the OS address book via the * `ulti-core` contacts plugin (Android ContactsContract / iOS Contacts) and * maps to the shared `ContactImportInput` shape used by the import dialog. */ import { invoke } from "@/lib/native/bridge" import { isTauriRuntime } from "@/lib/platform" import type { ContactImportInput } from "@/lib/contacts/import-parsers" type DeviceContact = { display_name?: string | null first_name?: string | null last_name?: string | null emails: string[] phones: string[] organization?: string | null } function splitDisplayName(name: string): { firstName: string; lastName: string } { const parts = name.trim().split(/\s+/) if (parts.length <= 1) return { firstName: parts[0] ?? "", lastName: "" } return { firstName: parts[0], lastName: parts.slice(1).join(" ") } } function toImportInput(c: DeviceContact): ContactImportInput { let firstName = c.first_name ?? "" let lastName = c.last_name ?? "" if (!firstName && !lastName && c.display_name) { const parts = splitDisplayName(c.display_name) firstName = parts.firstName lastName = parts.lastName } return { firstName, lastName, company: c.organization ?? undefined, emails: c.emails.map((value) => ({ value, label: "personal" })), phones: c.phones.map((value) => ({ value, label: "mobile" })), } } /** True when device contacts import is available (native runtime). */ export function deviceContactsAvailable(): boolean { return isTauriRuntime() } /** Fetch device contacts. Throws a coded error if unsupported/denied. */ export async function fetchDeviceContacts(): Promise { const list = await invoke("plugin:ulti-core|contacts_fetch") if (!list) throw new Error("contacts_unavailable") return list.map(toImportInput) }