import { HStack, Image, Text, VStack } from "@expo/ui/swift-ui"; import { frame, font, foregroundStyle, layoutPriority, lineLimit, minimumScaleFactor, monospacedDigit, padding, truncationMode, widgetAccentedRenderingMode, } from "@expo/ui/swift-ui/modifiers"; import { createLiveActivity, type LiveActivityEnvironment } from "expo-widgets"; import type { TimeClockActivityProps } from "@/lib/time-clock-live-activity.types"; function TimeClockActivity(props: TimeClockActivityProps, _environment: LiveActivityEnvironment) { "widget"; const island = "#FFFFFF"; const businessLabel = props.businessName.trim(); const clientLabel = props.clientName.trim(); const title = clientLabel || businessLabel || "Clock In"; const timerMods = [ font({ weight: "bold", size: 17 }), foregroundStyle({ type: "hierarchical", style: "primary" }), lineLimit(1), minimumScaleFactor(0.75), layoutPriority(2), frame({ width: 100, alignment: "center" }), ]; // A live timer Text reserves a large ideal width (for the H:MM:SS format at // the Dynamic Island's font), so with no width cap it inflates the compact // pill to full width. Pin it to a snug fixed width sized for the common // MM:SS case; minimumScaleFactor lets the occasional H:MM:SS scale down to // fit instead of widening the pill. This keeps the pill tight with no gap. const compactTimerMods = [ font({ design: "monospaced", weight: "semibold", size: 11 }), monospacedDigit(), foregroundStyle(island), lineLimit(1), minimumScaleFactor(0.6), frame({ width: 40, alignment: "center" }), ]; const clientMods = [ font({ weight: "bold", size: 17 }), foregroundStyle({ type: "hierarchical", style: "primary" }), lineLimit(1), truncationMode("tail"), ]; // Avoid greedy layout primitives here: the live timerInterval Text can // collapse when paired with Spacer/maxWidth. Fixed centered boxes keep the // content stable without forcing left/right edge alignment. const titleRowMods = [ layoutPriority(1), frame({ width: 140, alignment: "center" }), ]; const sideZoneMods = [ frame({ width: 100, alignment: "center" }), ]; const bannerRowMods = [ padding({ horizontal: 10, vertical: 10 }), frame({ maxWidth: Infinity, alignment: "center" }), ]; const startedAt = new Date(props.startedAtMs); // Bounded to 24h so SwiftUI reserves width for the H:MM:SS format only. // (An open-ended `.timer`/`.date` style reserves width for a huge duration, // which blows the compact Dynamic Island pill out to full width.) const timerRange = { lower: startedAt, upper: new Date(props.startedAtMs + 24 * 60 * 60 * 1000), }; // Banner (Notification Center / Lock Screen): timerInterval inside a fixed // 100pt box, which absorbs the reserved width. const bannerTimer = ( ); // Dynamic Island (compact): same bounded timerInterval, sized intrinsically — // no fixed/minWidth frame or layoutPriority, so the pill hugs the reserved // H:MM:SS width instead of being padded out or stretched. const compactTimer = ( ); const logoLarge = ( ); const logoSmall = ( ); // Apple Watch / CarPlay (`bannerSmall`). The strip is only ~150-180pt wide, // so it cannot use the iPhone banner's fixed 100/140/100 columns (those // overflow and push the timer off-screen, leaving just its first digit). // Instead: fixed-size logo, a flexible client name that truncates to fill // the leftover space, and a compact fixed-width timer that stays fully // visible (fixed width — not maxWidth — so the live timer text doesn't // collapse). const watchRowMods = [ padding({ horizontal: 10, vertical: 6 }), frame({ maxWidth: Infinity, alignment: "center" }), ]; const watchTitleMods = [ font({ weight: "semibold", size: 14 }), foregroundStyle({ type: "hierarchical", style: "primary" }), lineLimit(1), truncationMode("tail"), frame({ maxWidth: Infinity, alignment: "leading" }), ]; const watchTimerMods = [ font({ design: "monospaced", weight: "bold", size: 14 }), monospacedDigit(), foregroundStyle({ type: "hierarchical", style: "primary" }), lineLimit(1), minimumScaleFactor(0.6), frame({ width: 58, alignment: "trailing" }), ]; const watchLogo = ( ); const watchTimer = ( ); return { banner: ( {logoLarge} {title} {bannerTimer} ), bannerSmall: ( {watchLogo} {title} {watchTimer} ), compactLeading: logoSmall, compactTrailing: compactTimer, minimal: logoSmall, }; } export default createLiveActivity("TimeClockActivity", TimeClockActivity);